Skip to main content

Overview

Binary Ninja’s Intermediate Language (IL) representations provide architecture-independent views of code. This guide demonstrates how to parse, visit, and analyze IL operations using real examples from the Binary Ninja API.

IL Levels

Binary Ninja provides multiple IL representations, each with different levels of abstraction:

LLIL

Low Level IL - Close to assembly, architecture-specific details normalized

MLIL

Medium Level IL - Variables instead of registers, simplified expressions

HLIL

High Level IL - C-like representation with control flow structures

Accessing IL Functions

func = bv.get_function_at(0x401000)
llil = func.low_level_il

# Iterate through basic blocks
for block in llil.basic_blocks:
    for instr in block:
        print(f"{instr.instr_index} @ {instr.address:#x}: {instr}")

Parsing LLIL Instructions

The Low Level IL parser example from Binary Ninja source demonstrates how to recursively parse IL expression trees.
1

Access IL Instruction

Ref<LowLevelILFunction> il = func->GetLowLevelIL();
if (!il) {
    printf("Function does not have LLIL\n");
    return;
}

// Loop through basic blocks
for (auto& block : il->GetBasicBlocks()) {
    for (size_t instrIndex = block->GetStart();
         instrIndex < block->GetEnd(); instrIndex++) {
        LowLevelILInstruction instr = (*il)[instrIndex];
        // Process instruction
    }
}
2

Display Instruction Operation

// Get the formatted text representation
vector<InstructionTextToken> tokens;
il->GetInstructionText(func, func->GetArchitecture(), instrIndex, tokens);

printf("%zu @ 0x%llx  ", instrIndex, instr.address);
for (auto& token : tokens)
    printf("%s", token.text.c_str());
printf("\n");
3

Parse Operands Recursively

void PrintILExpr(const LowLevelILInstruction& instr, size_t indent) {
    PrintIndent(indent);
    PrintOperation(instr.operation);
    printf("\n");

    indent++;

    for (auto& operand : instr.GetOperands()) {
        switch (operand.GetType()) {
        case IntegerLowLevelOperand:
            PrintIndent(indent);
            printf("int 0x%llx\n", operand.GetInteger());
            break;

        case RegisterLowLevelOperand:
            PrintIndent(indent);
            printf("reg ");
            PrintRegister(instr.function, operand.GetRegister());
            printf("\n");
            break;

        case ExprLowLevelOperand:
            // Recursively parse subexpression
            PrintILExpr(operand.GetExpr(), indent);
            break;

        case SSARegisterLowLevelOperand:
            PrintIndent(indent);
            printf("ssa reg ");
            PrintRegister(instr.function, operand.GetSSARegister().reg);
            printf("#%zu\n", operand.GetSSARegister().version);
            break;

        // ... handle other operand types
        }
    }
}

LLIL Operation Types

Common LLIL operations you’ll encounter:
OperationDescriptionExample
LLIL_SET_REGRegister assignmentrax = 0x1234
LLIL_LOADMemory readrax = [rbp - 8]
LLIL_STOREMemory write[rsp] = rax
LLIL_CALLFunction callcall(0x401000)
LLIL_SYSCALLSystem callsyscall
LLIL_ADDAdditionrax + rbx
LLIL_CMP_EEquality comparisonrax == 0
LLIL_IFConditional branchif (condition)

Using IL Visitors

Visitors provide a powerful way to find specific patterns in IL code without manual recursion.
// Example: Find all constants in an instruction
instr.VisitExprs([&](const LowLevelILInstruction& expr) {
    switch (expr.operation) {
    case LLIL_CONST:
    case LLIL_CONST_PTR:
    case LLIL_EXTERN_PTR:
        printf("Found constant 0x%llx\n", expr.GetConstant());
        return false;  // Stop visiting this branch
    default:
        break;
    }
    return true;  // Continue visiting subexpressions
});
Return true to continue visiting child expressions, false to stop.

Parsing MLIL Instructions

Medium Level IL uses variables instead of registers and simplifies expressions.
1

Access MLIL Function

Ref<MediumLevelILFunction> il = func->GetMediumLevelIL();
if (!il) {
    printf("Function does not have MLIL\n");
    return;
}
2

Iterate Instructions

for (auto& block : il->GetBasicBlocks()) {
    for (size_t instrIndex = block->GetStart();
         instrIndex < block->GetEnd(); instrIndex++) {
        MediumLevelILInstruction instr = (*il)[instrIndex];

        // Display instruction
        vector<InstructionTextToken> tokens;
        il->GetInstructionText(func, func->GetArchitecture(),
                             instrIndex, tokens);
        printf("%zu @ 0x%llx  ", instrIndex, instr.address);
        for (auto& token : tokens)
            printf("%s", token.text.c_str());
        printf("\n");
    }
}
3

Parse MLIL Operands

void PrintILExpr(const MediumLevelILInstruction& instr, size_t indent) {
    for (auto& operand : instr.GetOperands()) {
        switch (operand.GetType()) {
        case IntegerMediumLevelOperand:
            printf("int 0x%llx\n", operand.GetInteger());
            break;

        case VariableMediumLevelOperand:
            printf("var ");
            PrintVariable(instr.function, operand.GetVariable());
            printf("\n");
            break;

        case SSAVariableMediumLevelOperand:
            printf("ssa var ");
            PrintVariable(instr.function,
                        operand.GetSSAVariable().var);
            printf("#%zu\n", operand.GetSSAVariable().version);
            break;

        case ExprMediumLevelOperand:
            PrintILExpr(operand.GetExpr(), indent);
            break;
        }
    }
}

MLIL Operation Types

OperationDescriptionExample
MLIL_SET_VARVariable assignmentvar_10 = 0x1234
MLIL_LOADMemory readvar_8 = [rbp - 0x10]
MLIL_STOREMemory write[var_10] = var_8
MLIL_CALLFunction callcall(sub_401000)
MLIL_VARVariable referencevar_10
MLIL_VAR_SSASSA variablevar_10#1
MLIL_IFConditionalif (var_8 == 0)

Templated Accessors

Binary Ninja provides type-safe templated accessors for efficiently parsing specific IL operations.
// Type-safe access to LOAD instruction components
if (expr.operation == MLIL_LOAD) {
    // GetSourceExpr is templated on the operation type
    auto src = expr.GetSourceExpr<MLIL_LOAD>();

    if (src.operation == MLIL_CONST_PTR) {
        uint64_t addr = src.GetConstant<MLIL_CONST_PTR>();
        printf("Loading from constant address 0x%llx\n", addr);
    }
}

// Type-safe access to binary operations
if (expr.operation == MLIL_ADD) {
    auto left = expr.GetLeftExpr<MLIL_ADD>();
    auto right = expr.GetRightExpr<MLIL_ADD>();
    // Process addition operands
}
Templated accessors require you to specify the operation type. Make sure the operation matches before calling templated methods.

SSA Form

Both LLIL and MLIL have Static Single Assignment (SSA) forms that make data flow analysis easier.
# Access SSA form
llil_ssa = func.low_level_il.ssa_form
mlil_ssa = func.medium_level_il.ssa_form

# SSA instructions have version numbers
for instr in mlil_ssa:
    if instr.operation == MediumLevelILOperation.MLIL_SET_VAR_SSA:
        var = instr.dest
        print(f"{var.var.name}#{var.version} = {instr.src}")

SSA Phi Nodes

from binaryninja.enums import MediumLevelILOperation

for instr in mlil_ssa:
    if instr.operation == MediumLevelILOperation.MLIL_VAR_PHI:
        dest = instr.dest
        sources = instr.src
        print(f"{dest.var.name}#{dest.version} = φ({sources})")

Complete LLIL Parser Example

Here’s the complete LLIL parser from Binary Ninja source:
~/workspace/source/examples/llil_parser/src/llil_parser.cpp
Ref<BinaryView> bv = BinaryNinja::Load(argv[1]);
if (!bv || bv->GetTypeName() == "Raw") {
    fprintf(stderr, "Input file does not appear to be an executable\n");
    return -1;
}

// Go through all functions in the binary
for (auto& func : bv->GetAnalysisFunctionList()) {
    Ref<Symbol> sym = func->GetSymbol();
    if (sym)
        printf("Function %s:\n", sym->GetFullName().c_str());
    else
        printf("Function at 0x%llx:\n", func->GetStart());

    Ref<LowLevelILFunction> il = func->GetLowLevelIL();
    if (!il) {
        printf("    Does not have LLIL\n\n");
        continue;
    }

    for (auto& block : il->GetBasicBlocks()) {
        for (size_t instrIndex = block->GetStart();
             instrIndex < block->GetEnd(); instrIndex++) {
            LowLevelILInstruction instr = (*il)[instrIndex];

            // Display instruction
            vector<InstructionTextToken> tokens;
            il->GetInstructionText(func, func->GetArchitecture(),
                                 instrIndex, tokens);
            printf("    %zu @ 0x%llx  ", instrIndex, instr.address);
            for (auto& token : tokens)
                printf("%s", token.text.c_str());
            printf("\n");

            // Parse IL tree
            PrintILExpr(instr, 2);

            // Find constants using visitor
            instr.VisitExprs([&](const LowLevelILInstruction& expr) {
                switch (expr.operation) {
                case LLIL_CONST:
                case LLIL_CONST_PTR:
                case LLIL_EXTERN_PTR:
                    printf("        Found constant 0x%llx\n",
                          expr.GetConstant());
                    return false;
                default:
                    break;
                }
                return true;
            });
        }
    }
    printf("\n");
}

Next Steps

Data Flow Analysis

Use IL to track data flow and perform advanced analysis

Binary Analysis

Learn fundamental binary analysis workflows

Build docs developers (and LLMs) love