Matching SHA-256 values removed programs such as uhttpd from this release question.
Firmware triage with a saved evidence trail
Find the binaries worth opening.
The working loop is a release delta plus a named-import verify. JSON stays on stdout. Analysis never calls a model.
Official AArch64 firmware · --quick · no model calls · target not run
106 unchanged / 3 changed
ubusd→3 strcpy callers→len guardbusybox and odhcpd also changed; they were not followed. Ground truth: OpenWrt advisory and upstream patch
RECORDED RUN · OPENWRT AARCH64
Two releases. One trail you can inspect.
Select a node to see the saved fact, why it changed the scope, and the command or source that supports it. The graph starts with the firmware pair, not a hand-picked crackme.
Changed common userland ELFs
Path and SHA-256 comparison leaves three common userland binaries. This narrows the work; it is not a vulnerability verdict.
bin/busybox · sbin/ubusd · usr/sbin/odhcpdCounts compare regular ELF files at paths present in both root filesystems. Versioned kernel-module and library paths remain outside the 109-path comparison. Default brief --extract is 64 MiB / 200 files / depth 2; the 176-ELF inventory used sandboxed unsquashfs with a higher file cap. Device nodes still need root.
WHAT THE PASS ADDED
A shorter list, with the evidence behind it.
The published advisory tells us the answer. This run tests whether the binary workflow gets to the same neighborhood without pretending an import name is a finding.
strcpy call sites became concrete leads.The import was only a pivot. verify supplied caller addresses for a short decompilation pass.
24.10.3 reads pattern[len - 1] first. 24.10.4 checks the length first. Upstream source confirms the function and intent.
strcpy callers under bubblewrap. Device nodes still need root. The 176-ELF inventory used sandboxed unsquashfs with a higher file cap than default brief --extract (64 MiB / 200 files / depth 2).DEMO · PLANNERS ON ONE BRIEFING
Empty next_argv. Four planners still ran r2b verify.
Same 24.10.3 ubusd card. Rules review kept network ahead of the strcpy capsule. Four planners were told: only uv run r2b, no raw r2. All four hit the three gold callers. Kimi and GLM had no agent CLI; they got one tool that can only exec r2b.
next_argv was empty. strcpy was still in the briefing.Process-launch auto-queue does not fire. The memory capsule and dangerous_imports do. That is the demo, not a missing feature.
verify --import strcpy.Addresses 0x4b50, 0x4b6c, 0x4ba8. No r2 axt. No Ghidra on entry.
function_addr.Call sites and r2 0x33c4 were not Ghidra functions. A later live decompile of 0x4ba8 returns FUN_00104a3c at 0x00104a3c. Not a bake-off score.
RECORDED RUN · DARPA CGC I386
Briefs ranked main. next_argv was empty. Address hops hit one in nine.
Eighteen CGC service binaries, a frozen stride of nine challenges. Quick ranking was identical: entry:main. handoff.next_argv was empty. Function VAs live on handoff.regions[].addr. Explicit decompile hops from those addresses hit Palindrome's planted site once. That hop experiment is not the current auto-queue.
entry:main.These services import libcgc, not strcpy. There is no PLT pivot to split patched from unpatched. next_argv stayed empty.
After af+pdf, Palindrome's full listing names cgc_check. The 12-line capsule may not. There is no torn c7 invalid.
Not from auto-queued decompile. CNMP showed insert_joke as a callee. The other six frozen challenges never named the planted routine.
next_argv is valid. r2b decompile BIN ADDR is explicit. Palindrome is DARPA's easy sample. Do not treat a source-named decompile as a loop hit.RETRIEVAL CHECK · NIST JULIET AARCH64
Region recall was zero. Good and bad ranked the same.
Seven CWEs, good and bad aarch64 ELFs, gcc -O0 -g. This is a failed retrieval check against labelled synthetic controls, not a detector score.
entry:main is the INCLUDEMAIN prologue, not the sink. imports:memory listing strcpy is not a hit.
Both twins still call strcpy, system, or free — or neither. Ranking did not split labelled good from labelled bad.
verify found a non-constant libc-sink caller when asked. It abstained on CWE-190, CWE-416, and CWE-476. That is a pivot, not a CWE verdict.
SAME EVIDENCE, MORE REVIEW ANGLES
Width cannot add evidence.
Keep review-width as a sort of the saved regions. It is not the OpenWrt result. In the pre-fix ubusd run, width 2 adds nothing; width 3 promotes the memory/path region that leads to verify.
Triage
Which saved regions deserve the first look?
- Passes included
- 1
- Model calls
- 0
- Unique regions
- 2
imports:networkvisible at width 1accept
entry:entry0visible at width 1entry0 @ 0x1e18
imports:memoryenters at width 3memcpy · sprintf · strcpy
One saved briefing · rules mode · top two per pass · duplicate evidence IDs collapse · target not executed
HOW THE RUN WAS BUILT
Use the tool that answers the next question.
r2b does not run every adapter on every file. It classifies the subject, records what the host can support, and keeps the result of each step with the subject hash.
Inventory and extract
Binwalk v3 found the SquashFS. unsquashfs recovered regular files. The artifact DAG kept parent and child hashes.
Map the binary
file, radare2, and Capstone supplied format, imports, functions, entry disassembly, and caller pivots.
Open one routine
Ghidra decompiled the selected old and new functions. angr added a bounded CFG. DWARF was checked for metadata; none was present.
Opt in when needed
Frida and GEF were not run. Executing a foreign-architecture target is an explicit decision, not a side effect of “full.”
PORTABLE RESULT
The bundle keeps the run together.
The downloadable bundle contains the pre-fix ubusd analysis, brief, tool status, provenance, and rules review. Target bytes are excluded. The adjacent case JSON links it to both firmware releases.
90544b29015b3fdd…201cbafr2b bundle inspect openwrt-24.10.3-ubusd-full.r2br --json
# analysis.json · briefing.json · provenance.json
# review.json · tools.json · target bytes excluded
INSTALL
Install r2b. Run a brief.
The installer creates a repo-local environment. It does not use sudo or modify system Python.
Let the installer choose a flavor.
It checks RAM and CPU architecture, then picks core, lab, or full. You can override the choice.
git clone https://github.com/sandbornm/r2brief.git
cd r2brief
./scripts/install.sh
coreradare2 support and the small-host path. No Ghidra, angr, or web UI.Pi, ARM, or under 8 GB
labThe regular workstation install. Add heavyweight analyzers only when a target needs them.A normal workstation
fullAdds angr support and enables the Ghidra adapter. Ghidra still has its own local setup step.16 GB+ x86_64
Ways to call r2b
CLI vs. harness: the CLI produces one result. A harness calls it, reads the versioned JSON, and decides what to run next. Empty next_argv is valid.
CLI
r2b brief ./firmware.itb \
+ --quick --extract --json
Run a brief and inspect it. JSON stays on stdout; diagnostics stay on stderr. --quick is the product.
Python
from r2b import analyze
report = analyze("./ubusd")
for region in report.regions:
print(region.id, region.evidence)
Call the same analysis as a library. Saving and model use remain explicit.
Harness
r2b brief ./ubusd --quick --json > brief.json
jq '.handoff.next_argv' brief.json
# empty is valid; VAs live on handoff.regions[].addr
r2b verify ./ubusd --import strcpy --json
jq next_argv; empty is valid. Function VAs live on handoff.regions[].addr. The real ubusd follow-up is a named-import verify.
Analysis never calls a model. Optional review can use Ollama or a configured hosted provider to reorder known evidence; the original brief stays unchanged. Provider setup →