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 the
RE_DECOMPILER=binaryninjascenario - Don't use: already have a Ghidra/IDA workflow and no motivation to switch (defaults to
RE_DECOMPILERsession 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, the
Functionspanel has a function list;Options > Aboutshows the license type - Linux without a graphical environment: the GUI version needs an X display (
xvfb-run ./binaryninjacan 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
pluginmanageris 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 checkcore_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 binaryninjawill 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-headlessexecutable — the proper way is to register viainstall_api.pyand then write scripts directlybinaryninja.load()(see [[commands]] sequence 2); the headless license uses the official standalone download package (no GUI, only the headless license can be downloaded)
Procedure
-
Import and Analysis:
File > OpenSelect sample → automatic analysis (progress bar at the bottom) → on the leftFunctions/Data/Referencesview to confirm the analysis results- Confirm: the entry function (entry) is recognized, the import table (
Importsview) has content, strings (Stringsview) are visible - Analysis anomalies (few functions/garbled strings) → may be packed; first [[re-packer-id]] / [[re-anti-analysis]]
- Analysis options:
Options > Analysischeck/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
-
MLIL Reading (IL View Switching):
- Double-click a function to enter the decompilation view (HLIL High Level IL by default);
icycle between disassembly/LLIL/MLIL/HLIL, or use theOptionsmenu 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 presentsif (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:
mkey in Binary Ninja is "display integer as applied enum", not MLIL switching——to switch IL usei
- Double-click a function to enter the decompilation view (HLIL High Level IL by default);
-
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:
Typescreate a new Structure in the panel → apply to a variable/pointer → field names propagate with the type into the decompilation view - Other type shortcuts:
oset pointer reference,aset character array,dcycle integer width,*create array (see the [[commands]] shortcut table)
- Rename: with the cursor on a symbol/variable,
-
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"thenbv.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 Pluginsare 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 > Pythontest-run in the panel (you can seeprintoutput 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-headlessCLI; the proper way isinstall_api.pyafter 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 GUIFile > Pythonpanel - 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_infotraces
- Batch rename:
-
String/reference location chain:
Stringsview to find the hint string → double-click to enter the disassembly → right-clickShow References(orxrefspanel) → 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)
-
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-clickShow Referencesto cross-validate call boundaries; DFG viewEscto exit and return to the original view
- In the MLIL/HLIL view, right-click an instruction →
-
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 -
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
-
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 binaryninjareports 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 directoryinstall_api.pyto align the versions,core_version()compare with the About version - Headless Mode Usage: Symptom—Personal edition
binaryninja.headlessdoes not exist; Cause—headless automation is a commercial edition capability, trimmed from the personal edition; Countermeasure—in the personal edition, run scripts from theFile > Pythonpanel within the GUI, or in scripts only do theload+update_analysis_and_waitalternative 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 > Analysisturn off unnecessary passes (e.g., keep only the minimal set needed for dataflow and decompile), or analyze only key functions (f.analysis_skip_overrideapproach) - Results unchanged after patch (forgot to reanalyze): Symptom—
bv.writechanged the bytes, but the decompiled output is still the old one; Cause—analysis result cache was not invalidated; Countermeasure—after changing, callbv.update_analysis_and_wait()to force reanalysis, or reload - Project database separated from sample:
.bndbThe 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—
Stringsin 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 scriptbv.get_data_var_atthen change the type), then trigger analysis - For version differences, plugin/headless details, and shortcut key comparisons, see [[gotchas]] / [[commands]]