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.

Evidence and trust boundariesSame sample hashr2 / BN viewsCompeting claimsCross-tool proofPropose. Enforce. Verify.
Different representations are corroborating evidence, not independent proof unless their assumptions are checked.

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.

ToolCategoryUseful role
r2airadare2 AI pluginExplain command output and propose semantic names
decaiAI-assisted decompilation pluginCompare a recovered representation with instructions
radare2 MCP / agent projectsBridges and orchestrationExternal clients; exact capability requires verification
Binary Ninja SidekickVendor-integrated AI analysis productSearch, recovery, evidence-linked findings, analysis automation
Binary Ninja community MCP bridgesThird-party API adaptersBounded 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...]> q

Symbol 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

ClaimCheckCommon model error
Length is little-endianByte loads, shift by eight, ORInferring network byte order from a parser name
Capacity is enforcedComparison and both successor blocksReading one branch without its destination
No copy occurs hereInstruction/call inventoryInventing memcpy from the function's purpose
Return -2 means rejectionImmediate value and caller useDisplaying unsigned representation as a huge success value
Caller is safeSeparate caller analysis and runtime testAssuming 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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 references

9. Troubleshooting, Retesting, and Completion Evidence

SymptomLikely causeDiscriminating check
Tools disagree about function boundariesDifferent analysis heuristics or architecture settingsInspect bytes, entry references, and actual control flow
A plugin exports too much dataUnbounded function graph or whole-image contextRestrict one sample/function and explicit output limits
Agreement is treated as proofBoth views share an unverified type assumptionUse 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.

ToolRoleImportant boundary
rabin2Read executable identity, imports, and metadataMetadata and sparse imports are leads, not proof of behaviour
radare2Export function metadata/disassembly for one known binaryGeneric command access can still reach shell and mutation capabilities
Binary Ninja API / SidekickInspect bounded IL and review evidence-linked proposalsUse the installed API/licence and review any database or shell action separately

Step-by-Step Tool Operation

  1. Record the sample hash and radare2/Binary Ninja versions. Confirm the file matches the earlier build rather than another sample with the same basename.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.txt
from 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.