SuperAPI AI Directory

re-binaryninja

Coding & development

View repository

Updated 10-15 21:34

About this skill

Binary Ninja reverse engineering workflow

When to use / when not to use

  • Use: need MLIL intermediate language (far more readable than raw disassembly) to read function logic; Python scripting for automated annotation/batch processing; choose by budget (Personal/Commercial)
  • Use: need DFG data flow graph (visualization of register/constant propagation paths) to trace the flow of a single piece of data (keys, flags)
  • Use: multi-architecture targets (common architectures such as x86/ARM/MIPS/RISC-V/PowerPC are all supported; mobile/embedded targets can be analyzed directly)
  • Use: as a decompiler in theRE_DECOMPILER=binaryninja scenario
  • Don't use: already have a Ghidra/IDA workflow and no motivation to switch (defaults to RE_DECOMPILER session variable, [[re-ghidra]])
  • Don't use: pure command-line quick analysis ([[re-radare2]] is lighter); simplest headless batch scenarios ([[re-ghidra]] analyzeHeadless ecosystem is more mature)
  • Don't use: targets focused on dynamic debugging (BN's built-in debugger is experimental — use [[re-gdb]] / [[re-x64dbg]] / [[re-lldb]] for debugging)
  • Don't use: large-scale headless automation on the Personal edition (headless is a Commercial edition capability, see [[gotchas]])

Tool preparation

See [[re-analyze/platform-tips]] — Binary Ninja static analysis needs no sandbox; when scripts trigger dynamic execution, the default is inside the sandbox.

Binary Ninja (commercial, has a Personal edition)

  • Download/purchase: official site binary.ninja (Commercial edition / Personal edition; Personal edition is for personal use only, automation and some features are restricted by edition; there is also a free Free tier)
  • Free edition: no API or plugin access, limited IL; supported architectures x86 / x86_64 / armv7+Thumb2 / armv8 — when you need MLIL or scripting automation, upgrade by budget
  • Windows: run the installer directly; macOS: brew install --cask binary-ninja(community cask, requires an existing license); Linux: extract the official tarball and run ./binaryninja
  • Container formats: directly import PE/ELF/Mach-O/Dalvik/firmware ROM etc. (no separate unpacking step needed); packed targets are the exception (the packed section is not decrypted, unpack first)
  • Verification: open any sample to complete automatic analysis, theFunctions panel has a function list;Options > About shows the license type
  • Linux without a graphical environment: the GUI version needs an X display (xvfb-run ./binaryninja can run temporarily); for batch tasks go directly through headless (Commercial edition), headless does not need a display
  • Version differences: 6.x is the current mainline; starting from 6.0 the plugin manager was restructured into the Extension Manager (the old pluginmanager is kept only as a shim), bundled Python was upgraded to 3.13 (covering Linux for the first time) while the minimum external Python is still 3.10 — there are breaking API changes between the 2.x → 3.x → 4.x → 6.x major versions, for script dependencies check core_version() and the release notes first (see [[gotchas]])

binaryninja Python API

  • Installation: in the Binary Ninja installation directory run python install_api.py(registers the API into the Python environment; there is no binaryninja package on PyPI,pip install binaryninja will fail)
  • The install script will detect/prompt for a matching Python version — when the version does not match, the script errors out or registration fails, switch Python versions as prompted
  • Verification: python3 -c "import binaryninja; print(binaryninja.core_version())" outputs a version number consistent with the software version
  • Headless batch processing (Commercial edition): there is no binaryninja-headless executable — the proper way is to register via install_api.py and then write scripts directly binaryninja.load()(see [[commands]] sequence 2); the headless license uses the official standalone download package (no GUI, only the headless license can be downloaded)

Procedure

  1. Import and Analysis:

    • File > Open Select sample → automatic analysis (progress bar at the bottom) → on the left Functions / Data / References view to confirm the analysis results
    • Confirm: the entry function (entry) is recognized, the import table (Imports view) has content, strings (Strings view) are visible
    • Analysis anomalies (few functions/garbled strings) → may be packed; first [[re-packer-id]] / [[re-anti-analysis]]
    • Analysis options: Options > Analysis check/uncheck pass——check here first when importing a large file or an obfuscated sample for the first time; leaving everything on by default will be slow
  2. MLIL Reading (IL View Switching):

    • Double-click a function to enter the decompilation view (HLIL High Level IL by default);i cycle between disassembly/LLIL/MLIL/HLIL, or use the Options menu in the bottom right to select the IL level
    • MLIL characteristics: variables made explicit (rax = rax ^ 0xdeadbeef), type recovery, semanticized conditional jumps——more readable than raw disassembly; for key functions, look at MLIL before drawing conclusions
    • MLIL example (disassembly vs MLIL):cmp eax, 0x1337 / jne L1 / mov byte [rdi], 1 / jmp L2 / L1: mov byte [rdi], 0 / L2: ... → MLIL directly presents if (rax == 0x1337) *(uint8_t*)rdi = 1; else *(uint8_t*)rdi = 0;——no need to follow jumps one by one to read the logic
    • When decompilation is unclear, downgrade: MLIL → LLIL (View > IL > LLIL, Low Level IL, retains instruction-level details) → disassembly (Space/assembly view) to verify layer by layer
    • Note: m key in Binary Ninja is "display integer as applied enum", not MLIL switching——to switch IL use i
  3. Rename / Type:

    • Rename: with the cursor on a symbol/variable, n(or right-click Rename)——works for functions, variables, and global data
    • Change type: y(e.g. void *、DWORD、int (*)(void *, size_t)); type propagation automatically improves the quality of subsequent decompilation
    • Define struct: Types create a new Structure in the panel → apply to a variable/pointer → field names propagate with the type into the decompilation view
    • Other type shortcuts: o set pointer reference,a set character array,d cycle integer width,* create array (see the [[commands]] shortcut table)
  4. Python Scripting (Automated Annotation):

    import binaryninja
    bv = binaryninja.load("sample.bin")
    bv.update_analysis_and_wait()          # 无头模式:手动触发完整分析
    for f in bv.functions:
        if f.analysis_skip_reason is None and f.medium_level_il:
            n = len(list(f.medium_level_il.instructions))
            if n > 200:
                print(f"big function: {f.name} @ {hex(f.start)} len={n}")
    
    • Batch rename: bv.get_function_at(0x401000).name = "check_license" then bv.save() write back
    • Export decompiled text: str(f.high_level_il) directly get the HLIL pseudocode string——the fastest way to batch export function logic
    • Plugins: 6.x is managed via Extension Manager (the duties of the old File > Manage Plugins are merged into it); plugin directories by platform: Windows %APPDATA%\Binary Ninja\plugins、Linux ~/.binaryninja/plugins、macOS ~/Library/Application Support/Binary Ninja/plugins(macOS is not ~/.binaryninja; if placed incorrectly it will not be loaded)
    • For your own scripts, first File > Python test-run in the panel (you can see print output and exceptions); once stable, convert to a plugin or headless script——debugging a script is much faster than running it and then looking at the results
    • Headless mode: headless is available in the commercial version——there is no binaryninja-headless CLI; the proper way is install_api.py after registering, write a script (binaryninja.load() + update_analysis_and_wait(), i.e., the code structure above); for the headless license use the official standalone download package; when the personal edition is restricted, use the same code executed within the GUI File > Python panel
    • Errors in headless scripts do not pop up dialogs—exceptions go straight to stderr/log files; for troubleshooting look at the tail of the output and the bv.log_info traces
  5. String/reference location chain:

    • Strings view to find the hint string → double-click to enter the disassembly → right-click Show References(or xrefs panel) → jump to the referencing function → read in MLIL
    • When there are many strings, sort by reference count/length (click the column header); prioritize strings referenced in multiple places—these are usually key nodes such as error messages/format strings
    • Same idea as [[re-ida]]'s Alt+T→x→F5 and [[re-radare2]]'s izz→axt→pdf; for batch annotation use bv.get_strings() + f.get_callers() scripting (see [[commands]] sequence 2)
  6. DFG data flow graph (single-value flow tracing):

    • In the MLIL/HLIL view, right-click an instruction → Show DFG—a graph of the variable/constant propagation paths starting from that instruction
    • Scenario: trace the full path of a key/flag from loading to comparison; DFG is faster than reading MLIL instruction by instruction and is suited to "where does this value come from / where does it go" questions
    • Use together with x/right-click Show References to cross-validate call boundaries; DFG view Esc to exit and return to the original view
  7. Evidence verification (wrap-up): renames/types/comments are persisted to disk with bv.save() ; cross-check headless export artifacts (function list/HLIL text) against the [[re-triage]] initial survey values; write key conclusions into [[re-analyze/analysis-contract]]—annotations must be able to be reconstructed into a report, don't just leave them in the project

  8. Cross-validation (mutual verification with multiple decompilers): decompile and compare the same function with [[re-ghidra]] / [[re-ida]] (if available)—when the MLIL and Ghidra pseudocode structures match, confidence in the conclusion increases; when they differ greatly, go back to LLIL/assembly to verify, and prefer to trust evidence at the assembly level

  9. Comparison and selection (price / plugin ecosystem):

    • Price: Binary Ninja (commercial paid; personal edition is cheap but limited to personal use) vs [[re-ida]] (most expensive) vs [[re-ghidra]] (free and open source) vs [[re-radare2]] (free and open source CLI)
    • Ecosystem: Ghidra has the largest community and the most mature scripting (Java/Python/headless); IDA's idapython plugins have the longest history; Binary Ninja has fewer plugins but a modern API design (Python-first)
    • Decision: budget-sensitive → Ghidra/radare2; want MLIL readability + Python API → Binary Ninja; already have an IDA license → stay with IDA, and use this skill only when switching stacks

Cross-domain combinations

  • [[re-binary-core]]: decompiler alternative for step 5 of the workflow (RE_DECOMPILER=binaryninja)
  • [[re-ctf]]: decompilation and scripted solving for reverse engineering challenges
  • [[re-malware]]: deep analysis of malicious samples (batch annotation export with Python)
  • [[re-cracking]]: restoration of authorization/verification algorithms
  • [[re-kernel]]: optional tool for driver decompilation (choose one of three with [[re-ghidra]] / [[re-ida]])
  • [[re-android-native]] / [[re-ios]]: MLIL readability is clearly valuable when analyzing mobile native libraries (ARM64 instructions + few symbols)
  • [[re-game]]: optional tool for game binaries (decompilation + scripted batch annotation)
  • There is no direct engineering import/export format between it and [[re-ghidra]] / [[re-ida]]; when switching stacks, manually port function by function or unify annotations with scripts

Common Pitfalls and Traps

  • MLIL Distorts Obfuscated Code: Symptom—junk instructions/control-flow flattening samples decompile into bizarre structures (dead code, always-false conditions); Cause—MLIL is based on data-flow reconstruction, and obfuscation destroys control-flow information; Countermeasure—drop down to LLIL/assembly for cross-validation, or first [[re-deobfuscate]] and then import
  • Script API Version Differences: Symptom—import binaryninja reports a version error/behavior does not match the documentation; Cause—the pip package does not match the major version of the installed software (APIs are incompatible across major versions); Countermeasure—rerun the installation directory install_api.py to align the versions,core_version() compare with the About version
  • Headless Mode Usage: Symptom—Personal edition binaryninja.headless does not exist; Cause—headless automation is a commercial edition capability, trimmed from the personal edition; Countermeasure—in the personal edition, run scripts from the File > Python panel within the GUI, or in scripts only do the load + update_analysis_and_wait alternative path
  • Large binaries are slow/high-memory on first analysis: Symptom—100MB+ samples take minutes to analyze or even hang; Cause—default analysis passes are all enabled; Countermeasure—Options > Analysis turn off unnecessary passes (e.g., keep only the minimal set needed for dataflow and decompile), or analyze only key functions (f.analysis_skip_override approach)
  • Results unchanged after patch (forgot to reanalyze): Symptom—bv.write changed the bytes, but the decompiled output is still the old one; Cause—analysis result cache was not invalidated; Countermeasure—after changing, call bv.update_analysis_and_wait() to force reanalysis, or reload
  • Project database separated from sample:.bndb The project database stores all annotations, and the sample itself is unchanged—when distributing annotations, pass along the .bndb (note version compatibility); do not expect the sample to carry the annotations; writing bytes back to the sample is a different matter (in scripts, bv.write + save according to the official patch procedure)
  • Wide-character/UTF-16 string garbling: Symptom—Strings in the view, Chinese/wide strings display as garbled or split; Cause—incorrect determination of data variable width; Countermeasure—select the data and right-click to set 2-byte width (or in a script bv.get_data_var_at then change the type), then trigger analysis
  • For version differences, plugin/headless details, and shortcut key comparisons, see [[gotchas]] / [[commands]]