AI Pentesting, Part 13: radare2 and Binary Ninja: Compare AI Analysis Across Representations
Two decompilers agreeing is useful, but agreement can reflect the same wrong type assumption. Use radare2 and Binary Ninja to compare bytes, control flow, intermediate languages, and runtime evidence. Let AI organise the comparison while the analyst verifies the boundaries.
Lab boundary: written authorisation, synthetic identities and data, isolated targets, bounded requests, and human approval for active actions. Examples are instructional fixtures, not claims of testing a live organisation. Tool inventory reviewed 2026-10-28; compatibility and availability can change.
1. Distinguish plugins, agents, and MCP bridges
RADARE in this series refers to the radare2 ecosystem. The reviewed radareorg/r2ai repository contains r2ai, a native AI plugin, and decai, a plugin focused on decompilation. It documents local/remote providers, scriptability, prompt customisation, retrieval, and an automatic ReAct-style mode. These modes have different risk profiles: explaining supplied output is narrower than allowing an agent to issue arbitrary radare2 commands.
The r2ai README also points to MCP and agent projects, including r2mcp, r2copilot, and r2agent. The linked radare2 MCP repository could not be retrieved during this review, so this article does not claim its current installation path, support status, or exact tool schema. Verify those directly before deployment. An upstream reference is not proof that a package remains available or maintained.
| Tool | Category | Useful role |
|---|---|---|
| r2ai | radare2 AI plugin | Explain command output and propose semantic names |
| decai | AI-assisted decompilation plugin | Compare a recovered representation with instructions |
| radare2 MCP / agent projects | Bridges and orchestration | External clients; exact capability requires verification |
| Binary Ninja Sidekick | Vendor-integrated AI analysis product | Search, recovery, evidence-linked findings, analysis automation |
| Binary Ninja community MCP bridges | Third-party API adapters | Bounded function/IL export after source and version review |
2. How to inspect the shared fixture with radare2
Use the exact Part 11 binary. Open it for analysis without write mode. Auto-analysis itself parses untrusted data, so keep the tool patched and use an isolated workspace. The following commands inspect functions, instructions, and strings; they do not launch the binary.
r2 -A ./lab_parser
[0x...]> afl
[0x...]> s sym.parse_record
[0x...]> pdf
[0x...]> afij
[0x...]> izzj
[0x...]> qSymbol names differ if the binary is stripped or uses a different object format. If sym.parse_record is absent, locate the caller and inspect its branch target rather than guessing an address. afij and izzj provide structured JSON useful for a bounded adapter.
With a reviewed r2ai installation, use the installed help and prompt inventory to select explanation-only behaviour. The documented prompt families include function explanation, variable naming, signatures, and decompilation. Do not enable automatic command execution on an unknown sample. A generic radare command tool can reach shell and write capabilities; restrict exact operations, not just command prefixes.
3. Inspect IL and use Sidekick with review
Binary Ninja exposes disassembly and multiple intermediate-language levels. LLIL stays closer to machine operations; MLIL adds recovered variables and data-flow structure; HLIL aims for a higher-level representation. The same address can appear differently across these views. Use lower-level evidence to check a high-level claim about signedness, pointer width, or a guard condition.
The reviewed Sidekick product materials describe semantic search, name/type/structure recovery, evidence-linked findings and notebooks, validation states, reversible analysis transactions, and a Python automation API. They also describe approved shell commands and managed or on-premises deployment. Those are vendor-described capabilities, not a guarantee of correct analysis on every binary. Keep shell approval and database edits under operator control.
import binaryninja
with binaryninja.load("lab_parser") as view:
for function in view.functions:
if "parse_record" in function.name:
print(hex(function.start), function.name)
for instruction in function.high_level_il:
print(instruction)This example assumes a compatible licensed Binary Ninja Python environment and an unstripped fixture. It reads analysis and prints IL; it does not call a Sidekick-specific API or modify the database. Verify the installed API and licensing before headless use.
4. Design a portable read-only RE adapter
For community Binary Ninja MCP bridges or a local radare adapter, start with file metadata, bounded function listing, one-function disassembly/IL, and cross-reference queries. Pin one sample and one analysis database. Disable arbitrary Python, generic command execution, patching, database writes, and debugging until separately reviewed. Do not infer MCP support merely because a plugin supports AI.
{
"sample_id": "lab-parser-build-001",
"operation": "read_function_evidence",
"function_id": "parse_record",
"views": ["disassembly", "high_level_il"],
"max_instructions": 120,
"write_access": false,
"include": ["addresses", "types", "analysis_warnings"]
}This is an illustrative cross-tool contract. Map it to the real API rather than assuming every bridge accepts these keys. Preserve tool-specific warnings and unresolved types instead of normalising them away. Bound recursion, call-graph depth, result count, and output bytes so a single request cannot dump a proprietary firmware image into model context.
5. Cross-check five concrete claims
| Claim | Check | Common model error |
|---|---|---|
| Length is little-endian | Byte loads, shift by eight, OR | Inferring network byte order from a parser name |
| Capacity is enforced | Comparison and both successor blocks | Reading one branch without its destination |
| No copy occurs here | Instruction/call inventory | Inventing memcpy from the function's purpose |
| Return -2 means rejection | Immediate value and caller use | Displaying unsigned representation as a huge success value |
| Caller is safe | Separate caller analysis and runtime test | Assuming validation automatically makes later code safe |
Ask the assistant to produce one supported interpretation and one plausible alternative for each claim. Resolve the difference with bytes, IL, or Part 12's runtime trace. Do not use majority voting between models as a replacement for evidence.
Renaming a variable from var_10 to validated_length can prematurely imply safety. Prefer a neutral provisional name such as decoded_length until the caller's use is established. Review every inferred structure offset against actual memory accesses.
6. Preserve reversible analysis and reproducibility
Snapshot the database before AI-assisted changes. Group related type and name proposals, inspect their effect on decompilation, and revert a change if it hides an unresolved operation. Sidekick's described transaction workflow can help, but analysts still need to understand which edits were applied and whether later work depends on them.
Export a cross-tool comparison with sample hash, image base, tool versions, instruction addresses, IL excerpts, observed runtime values, confirmed semantics, and unresolved questions. Keep model-generated labels distinct from original symbols. For proprietary binaries, review provider retention, on-premises options, local logs, and plugin telemetry before processing.
RE series outcome: Ghidra/IDA establish a static hypothesis; LLDB supplies bounded runtime state; radare2/Binary Ninja provide alternate representations and corroboration. No tool or assistant alone proves exploitability. Part 14 lifts these patterns into end-to-end automation architectures.
7. Step-by-Step Lab: Resolve a Cross-Tool Disagreement
Prerequisites: The same hashed parser from Part 11, isolated radare2 and Binary Ninja projects, and access to the relevant supported APIs/licences. AI plugins and community MCP bridges are optional; the comparison can be performed manually without granting command or database-write authority.
- Open the known fixture without write mode in radare2. Use function listing, seek, disassembly, and structured function metadata commands already shown. Preserve the tool version, image base, and function identity.
- Load the identical binary in Binary Ninja and inspect the function through disassembly and the available IL levels. Record which view supports each observation and which types remain recovered assumptions.
- Select five narrow claims: byte order, minimum header, capacity check, return value, and presence or absence of a copy in this function. Do not ask for a broad vulnerability verdict before establishing these facts.
- Ask AI to summarise each tool's evidence independently and cite addresses. Compare the summaries using the offline consensus fixture below, which requires evidence support rather than a majority vote.
- When a claim conflicts, revisit the actual instructions, IL guards, or Part 12 runtime trace. Resolve the specific disagreement before applying names or types that could bias the next pass.
- Apply one reviewed reversible analysis change, if needed, and inspect its effect on all relevant views. Preserve original databases and note whether a change propagates types into callers.
- Export a concise comparison packet with supported claims, rejected claims, and unresolved questions. Bound any MCP export by sample, function, view, call-graph depth, and output size.
const claims = [
{ tool: "view-A", key: "byte_order", value: "little", supported: true },
{ tool: "view-B", key: "byte_order", value: "little", supported: true },
{ tool: "view-A", key: "copies_data", value: false, supported: true },
{ tool: "model-note", key: "copies_data", value: true, supported: false }
];
function evidenceConsensus(key) {
const values = claims.filter(claim => claim.key === key && claim.supported).map(claim => claim.value);
return values.length && values.every(value => value === values[0]) ? values[0] : "unresolved";
}
if (evidenceConsensus("byte_order") !== "little" || evidenceConsensus("copies_data") !== false) throw Error("evidence mismatch");
console.log(JSON.stringify({ byteOrder: evidenceConsensus("byte_order"), copiesData: evidenceConsensus("copies_data") }));Expected result: The supported fixture claims agree on little-endian decoding and no copy. The supported flag is a manually supplied review label here, not automatic proof. In a real workflow, the analyst must inspect the cited evidence before granting it that status.
8. Worked Examples and Expected Outcomes
Example A: HLIL hides a conversion
A high-level view simplifies a cast that matters to signedness. Check lower-level IL and machine instructions before declaring an arithmetic flaw. Preserve which representation justified the final statement.
Example B: command access defeats a read-only wrapper
A radare adapter accepts arbitrary command strings even though its intended role is function explanation. Replace it with narrow reviewed operations or deny access; do not rely on a prompt asking the model not to use shell or write features.
Example C: an AI rename changes later interpretation
A provisional variable name implies validation that the caller does not perform. Revert or neutralise it and re-examine dependent annotations. Track original symbols separately from model-suggested labels.
Comparison packet:
same sample hash; correct base and relative locations
view type and tool revision for each excerpt
observed instruction facts versus recovered assumptions
runtime corroboration where required
reviewed changes and rollback references9. Troubleshooting, Retesting, and Completion Evidence
| Symptom | Likely cause | Discriminating check |
|---|---|---|
| Tools disagree about function boundaries | Different analysis heuristics or architecture settings | Inspect bytes, entry references, and actual control flow |
| A plugin exports too much data | Unbounded function graph or whole-image context | Restrict one sample/function and explicit output limits |
| Agreement is treated as proof | Both views share an unverified type assumption | Use lower-level or independent runtime evidence |
Retest and completion: Retain the original and revised databases, bounded exports, address-cited claims, and resolved disagreements. Test a stripped and an optimised build only as separate identified samples. Do not transfer a conclusion by filename alone or claim that two AI summaries constitute independent verification.
10. Real Tool Workflow: Export radare2 JSON and a Bounded Binary Ninja IL View
Prerequisites: Patched radare2 tools, a compatible licensed Binary Ninja Python environment, and the same known parser used in Parts 11–12. Review startup configuration/plugins and keep analysis isolated. The commands inspect static files; debugger or write modes are not enabled.
Use reviewed releases and record the versions actually installed. Command references for this workflow were reviewed on 29 October 2026; product support and flags can change. Instructions and output shapes are not claims of a live assessment.
| Tool | Role | Important boundary |
|---|---|---|
| rabin2 | Read executable identity, imports, and metadata | Metadata and sparse imports are leads, not proof of behaviour |
| radare2 | Export function metadata/disassembly for one known binary | Generic command access can still reach shell and mutation capabilities |
| Binary Ninja API / Sidekick | Inspect bounded IL and review evidence-linked proposals | Use the installed API/licence and review any database or shell action separately |
Step-by-Step Tool Operation
- Record the sample hash and radare2/Binary Ninja versions. Confirm the file matches the earlier build rather than another sample with the same basename.
- Inspect executable metadata with rabin2, then export a function list using radare2's JSON view. Review function identity and architecture before selecting the parser symbol.
- Export only the parser disassembly from the known unstripped fixture. If the symbol is absent, locate the actual function through caller evidence and record its address rather than forcing the example symbol to resolve.
- Run the bounded Binary Ninja script in its approved Python environment against the corresponding sample. Preserve the view type, function start, and displayed IL. Do not install an unrelated Python package just because import binaryninja fails.
- Compare byte-order, capacity, return, and copy-operation claims across disassembly and IL. Give AI only the excerpts required for that comparison, with exact sample and address context.
- If using Sidekick or another AI bridge, keep generated names/types as reviewed proposals and record reversible changes. Close sessions, retain the original database, and clean up disposable exports according to the agreed retention policy.
r2 -v
rabin2 -I ./lab_parser
r2 -A -q -c 'aflj' ./lab_parser > functions.json
r2 -A -q -c 'pdf @ sym.parse_record' ./lab_parser > parser-disassembly.txtfrom itertools import islice
import binaryninja
with binaryninja.load("lab_parser") as view:
for function in view.functions:
if "parse_record" in function.name:
print(hex(function.start), function.name)
for instruction in islice(function.high_level_il, 40):
print(instruction)Expected Evidence and Interpretation
The shell commands assume the POSIX-style fixture filename; adjust the actual path and format for your compiler output. functions.json should contain structured function records if analysis succeeds. No fixed address list is promised. The Python listing caps the selected function's printed IL to forty instructions, suitable for a small review packet rather than whole-image model context.
When a high-level view hides a conversion or simplifies a guard, inspect lower-level IL and machine code. A second assistant agreeing with the first does not independently validate the claim. Sidekick's described evidence and transaction features can help organise review, but do not replace sample-specific proof or authorisation.
Troubleshooting and Cleanup
If the JSON export contains diagnostics or is empty, inspect tool output and exit status before parsing it as evidence. If the architecture or symbol name differs, correct the analysis context rather than patching the sample. Avoid -w and -d for this static exercise. Preserve original symbols separately from proposed names and do not expose arbitrary radare or Python commands to a nominally read-only model.