Agent-friendly Binary Ninja CLI with a GUI companion bridge
Project description
bn
bn is a coding agent-first CLI for Binary Ninja. It gives a shell session or tool-calling agent stable commands, structured output, and access to the same live Binary Ninja database you already have open in the GUI.
See the changelog for release notes by version and date.
Headline Features
- Query live Binary Ninja state from the shell: targets, functions, callsites, decompile text, IL, disassembly, xrefs, types, strings, imports, and reusable bundles.
- Execute unrestricted Python inside the Binary Ninja process, with stable helpers for common reads and symbol/function resolution.
- Apply mutations with
--preview, capture decompile diffs, and verify the live post-state before reporting success. - Emit structured
jsonorndjsonoutput, auto-spill large results to files, and return token counts so agents can budget context intelligently.
Install
Recommended setup: install the CLI, the Binary Ninja companion plugin, and the bundled Codex skill.
Install the CLI on your PATH:
uv tool install bn-cli
Install the Binary Ninja companion plugin:
bn plugin install
That links the packaged bn_agent_bridge companion into your Binary Ninja plugins directory.
Install the bundled Codex skill:
bn skill install
That symlinks the packaged bn skill into $CODEX_HOME/skills/bn by default. Use --mode copy if you want a standalone copy instead. Restart Codex to pick up a new or renamed skill.
Upgrade later with uv tool upgrade bn-cli.
Use BN Agent Bridge\\Restart Bridge from Binary Ninja's command palette to restart the socket bridge. After plugin code or version changes, reload Python plugins or restart Binary Ninja so Python imports the new module code.
How It Works
bnhas two parts:- a normal Python CLI that you can run from your shell or agent tool harness
- a Binary Ninja GUI plugin that owns all Binary Ninja API access
- The plugin creates one fixed bridge socket and one fixed registry file.
- The CLI discovers that bridge, connects to it, and forwards commands into the live GUI session.
- Because the Binary Ninja side runs as a GUI plugin, it works with a personal license and does not require a commercial headless license.
Quick Start
Open a binary or .bndb in Binary Ninja, then run:
bn doctor
bn target list
bn refresh
bn function list
bn decompile sub_401000
If exactly one BinaryView is open, target-specific commands can omit --target entirely. If multiple targets are open, pass --target <selector> from bn target list.
Target Selection
Use bn target list to see available targets:
bn target list
Targets can be selected with:
- the
selectorfield frombn target list - the full
target_id - the BinaryView basename
- the full filename
- the view id
activewhen you explicitly want the GUI-selected target
In normal use, prefer the selector field. For a single open database, this is usually just the .bndb basename:
bn decompile update_player_movement_flags --target SnailMail_unwrapped.exe.bndb
--target may appear anywhere in the command, including before the top-level command, and BN_TARGET provides a shell-wide default:
bn --target SnailMail_unwrapped.exe.bndb decompile update_player_movement_flags
bn function --target SnailMail_unwrapped.exe.bndb search attachment
export BN_TARGET=SnailMail_unwrapped.exe.bndb
Omitting --target only works when exactly one target is open. If multiple targets are open, the CLI rejects the command instead of silently falling back to active.
Output Behavior
Every command supports:
--format json--format text--format ndjson--out <path>--match <regex>with--beforeand--aftertext context--no-spillto stream a complete result to stdout
Interactive read commands default to text. Mutation, setup, and export commands default to json.
Add --format json when you need stable fields for automation or piping into structured tooling.
Examples:
bn function list --format ndjson
bn function list --min-address 0x401000 --max-address 0x40ffff
bn function search --regex 'attach|detach'
bn decompile sample_track_floor_height_at_position --out /tmp/floor.json
If --out is set, the command writes the rendered result to that path and prints a compact JSON envelope with the artifact path, byte size, token count, tokenizer, hash, and summary. Agents can use that envelope to decide whether to read the full artifact, keep a summary, or defer loading it into context.
Use --match when an agent only needs a few lines from otherwise large text output. Use --no-spill when a downstream process must consume stdout directly and is prepared for the full result.
bn function list and bn function search return the full matching set for the selected target or address range. Large results auto-spill to an artifact instead of forcing manual pagination. Spill is token-based and currently triggers above 10,000 tokens. When that happens, stdout stays empty and stderr carries the spill metadata as plain text, including the artifact path and size counts.
Extraction Commands
Common read-only commands:
bn target list
bn target info
bn function list
bn function list --min-address 0x401000 --max-address 0x40ffff
bn function search attachment
bn function search --regex 'attach|detach|follow'
bn function info end_track_attachment_follow_state
bn function containing 0x401234
bn callsites crt_rand --within bonus_pick_random_type
bn callsites crt_rand
bn callsites crt_rand --within-file /tmp/rng-functions.txt --format ndjson
bn proto get end_track_attachment_follow_state
bn local list end_track_attachment_follow_state
bn refresh
bn decompile end_track_attachment_follow_state
bn il end_track_attachment_follow_state
bn disasm end_track_attachment_follow_state
bn disasm 0x401234 --before 5 --after 10
bn address info global_player+0x308
bn data read global_player+0x308 --type u32 --count 4
bn xrefs end_track_attachment_follow_state
bn xrefs global_player+0x308
bn refs end_track_attachment_follow_state
bn xrefs field TrackRowCell.tile_type
bn comment get --address 0x401000
bn types --query Player
bn types show Player
bn struct show Player
bn types declare --file /path/to/win32_min.h --preview
bn strings --query follow
bn imports
bn search text 'crt_rand' --view hlil --max-results 50
bn search constant 0x370 --max-results 50
bn schema --format json --out /tmp/bn-schema.json
bn function search stays case-insensitive substring matching by default. Add --regex when you need regular expressions. bn function list and bn function search both accept --min-address and --max-address to filter by function start address.
bn callsites is the direct-call lane for exact return-address recovery. It reports both the native call_addr and the post-call caller_static, where caller_static = call_addr + instruction_length. Without a scope it derives caller functions from Binary Ninja's inbound code-reference index. Use --within <function> or --within-file <path> to narrow the work; the file format is one function identifier per non-empty line, with # comments ignored.
Read commands that take a function also accept an address inside that function. Address-bearing commands resolve numeric addresses, function names, data/import symbols, and symbol+offset expressions.
Whole-database searches stop after five seconds by default and return partial-result metadata when the time or result limit is reached. Raise the budget explicitly with --timeout <seconds> when a slower IL view is intentional.
Each callsite row also includes:
call_index: zero-based ordinal for matching callsites in the containing function, ordered bycall_addrwithin_query: the original unresolved scope token from--withinor--within-filehlil_statement: the smallest recoverable HLIL expression or statement containing the call, ornullwhen Binary Ninja only exposes a coarse enclosing regionpre_branch_condition: the nearest enclosing pre-call HLIL condition when it can be recovered confidently, otherwisenull
hlil_statement is intentionally local-or-null. If the best available HLIL mapping expands to a broad whole-function or multi-statement blob, bn callsites suppresses it instead of returning noisy context.
Bundles And Python
bn decompile is the HLIL-text convenience lane. It is useful for quick function reading, but typed layouts remain authoritative in bn types show and bn struct show.
Export a reusable function bundle:
bn bundle function end_track_attachment_follow_state --out /tmp/end_track_attachment_follow_state.json
Run Python inside the Binary Ninja process for one-off inspection and BN-native scripting:
bn py exec --code "print(hex(bv.entry_point)); result = {'functions': len(list(bv.functions))}"
bn py exec --stdin <<'PY'
print(hex(bv.entry_point))
result = {"functions": len(list(bv.functions))}
PY
Use --stdin or --script for multiline Python snippets. Use --code for true one-liners only.
bn py exec --stdin <<'PY'
out = []
for f in bv.functions:
if 0x416000 <= f.start < 0x41C000:
out.append((f.start, f.symbol.short_name))
out.sort()
print("\n".join(f"{addr:#x} {name}" for addr, name in out))
PY
Use a quoted heredoc for multiline Python snippets.
When you need counts from BN iterators such as f.hlil.instructions, materialize them explicitly with list(...) or consume them with sum(1 for ...) instead of assuming sequence semantics.
The py exec environment includes:
bnbinaryninjabvcurrent_view(an alias forbv)address,function, andfunctions_containingread_u8,read_u16,read_u32,read_u64read_i8,read_i16,read_i32,read_i64read_ptr,read_f32,read_f64, andread_cstrresult
These small functions cover common API-shape mistakes without introducing a wrapper API or restricting the full Binary Ninja module or view object. Stdout and result are both returned. If result is not JSON-serializable, bn returns repr(result) and includes a warning instead of silently stringifying the whole response. Python failures include the in-process traceback and any stdout produced before the exception.
Agent Protocol
bn schema --format json emits the protocol version plus the complete global and per-command argument surface without contacting Binary Ninja. bn doctor --format json reports the live plugin's supported operations and feature flags. Bridge failures use structured error codes and details in JSON/NDJSON mode, while text mode includes concise suggestions.
Mutation Commands
Mutations follow the same target-selection rules as other target-specific commands.
Examples:
bn symbol rename sub_401000 player_update --preview
bn comment set --address 0x401000 "interesting branch" --preview
bn comment get --address 0x401000
bn proto get sub_401000
bn proto set sub_401000 "int __cdecl player_update(Player* self)" --preview
bn local list sub_401000
bn local rename sub_401000 0x401000:local:StackVariableSourceType:-20:2:12345 speed --preview
bn local retype sub_401000 0x401000:local:StackVariableSourceType:-20:2:12345 float --preview
bn types declare "typedef struct Player { int hp; } Player;" --preview
bn struct field set Player 0x308 movement_flag_selector uint32_t --preview
Preview mode applies the change, refreshes analysis, captures affected decompile diffs, and then reverts the mutation.
Non-preview writes only report success after reading the live BN session back and verifying that the requested post-state actually landed. If verification fails, the CLI returns a nonzero exit code and reverts the mutation.
After any live type or prototype mutation, do an explicit readback:
bn proto get sub_401000
bn struct show Player
bn types show Player
bn decompile sub_401000
For declaration and struct mutations, preview results also include affected_types with before/after layouts and a unified diff. If a field edit is already identical, the result is marked with changed: false and a No effective change detected message.
For the first few changed functions, affected_functions also includes short before_excerpt and after_excerpt snippets around the first changed HLIL lines.
Mutation results now distinguish:
verifiednoopunsupportedverification_failed
When verification fails, JSON output also includes requested and observed state for the failed op.
bn types declare now uses Binary Ninja's source parser when available. When you pass --file, the CLI also forwards the source path so relative includes resolve the same way they would during header import in the GUI.
If a declaration only parses functions or extern variables and introduces no named types to persist, types declare returns a verified no-op instead of failing with No named types found in declaration.
bn local list and bn function info return stable local_id values for parameters and locals. Prefer those IDs for bn local rename and bn local retype; legacy name-based targeting still works for compatibility.
Troubleshooting
Check bridge state:
bn doctor
If bn target list is empty:
- make sure Binary Ninja is open
- make sure a binary or
.bndbis open - make sure the plugin is installed with
bn plugin install - reload Binary Ninja plugins or restart Binary Ninja after plugin changes
If bn doctor sees a bridge registry but reports Operation not permitted under Codex,
the Codex sandbox is blocking the Unix socket that connects to the live Binary Ninja GUI
process. Let Codex run bn outside the sandbox by adding this rule to
~/.codex/rules/default.rules:
prefix_rule(pattern=["bn"], decision="allow")
Restart Codex or reload rules after editing the file. This is only needed for Codex
sandboxed runs; normal shells can use bn without that rule.
If multiple targets are open, omitted --target is rejected. Pass --target <selector> from bn target list, or use --target active only when you intentionally want the GUI-selected target.
If decompile text still looks stale after a type change, run:
bn refresh
That forces an analysis refresh, but it still may not fully eliminate Binary Ninja's stale __offset(...) presentation in every case.
Development
Run tests with:
uv run pytest
Run the CLI from the repo without installing it globally:
uv run bn --help
Install an editable development build as a uv tool with:
uv tool install -e .
Verify that package, plugin, and lockfile versions agree:
uv run python scripts/release.py --check
For a release, update every version field with one command before editing the changelog and creating the tag:
uv run python scripts/release.py 0.14.0
Pushing the matching v0.14.0 tag runs the publish workflow, which tests, builds, publishes the distributions to PyPI through Trusted Publishing, and creates the GitHub release from the same artifacts.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bn_cli-0.14.0.tar.gz.
File metadata
- Download URL: bn_cli-0.14.0.tar.gz
- Upload date:
- Size: 54.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ed8deedf4754e1ccd674dd5a92ecaad0b5a69e6ab06b148b8724238d653ea11
|
|
| MD5 |
1fc73af8e56c32cf1fa82f638f6b8916
|
|
| BLAKE2b-256 |
03b9d6a87cb5c14e4fa086b2037a6970fc0c18eba636d7a991959092fe91180d
|
Provenance
The following attestation bundles were made for bn_cli-0.14.0.tar.gz:
Publisher:
publish.yml on banteg/bn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bn_cli-0.14.0.tar.gz -
Subject digest:
1ed8deedf4754e1ccd674dd5a92ecaad0b5a69e6ab06b148b8724238d653ea11 - Sigstore transparency entry: 2165227202
- Sigstore integration time:
-
Permalink:
banteg/bn@62635f11feec3d33fff9d0cab1d1f63e37cd41c4 -
Branch / Tag:
refs/tags/v0.14.0 - Owner: https://github.com/banteg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@62635f11feec3d33fff9d0cab1d1f63e37cd41c4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file bn_cli-0.14.0-py3-none-any.whl.
File metadata
- Download URL: bn_cli-0.14.0-py3-none-any.whl
- Upload date:
- Size: 61.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1492f600f7b1120ee1f4e1b49bbc1cc841501fae081415936d8da359ccb30b06
|
|
| MD5 |
58005fabe0d3f72dae5f353b2b17e2ab
|
|
| BLAKE2b-256 |
71076b734722329c89dfea719fbc50cabde4f2dbbdf2ba50960828b0bc94ff61
|
Provenance
The following attestation bundles were made for bn_cli-0.14.0-py3-none-any.whl:
Publisher:
publish.yml on banteg/bn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bn_cli-0.14.0-py3-none-any.whl -
Subject digest:
1492f600f7b1120ee1f4e1b49bbc1cc841501fae081415936d8da359ccb30b06 - Sigstore transparency entry: 2165227222
- Sigstore integration time:
-
Permalink:
banteg/bn@62635f11feec3d33fff9d0cab1d1f63e37cd41c4 -
Branch / Tag:
refs/tags/v0.14.0 - Owner: https://github.com/banteg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@62635f11feec3d33fff9d0cab1d1f63e37cd41c4 -
Trigger Event:
push
-
Statement type: