Skip to main content
Architecture plugins add support for new instruction sets to Binary Ninja. They define how to disassemble, lift to IL, and analyze code for a specific CPU architecture.

When to create an architecture plugin

Create an architecture plugin when:
  • You need to analyze binaries for an unsupported architecture
  • You want to add architecture-specific analysis improvements
  • You’re working with custom or proprietary instruction sets
Binary Ninja includes built-in support for x86, x86_64, ARM64, ARMv7, MIPS, PPC, RISC-V, and MSP430.

Architecture hooks vs. new architectures

Before creating a full architecture plugin, consider if an architecture hook is sufficient:
arch_hook.py
from binaryninja.architecture import Architecture, ArchitectureHook

class X86ReturnHook(ArchitectureHook):
    def get_instruction_text(self, data, addr):
        # Call the original implementation
        result, length = super().get_instruction_text(data, addr)

        # Patch the name of 'retn' to 'ret'
        if len(result) > 0 and result[0].text == 'retn':
            result[0].text = 'ret'

        return result, length

# Install the hook
X86ReturnHook(Architecture['x86']).register()
Architecture hooks modify existing architectures. Use them for:
  • Changing instruction mnemonics
  • Adding custom instruction annotations
  • Modifying flag behavior
From python/examples/arch_hook.py

Creating a new architecture

A complete architecture plugin must implement:
  1. Instruction disassembly - Convert bytes to instructions
  2. IL lifting - Translate instructions to LLIL
  3. Register definitions - Define CPU registers
  4. Flag definitions - Define status flags
  5. Calling conventions - Define function calling conventions

Architecture class structure

from binaryninja import Architecture, InstructionInfo, InstructionTextToken
from binaryninja.enums import BranchType, InstructionTextTokenType

class MyArchitecture(Architecture):
    name = "my_arch"
    address_size = 4  # 32-bit architecture
    default_int_size = 4
    instr_alignment = 4  # Instructions are 4-byte aligned
    max_instr_length = 4

    # Define registers
    regs = {
        'r0': RegisterIndex(0),
        'r1': RegisterIndex(1),
        # ... more registers
    }

    # Define stack pointer
    stack_pointer = 'sp'

    # Define flags
    flags = ['z', 'n', 'c', 'v']  # Zero, Negative, Carry, Overflow

    def get_instruction_info(self, data, addr):
        """Decode instruction and return branch info"""
        result = InstructionInfo()
        result.length = 4  # Instruction length

        # Decode instruction
        opcode = struct.unpack('>I', data[:4])[0]

        # Set branch info for control flow
        if is_branch(opcode):
            result.add_branch(BranchType.UnconditionalBranch, target)

        return result

    def get_instruction_text(self, data, addr):
        """Disassemble instruction to text"""
        tokens = []
        opcode = struct.unpack('>I', data[:4])[0]

        # Create instruction tokens
        tokens.append(InstructionTextToken(
            InstructionTextTokenType.InstructionToken,
            "add"
        ))
        tokens.append(InstructionTextToken(
            InstructionTextTokenType.OperandSeparatorToken,
            " "
        ))
        # ... add operand tokens

        return tokens, 4  # Return tokens and instruction length

    def get_instruction_low_level_il(self, data, addr, il):
        """Lift instruction to LLIL"""
        opcode = struct.unpack('>I', data[:4])[0]

        # Lift to LLIL
        if opcode_is_add(opcode):
            il.append(il.set_reg(
                4,  # Size
                dest_reg,
                il.add(4,
                    il.reg(4, src1_reg),
                    il.reg(4, src2_reg)
                )
            ))

        return 4  # Instruction length

# Register the architecture
MyArchitecture.register()

Implementing IL lifting

Lifting instructions to LLIL is critical for analysis. Each instruction must be translated to equivalent IL operations:
def get_instruction_low_level_il(self, data, addr, il):
    opcode = struct.unpack('>I', data[:4])[0]
    instr_type = (opcode >> 26) & 0x3F

    if instr_type == 0x00:  # ADD instruction
        rd = (opcode >> 11) & 0x1F
        rs = (opcode >> 21) & 0x1F
        rt = (opcode >> 16) & 0x1F

        il.append(il.set_reg(
            4,  # Register size
            rd,  # Destination register
            il.add(
                4,  # Operand size
                il.reg(4, rs),  # Source 1
                il.reg(4, rt)   # Source 2
            )
        ))

    elif instr_type == 0x08:  # Branch if equal
        rs = (opcode >> 21) & 0x1F
        rt = (opcode >> 16) & 0x1F
        offset = (opcode & 0xFFFF) << 2
        target = addr + 4 + sign_extend(offset, 18)

        # Create conditional branch
        t = il.get_label_for_address(Architecture['my_arch'], target)
        if t is None:
            t = LowLevelILLabel()

        f = il.get_label_for_address(Architecture['my_arch'], addr + 4)
        if f is None:
            f = LowLevelILLabel()

        il.append(il.if_expr(
            il.compare_equal(4, il.reg(4, rs), il.reg(4, rt)),
            t,
            f
        ))

    return 4  # Instruction length
Every instruction must be lifted to IL for Binary Ninja’s analysis to work correctly.

Defining calling conventions

Calling conventions define how functions pass arguments and return values:
from binaryninja import CallingConvention

class MyCallingConvention(CallingConvention):
    name = "my_cc"
    int_arg_regs = ['r0', 'r1', 'r2', 'r3']
    int_return_reg = 'r0'
    high_int_return_reg = 'r1'  # For 64-bit returns on 32-bit arch
    callee_saved_regs = ['r4', 'r5', 'r6', 'r7']

# Register with architecture
arch = Architecture['my_arch']
arch.register_calling_convention(MyCallingConvention(arch, 'default'))
arch.standalone_platform.default_calling_convention = arch.calling_conventions['default']

Testing your architecture

Test your architecture plugin with sample binaries:
from binaryninja import BinaryViewType

# Load a test binary
bv = BinaryViewType['Raw'].open("test_binary.bin")
bv.arch = Architecture['my_arch']
bv.platform = Architecture['my_arch'].standalone_platform

# Add entry point
bv.add_entry_point(0x1000)

# Verify disassembly
for func in bv.functions:
    print(f"Function at {hex(func.start)}:")
    for block in func:
        for instr in block:
            print(f"  {hex(instr[0])}: {instr[1]}")

Example architecture plugins

Binary Ninja’s built-in architectures are open source:
The NES (6502) architecture in python/examples/nes.py is a complete working example.

Next steps

Binary view plugins

Create custom format loaders

IL operations guide

Learn about LLIL operations

Build docs developers (and LLMs) love