Binary view plugins allow you to add support for new binary file formats to Binary Ninja. They parse headers, create segments, and set up the binary for analysis.
When to create a binary view plugin
Create a binary view plugin when you need to:
Load custom or proprietary binary formats
Parse firmware images with specific structures
Handle containers with multiple embedded binaries
Create specialized views of existing formats
Binary Ninja includes built-in loaders for ELF, PE, Mach-O, and other common formats.
Binary view hierarchy
Binary views form a hierarchy:
Raw View (raw bytes)
└─ Format View (ELF, PE, Mach-O, etc.)
└─ Mapped View (memory mapping)
Your plugin typically creates a Format View that sits between the raw view and analysis.
Creating a binary view
A complete binary view plugin must:
Validate the file format
Parse headers and structures
Define segments and sections
Set the architecture and platform
Add entry points
Example: Nintendo DS ROM loader
Here’s a real binary view plugin that loads Nintendo DS ROM files:
from binaryninja import BinaryView, Architecture
from binaryninja.enums import SegmentFlag
from binaryninja.log import log_error
import struct
import traceback
class DSView ( BinaryView ):
name = "NDS"
long_name = "Nintendo DS ROM"
def __init__ ( self , data ):
BinaryView. __init__ ( self , file_metadata = data.file, parent_view = data)
self .raw = data
@ classmethod
def is_valid_for_data ( cls , data ):
"""Validate this is a Nintendo DS ROM"""
hdr = data.read( 0 , 0x 160 )
if len (hdr) < 0x 160 :
return False
# Validate CRC16 checksums
header_crc = struct.unpack( "<H" , hdr[ 0x 15e : 0x 160 ])[ 0 ]
logo_crc = struct.unpack( "<H" , hdr[ 0x 15c : 0x 15e ])[ 0 ]
if header_crc != crc16(hdr[ 0 : 0x 15e ]):
return False
if logo_crc != crc16(hdr[ 0x c0 : 0x 15c ]):
return False
return True
def init ( self ):
"""Initialize the view"""
try :
# Set architecture
self .platform = Architecture[ "armv7" ].standalone_platform
# Read header
hdr = self .raw.read( 0 , 0x 160 )
# Parse ARM9 section
arm9_offset = struct.unpack( "<L" , hdr[ 0x 20 : 0x 24 ])[ 0 ]
arm9_entry = struct.unpack( "<L" , hdr[ 0x 24 : 0x 28 ])[ 0 ]
arm9_load_addr = struct.unpack( "<L" , hdr[ 0x 28 : 0x 2C ])[ 0 ]
arm9_size = struct.unpack( "<L" , hdr[ 0x 2C : 0x 30 ])[ 0 ]
# Add ARM9 segment
self .add_auto_segment(
arm9_load_addr, # Virtual address
arm9_size, # Virtual size
arm9_offset, # File offset
arm9_size, # File size
SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable
)
# Add entry point
self .add_entry_point(arm9_entry)
return True
except :
log_error(traceback.format_exc())
return False
def perform_is_executable ( self ):
return True
def perform_get_entry_point ( self ):
hdr = self .raw.read( 0 , 0x 160 )
return struct.unpack( "<L" , hdr[ 0x 24 : 0x 28 ])[ 0 ]
# Register the view
DSView.register()
From python/examples/nds.py
This plugin creates separate views for ARM9 and ARM7 processors in the DS ROM.
Key methods to implement
is_valid_for_data(cls, data)
Validates whether this view can handle the file:
@ classmethod
def is_valid_for_data ( cls , data ):
"""Check magic number or file signature"""
magic = data.read( 0 , 4 )
return magic == b ' \x7f ELF' # Example: ELF magic
This method must be fast - it’s called for every file. Avoid expensive parsing.
init(self)
Initializes the view by parsing the file:
def init ( self ):
try :
# 1. Parse headers
header = self .parse_header()
# 2. Set architecture and platform
self .platform = Architecture[header.arch].standalone_platform
# 3. Create segments
for segment in header.segments:
self .add_auto_segment(
segment.vaddr,
segment.vsize,
segment.offset,
segment.size,
segment.flags
)
# 4. Create sections
for section in header.sections:
self .add_auto_section(
section.name,
section.start,
section.length,
section.semantics
)
# 5. Add entry point
self .add_entry_point(header.entry)
# 6. Define symbols
for sym in header.symbols:
self .define_auto_symbol(Symbol(
sym.type,
sym.address,
sym.name
))
return True
except Exception as e:
log_error( f "Failed to initialize view: { e } " )
return False
Optional methods
perform_get_entry_point()
Returns the entry point address: def perform_get_entry_point ( self ):
return self .entry_point
perform_get_default_endianness()
Creating segments and sections
Segments
Segments define memory regions:
from binaryninja.enums import SegmentFlag
# Read-only code segment
self .add_auto_segment(
0x 1000 , # Virtual address
0x 5000 , # Virtual size
0x 200 , # File offset
0x 5000 , # File size
SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable
)
# Read-write data segment
self .add_auto_segment(
0x 6000 ,
0x 2000 ,
0x 5200 ,
0x 2000 ,
SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable
)
Sections
Sections provide semantic information:
from binaryninja.enums import SectionSemantics
self .add_auto_section(
".text" , # Name
0x 1000 , # Start address
0x 5000 , # Length
SectionSemantics.ReadOnlyCodeSectionSemantics
)
self .add_auto_section(
".data" ,
0x 6000 ,
0x 2000 ,
SectionSemantics.ReadWriteDataSectionSemantics
)
Multi-view binaries
Some formats contain multiple distinct binaries (like Nintendo DS with ARM9/ARM7). Create separate views:
class DSArm9View ( BinaryView ):
name = "NDS_ARM9"
long_name = "Nintendo DS ARM9"
# ... implementation for ARM9
class DSArm7View ( BinaryView ):
name = "NDS_ARM7"
long_name = "Nintendo DS ARM7"
# ... implementation for ARM7
# Register both views
DSArm9View.register()
DSArm7View.register()
Users can switch between views using View → Select View in the UI.
Handling relocations
For relocatable binaries (PIE, shared libraries):
from binaryninja import Relocation
def init ( self ):
# ... parse relocations from file
for reloc in relocations:
self .add_relocation(
Architecture[ self .arch],
Relocation(
reloc.address,
reloc.type,
reloc.target,
reloc.addend
)
)
Setting up imports and exports
# Define imported functions
for import_entry in imports:
self .define_auto_symbol(Symbol(
SymbolType.ImportedFunctionSymbol,
import_entry.address,
import_entry.name
))
# Define exported functions
for export_entry in exports:
self .define_auto_symbol(Symbol(
SymbolType.FunctionSymbol,
export_entry.address,
export_entry.name
))
For formats with embedded type information:
from binaryninja import Type
# Define structures
struct_type = Type.structure_type(
StructureBuilder.create([
(Type.int( 4 ), "field1" ),
(Type.pointer( self .arch, Type.char()), "field2" )
])
)
self .define_user_type( "MyStruct" , struct_type)
Complete example: NES ROM loader
The NES ROM loader is a complete example including architecture and format:
Testing your binary view
from binaryninja import BinaryViewType
# Test loading
bv = BinaryViewType[ 'MyFormat' ].open( 'test.bin' )
print ( f "Architecture: { bv.arch } " )
print ( f "Platform: { bv.platform } " )
print ( f "Entry point: { hex (bv.entry_point) } " )
print ( " \n Segments:" )
for segment in bv.segments:
print ( f " { hex (segment.start) } - { hex (segment.end) } : { segment.flags } " )
print ( " \n Sections:" )
for section in bv.sections:
print ( f " { section.name } : { hex (section.start) } " )
Next steps
Architecture plugins Add CPU architecture support
UI plugins Extend the Binary Ninja interface