For reverse engineers triaging unfamiliar targets

Turn one binary into a short, verifiable worklist.

r2b gives the tools already in your lab one bounded first pass, ranks the addresses and artifacts worth opening, and keeps the evidence portable for a human or harness.

Deterministic by default · no target execution · no model call

samples/triage/bin/shallow-linux-arm64fresh dogfood run

$ r2b brief shallow-linux-arm64 --quick --no-save --json

ELFarm64 / 6418 functions9 imports

Prioritized regions

01

Entry / main

0x004007cc · ARM64

89
02

PLT / imported libc surface

strcpy

86
next argvr2b decompile shallow-linux-arm64 0x4007cc --json

Compiled and rerun from this checkout. Model calls: 0.

INTEGRATED, NOT REBADGED

Keep the native tools and interfaces you trust. r2b detects the stack, dispatches only the requested stages, and records every missing prerequisite.

Identifyfile · strings · readelf · libmagic
Disassembleradare2 · r2pipe · Capstone
Decompile + typesGhidra · DWARF
Firmwarebinwalk · unblob
Symbolic + runtimeangr · Frida · GEF · QEMU

DOGFOOD HOST · ready: file, radare2, Capstone, Ghidra, binwalk, Frida, QEMU · absent: unblob, reported as a skip

THE LOOP

A short path from “what is this?” to “check this next.”

01

Inspect

Wrap proven tools, preserve their output, and report missing analyzers without pretending every laptop is a full lab.

02

Rank

Select a small set of regions, imports, artifacts, and disagreements worth a seasoned practitioner’s attention.

03

Hand off

Emit exact verification, extraction, or single-function decompilation commands. Humans read them; harnesses execute them.

BURDEN OF PROOF

Yes, you already have the real tools.

r2brief is not a new disassembler, emulator, or oracle. It is useful only when orchestration, ranking, and a clean handoff save more time than they cost.

If you are already deep in one target inside r2, stay there.

“Just use file and readelf.”

Do that when identity and headers answer the question. r2b earns its keep when you need the same bounded pass across formats, recorded tool gaps, ranked regions, and a versioned handoff.

format + arch + tool status + r2b.briefing.v1

“Why not open it in r2?”

You should. radare2 is an analysis engine; r2b is a control and handoff layer. It chooses a small surface, keeps the evidence, and sends you back with an exact address—not a replacement UI.

r2b decompile … 0x4007cc --json

“Runtime truth needs QEMU.”

Correct. Static triage cannot prove runtime behavior. The default path avoids executing unknown targets; use emulation or instrumentation once the brief gives you a falsifiable candidate.

dynamic stages are explicit, never implied

“So it is an LLM wrapper.”

No model participates in classification, evidence collection, scoring, or handoff generation. Asking a model is a separate action over the completed brief.

models called: 0

“The score is just another opinion.”

It is a deterministic, testable point table—not a confidence percentage. Every region includes why it ranked and the source snippet. Ignore the order and keep the evidence if you disagree.

score + why + source + address
EARNS ITS KEEP

unfamiliar corpora · batch baselines · uneven lab machines · analyst-to-agent handoffs · replayable evidence

NOT THE POINT

replacing r2 fluency · proving runtime behavior · generating a wall of prose · hiding missing tools

CHECKED OUTPUT

Citations in. Precise argv out.

The useful unit is not a wall of generated prose. It is a finding tied to an address, artifact, and tool observation—with a concrete way to challenge it.

Claims are invitations to verify, not decorations.

{
  "schema_version": "r2b.briefing.v1",
  "subject": {
    "format": "elf",
    "arch": "arm64/64",
    "function_count": 18,
    "dangerous_imports": ["strcpy"]
  },
  "regions": [{
    "rank": 1,
    "title": "Entry / main",
    "address": "0x4007cc",
    "tool": "radare2"
  }],
  "handoff": {
    "schema_version": "r2b.handoff.v1",
    "next_argv": [
      "r2b decompile ... 0x4007cc --json"
    ]
  }
}

FRESH DOGFOOD RUN

A clean handoff across r2 and Ghidra.

The same bundled AArch64 fixture was compiled, briefed, verified, and decompiled on this machine. One evidence chain—not three disconnected screenshots.

01 · BRIEF / RADARE2

Narrow 18 functions to two regions.

ELF · arm64/64 · 18 functions · 9 imports
01  Entry / main @ 0x4007cc       89
02  PLT / strcpy                  86

The bounded pass identified the format, import surface, entry disassembly, and a concrete follow-up without executing the target.

02 · VERIFY / RADARE2

Find the caller. Stop where evidence stops.

{
  "import": "strcpy",
  "status": "dynamic",
  "call_sites": [{
    "function": "check_phrase",
    "address": "0x004007a0",
    "argument": "<unresolved>"
  }]
}

The verifier recovered the AArch64 call site and caller, then refused to invent a value for the first argument.

03 · ONE FUNCTION / GHIDRA

Confirm what the shallow pass could not.

bool check_phrase(char *param_1)
{
  int iVar1;
  char acStack_20[32];

  strcpy(acStack_20,param_1);
  iVar1 = strcmp(acStack_20,training_password);
  return iVar1 == 0;
}

One-function decompilation at 0x40078c confirms a fixed-size destination and dynamic source. Caller ownership is still the next question.

DOGFOOD PAID OFF: this run exposed a missing AArch64 bl/blr verifier path. It is fixed and regression-covered. The fixture stays intentionally shallow so failures are obvious.

NO HIGHLIGHT REEL

One source. Every target this machine could build.

These are the shallow fixture results from the current checkout—not a benchmark and not a hand-selected success case. Missing cross-compilers stay visible.

fixturetargetstatusobserved brief
shallow-hostMach-O · arm64built + checkedlow · __strcpy_chk · 2 regions
shallow-linux-arm64ELF · arm64built + checkedmedium · strcpy · 2 regions
shallow-linux-arm32ELF · arm32skipped herecross-compiler unavailable
shallow-windows-x86_64.exePE · x86_64skipped hereMinGW unavailable

PE and ARM32 parsers also have deterministic format fixtures in the test suite; they are not presented here as locally compiled binaries.

ONE REAL AARCH64 INVESTIGATION

uHTTPD: the scary import was not the lesson.

OpenWrt's small default web server sits in front of LuCI. A hash-pinned 66 KB build was downloaded, checked, and read—never executed or committed.

01 · QUICK BRIEF

Find the boundary

r2b grouped execl with socket acceptance, fork, and dynamic loading, then proposed caller verification. That turns 117 imports into one concrete question: why does a router's HTTP daemon launch a process?

02 · SOURCE REALITY

It is CGI, not a shell

The CGI dispatcher accepts configured interpreter suffixes or a CGI docroot. The child clears its environment, installs CGI variables, changes to the resolved root, and calls execl directly on the interpreter or script. Request data is piped to child stdin.

03 · WHAT FAILED

The handoff came back empty

r2b verify … --import execl returned no verdicts on this stripped, sectionless build. The quick rank found the right subsystem; the current verifier did not recover the call sites. The case records that failure instead of rounding it into a win.

04 · NEXT QUESTION

Review path authority

The useful audit is not “is execl dangerous?” It is whether configuration or a writable filesystem can influence the interpreter map, CGI docroot, resolved script path, or environment crossing into the child.

HONEST STRESS TEST

Large runtimes need scope, not a louder score.

A Homebrew Node build made the failure mode obvious. The launcher looked trivial because the V8 implementation lived in a linked object. The real object completed, but generic import and string signals had the wrong base rate for useful prioritization.

67 KBnode launcher

2.6s · 1 region · mostly a loader

65 MBlibnode.141.dylib

29.9s · 1,930 imports · 6 generic regions

r2b verdictBROAD / UNSCORED

No automatic next argv. Narrow by dependency, crash address, export, subsystem, or version diff. JIT code is absent from the static file and needs runtime capture.

SETUP

Clone once. Pick a surface after.

RECOMMENDED

Repo-local install

Bootstraps uv when needed and installs the selected Python environment into this checkout. No sudo. No system-Python mutation.

git clone https://github.com/sandbornm/r2brief.git
cd r2brief
./scripts/install.sh
AUTO./scripts/install.shsniffs memory + architecture
CORER2B_FLAVOR=core ./scripts/install.shconstrained ARM / Pi
LABR2B_FLAVOR=lab ./scripts/install.shpractitioner workstation
FULLR2B_FLAVOR=full ./scripts/install.shheavy analyzers, capable host

Once that succeeds, choose a mode:

01AFTER INSTALL

Run a brief

CLI

uv run r2b brief ./sample.bin \
  --quick --no-save --json

No extra service. The target is read, not executed, on the default path.

02AFTER INSTALL

Keep evidence in-process

Python

from r2b import analyze

report = analyze("./sample.bin")
for region in report.regions:
    print(region.id, region.evidence)

Run it with uv run python. Typed regions; no implicit record or model call.

03REQUIRES OLLAMA

Optional interpretation

Local model

# Ollama must already be installed and running
ollama pull gemma3:4b

uv run r2b brief ./sample.bin \
  --quick --ask --json

r2b does not install Ollama. Analysis completes before the model receives a scoped brief.

04AFTER INSTALL

Bring your own planner

Agent harness

uv run r2b brief ./sample.bin \
  --quick --no-save --json > brief.json

Point the harness at brief.json. No jq prerequisite; diagnostics stay on stderr.

COMPOSABLE BOUNDARIES

Configure the recipe, the analyzers, or one evidence object.

No framework inheritance maze. TOML bounds a run, a small protocol adds an analyzer, and each ranked region can carry its own evidence into a model call.

TOML

Bound the recipe

[analysis]
enable_ghidra = true
enable_angr = false

[extract]
enable = true
max_files = 80
max_bytes = 33554432

Pick depth and extraction limits without changing the engine.

PYTHON

Drop in an analyzer

class MyAnalyzer:
    name = "my_tool"

    def is_available(self) -> bool: ...
    def quick_scan(self, binary, **ctx): ...
    def deep_scan(self, binary, **ctx): ...

Unavailable prerequisites become explicit skips; adapters keep their native evidence.

PYTHON + OPTIONAL LLM

Ask one evidence object

report = analyze("./sample.bin")
region = report.regions[0]

print(region.evidence)
answer = region.ask(
    "What supports this rank?",
    config_path="config/local.toml",
)

The region already carries source, address, function, snippet, and why. The model receives only that scoped context.

WHERE IT SITS

A normalization boundary, not a meta-disassembler.

A harness can own this loop natively. r2b is the reusable part when several harnesses or analysts should share the same sniffing, tool probes, bounded run policy, evidence shape, and next-command contract.

ANALYSIS ENGINESfile · readelf · r2 · Ghidra · angrKeep using their native depth and UI.
normalized evidence ↓
r2brank · provenance · handoffIDA is a handoff target today, not an integrated adapter.
versioned context ↓
CONSUMERShuman · Python · web · agent harnessOne compact contract; each consumer owns the next decision.
INPUTS

ELF · PE · Mach-O · firmware · embedded containers · raw blobs

ANALYSIS

radare2 · libmagic · Capstone · Ghidra · binwalk · unblob · angr · DWARF · Frida · GEF

OPTIONAL MODELS

OpenAI · Anthropic · xAI · Kimi · GLM · Ollama · exo

FAQ

Reasonable questions. Suspiciously direct answers.

Does r2b execute the binary?+

Not in the default briefing path. Dynamic stages are explicit, optional, and belong in an isolation boundary appropriate to the target.

Is an LLM required?+

No. Classification, evidence collection, ranking, records, and handoffs are deterministic. A model is an optional interpreter of a completed brief.

Can a model call analysis tools?+

Yes—only when the host declares the tools, supplies the executor, allowlists each tool, and sets a bounded round limit. Declaring a tool alone never executes it.

Do I need MCP?+

No. The CLI and Python API already provide stable, typed harness boundaries. Add a thin MCP adapter only when your host benefits from discovery or remote invocation; it is not a core dependency.

Is this easy to modify?+

That is the point. Adapters share a small protocol, providers declare capabilities separately from transports, and JSON schemas make the handoff surface inspectable.

What about V8-sized runtimes?+

Treat the first pass as inventory. r2b now marks large/high-cardinality code targets broad and unscored, suppresses automatic next argv, and asks for a dependency, crash address, export, subsystem, or version diff. JIT-generated code requires runtime capture.

START SMALL

Give your next analyst fewer guesses.

Start with a deterministic brief. Add deeper tools or a model only when the evidence earns the cost.