Skip to main content
The Low Level IL (LLIL) is Binary Ninja’s lowest-level intermediate representation. It provides a platform-independent view of machine code while preserving architectural details.

Overview

LLIL is the first IL produced during analysis. Key characteristics:
  • Architecture-independent - Abstract away instruction encoding
  • Preserves details - Maintains flags, register splits, etc.
  • SSA form available - For data flow analysis
  • Tree-based - Instructions form expression trees

Accessing LLIL

# From a function
llil = func.llil
llil_ssa = func.llil.ssa_form

# Get LLIL at specific address
llil_instr = func.get_low_level_il_at(0x401000)

# Iterate all instructions
for instr in llil.instructions:
    print(f"{instr.address:#x}: {instr}")

LowLevelILFunction

Represents a function in LLIL.
source_function
Function
The original Function object
ssa_form
LowLevelILFunction
SSA form of this IL function
non_ssa_form
LowLevelILFunction
Non-SSA form
instructions
Generator[LowLevelILInstruction]
All IL instructions in the function
basic_blocks
list[LowLevelILBasicBlock]
Basic blocks in the IL function

Iterating LLIL

# By instruction
for instr in llil.instructions:
    print(instr)

# By basic block
for block in llil:
    print(f"Block {block.start}-{block.end}")
    for instr in block:
        print(f"  {instr}")

# Get instruction by index
instr = llil[5]  # Get instruction at index 5

# Get instruction for expression index
expr_idx = 10
instr = llil.get_instruction(expr_idx)

LowLevelILInstruction

Base class for all LLIL instructions (3.0 class hierarchy).

Core Properties

operation
LowLevelILOperation
The operation type (LLIL_SET_REG, LLIL_LOAD, etc.)
address
int
Address of the native instruction
size
int
Size of the operation in bytes
expr_index
int
Index of this expression in the IL
instr_index
Optional[int]
Index of the instruction (vs expression)
function
LowLevelILFunction
The IL function containing this instruction

Common Operations

In Binary Ninja 3.0, use isinstance() with instruction hierarchy classes to check instruction types.
from binaryninja.commonil import (
    Constant, Load, Store, BinaryOperation,
    Call, Return, Terminal
)

for instr in llil.instructions:
    # Check using class hierarchy
    if isinstance(instr, Constant):
        print(f"Constant: {instr.value}")
    
    elif isinstance(instr, Load):
        print(f"Load from {instr.src}")
    
    elif isinstance(instr, Store):
        print(f"Store to {instr.dest}")
    
    elif isinstance(instr, BinaryOperation):
        print(f"Binary op: {instr.left} {instr.operation} {instr.right}")
    
    elif isinstance(instr, Call):
        print(f"Call to {instr.dest}")
    
    elif isinstance(instr, Return):
        print("Return")

Register Operations

# SET_REG: dest = src
if instr.operation == LowLevelILOperation.LLIL_SET_REG:
    dest_reg = instr.dest  # ILRegister
    src_expr = instr.src   # Expression
    print(f"{dest_reg.name} = {src_expr}")

# REG: read register value
if instr.operation == LowLevelILOperation.LLIL_REG:
    reg = instr.src  # ILRegister
    print(f"Read {reg.name}")

# SET_REG_SPLIT: combine two registers
if instr.operation == LowLevelILOperation.LLIL_SET_REG_SPLIT:
    hi_reg = instr.hi
    lo_reg = instr.lo
    src_expr = instr.src
    print(f"{hi_reg.name}:{lo_reg.name} = {src_expr}")

Memory Operations

# LOAD: read from memory
if instr.operation == LowLevelILOperation.LLIL_LOAD:
    addr_expr = instr.src
    size = instr.size
    print(f"Load {size} bytes from {addr_expr}")

# STORE: write to memory  
if instr.operation == LowLevelILOperation.LLIL_STORE:
    dest_expr = instr.dest
    src_expr = instr.src
    size = instr.size
    print(f"Store {size} bytes to {dest_expr}")

Arithmetic Operations

# ADD, SUB, MUL, etc.
from binaryninja.enums import LowLevelILOperation

if instr.operation == LowLevelILOperation.LLIL_ADD:
    left = instr.left
    right = instr.right
    print(f"{left} + {right}")

# Floating point operations
if instr.operation == LowLevelILOperation.LLIL_FADD:
    print(f"{instr.left} +. {instr.right}")

Control Flow

# CALL
if instr.operation == LowLevelILOperation.LLIL_CALL:
    dest = instr.dest
    print(f"Call {dest}")
    
    # Get MLIL for call
    if instr.mlil:
        print(f"MLIL: {instr.mlil}")

# JUMP (unconditional)
if instr.operation == LowLevelILOperation.LLIL_JUMP:
    dest = instr.dest
    print(f"Jump to {dest}")

# IF (conditional)
if instr.operation == LowLevelILOperation.LLIL_IF:
    condition = instr.condition
    true_target = instr.true
    false_target = instr.false
    print(f"If {condition} goto {true_target} else {false_target}")

# RET (return)
if instr.operation == LowLevelILOperation.LLIL_RET:
    dest = instr.dest
    print(f"Return to {dest}")

ILRegister, ILFlag, ILIntrinsic

LLIL uses special wrapper classes for architecture elements:

ILRegister

reg = instr.dest  # ILRegister

print(f"Name: {reg.name}")
print(f"Index: {reg.index}")
print(f"Size: {reg.info.size}")
print(f"Is temp: {reg.temp}")

ILFlag

# Flag operations
if instr.operation == LowLevelILOperation.LLIL_SET_FLAG:
    flag = instr.dest  # ILFlag
    print(f"Set flag {flag.name}")

# Flag conditions
if instr.operation == LowLevelILOperation.LLIL_FLAG_COND:
    cond = instr.condition  # LowLevelILFlagCondition
    print(f"Condition: {cond}")

ILIntrinsic

# Architecture intrinsics
if instr.operation == LowLevelILOperation.LLIL_INTRINSIC:
    intrinsic = instr.intrinsic  # ILIntrinsic
    params = instr.param
    outputs = instr.output
    
    print(f"Intrinsic: {intrinsic.name}")
    print(f"Inputs: {intrinsic.inputs}")
    print(f"Outputs: {intrinsic.outputs}")

SSA Form

SSA (Static Single Assignment) form is useful for data flow analysis:
# Get SSA form
llil_ssa = llil.ssa_form

# Iterate SSA instructions
for instr in llil_ssa.instructions:
    print(instr)

# SSA registers have version numbers
if instr.operation == LowLevelILOperation.LLIL_SET_REG_SSA:
    dest_ssa = instr.dest  # SSARegister
    print(f"{dest_ssa.reg.name}#{dest_ssa.version} = {instr.src}")

# Get definition site
if instr.operation == LowLevelILOperation.LLIL_REG_SSA:
    reg_ssa = instr.src
    def_instr = llil_ssa.get_ssa_reg_definition(reg_ssa)
    print(f"Defined at: {def_instr}")

# Get use sites
uses = llil_ssa.get_ssa_reg_uses(reg_ssa)
for use in uses:
    print(f"Used at: {use}")

SSA Register Class

# SSARegister
ssa_reg = instr.dest
print(f"Register: {ssa_reg.reg.name}")
print(f"Version: {ssa_reg.version}")

Lifted IL

Some architectures have a “lifted” IL form:
# Get lifted IL (if available)
lifted = func.lifted_il

if lifted:
    for instr in lifted.instructions:
        print(instr)

Visiting Instructions

Traverse expression trees:
def visit_callback(name, operand, operand_type, parent):
    print(f"  {name}: {operand}")
    return True  # Continue visiting

# Visit all operands
for instr in llil.instructions:
    print(f"Instruction: {instr}")
    instr.visit(visit_callback)

Example: Find Buffer Operations

def find_buffer_operations(func):
    """Find operations on stack buffers."""
    results = []
    
    # Get stack variables
    stack_vars = [v for v in func.vars 
                  if v.source_type == VariableSourceType.StackVariableSourceType]
    
    for block in func.llil:
        for instr in block:
            # Check loads
            if isinstance(instr, Load):
                # Check if loading from stack variable
                # (simplified - actual check is more complex)
                results.append({
                    'type': 'load',
                    'address': instr.address,
                    'instr': str(instr)
                })
            
            # Check stores
            elif isinstance(instr, Store):
                results.append({
                    'type': 'store',
                    'address': instr.address,
                    'instr': str(instr)
                })
    
    return results

# Usage
for op in find_buffer_operations(func):
    print(f"{op['address']:#x}: {op['type']} - {op['instr']}")

Example: Constant Propagation

def find_constants(llil_func):
    """Find all constant values used."""
    from binaryninja.commonil import Constant
    
    constants = set()
    
    for instr in llil_func.instructions:
        # Recursively find constants
        for operand in instr.prefix_operands:
            if isinstance(operand, Constant):
                constants.add(operand.value)
    
    return sorted(constants)

# Usage
for const in find_constants(func.llil):
    print(f"Constant: {const:#x}")

Instruction Hierarchy (3.0)

Binary Ninja 3.0 provides abstract base classes:
from binaryninja.commonil import (
    Constant,       # Constant values
    BinaryOperation,  # Binary arithmetic/logic
    UnaryOperation,   # Unary operations  
    Load,            # Memory loads
    Store,           # Memory stores
    Call,            # Function calls
    Return,          # Returns
    Terminal,        # Terminal instructions
    ControlFlow,     # Control flow changes
    SSA,             # SSA instructions
    Phi,             # Phi nodes
)

# Visualize hierarchy
from binaryninja.lowlevelil import LowLevelILInstruction
LowLevelILInstruction.show_llil_hierarchy()

See Also

Build docs developers (and LLMs) love