Recorded run · OpenWrt · AArch64
From two firmware images to one changed routine.
OpenWrt 24.10.3 and 24.10.4 for the same router contain 176 regular ELFs each. Exact comparison removes 106 unchanged common paths. Full analysis and targeted decompilation carry the changed ubusd binary to the empty-pattern guard added upstream.
The run reaches the event-registration routine at 0x00104a3c in 24.10.3 and 0x001049a0 in 24.10.4. The newer binary checks the pattern length before reading its final byte. Upstream source names the routine and confirms why the guard was added.
THE EVIDENCE PATH
Inspect any step.
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.
01 · REDUCE THE CORPUS
Most common binaries did not change.
The two extracted root filesystems each contain 176 regular ELFs. There are 109 paths in common: 106 have the same SHA-256, while busybox, ubusd, and odhcpd changed. busybox and odhcpd were not followed. Sixty-seven paths exist only on each side, mostly under the release-specific kernel-module directories or versioned library names.
109 common ELF paths
├── 106 unchanged
└── 3 changed
├── bin/busybox (not followed)
├── sbin/ubusd
└── usr/sbin/odhcpd (not followed)
Default brief --extract caps at 64 MiB / 200 files / depth 2 and does not keep every rootfs ELF. The 176-ELF table used sandboxed unsquashfs with a higher file cap. An unchanged control matters here: uhttpd has SHA-256 4c2dd929…d480e2 in both releases, so the release-delta question stops there. Follow-up briefs of the other two changed ELFs: busybox is an eight-byte banner timestamp (rebuild noise); odhcpd import maps differ (strcpy 3→1) and were not followed to a patched routine.
02 · TURN A PIVOT INTO ADDRESSES
Three callers, not “strcpy is dangerous.”
The brief records strcpy in a memory/path region. That import alone says nothing about reachability or data flow. r2b verify recovers three dynamic call sites and gives the decompiler a bounded job.
strcpy dynamic
0x00004b50 fcn.000033c4
0x00004b6c function unresolved
0x00004ba8 fcn.00004a3c
The third lead sits in the event register/send handler. Source names are added only after checking the pinned upstream code.
03 · COMPARE ONE ROUTINE
The guard is visible in the binaries.
len = strlen(pattern);
last = pattern[len - 1];
if (pattern[0] == '\0' || acl_ok) {
strcpy(destination, pattern);
}
len = strlen(pattern);
if (0 < len) {
last = pattern[len - 1];
if (acl_ok) {
strcpy(destination, pattern);
}
}
The decompiler output is evidence of the changed control flow. The upstream patch ↗ supplies the name ubusd_alloc_event_pattern and explicitly rejects an empty pattern before pattern[len - 1]. The OpenWrt advisory ↗ supplies the security classification.
04 · FULL RUN COVERAGE
See what ran, what was thin, and what stayed off.
63 functions · 71 imports
130 functions
300 stored nodes · 381 edges
instructions only
no debug info
runtime stays explicit
THE HONEST CLAIM
r2b did not discover the CVE. It made the path to the relevant routine short and reviewable.
Hash comparison supplied the release delta. Named-import verify supplied the caller list. r2b kept the firmware-child relationship, static-tool coverage, caller leads, selected function addresses, review overlay, and next commands. Upstream source supplied names and ground truth.
Kali aarch64 replay on 2026-09-02: same 176 ELFs / 3 changed paths / three 24.10.3 strcpy callers. Extract used bubblewrap. Device nodes still fail without root; that is not macOS-only. Default extract is 64 MiB / 200 files / depth 2; the 176-ELF inventory used a higher file cap.