Introduction
hereticus compares two versions of a binary or firmware. It finds the functions that changed, and puts the security fix near the top.
$ hereticus before.bin after.bin
Note
Coding agents wrote much of this code.
Why hereticus
A vendor ships a security update with one line of advisory: “a memory corruption issue was fixed”. To know what the bug was, you must find the function that the update changed. This is patch diffing. Three problems make it slow:
- The binaries are stripped. Firmware and kernels ship with no symbols, and often with no ELF header at all.
- Everything moves. A new release moves each function and each global, so a byte diff marks almost all the code as changed.
- The usual tools are heavy. BinDiff and Diaphora need IDA, and Ghidra Version Tracking needs a project, a GUI and an analysis of each program. None fits in a shell script or a CI job.
hereticus is one static binary. It reads the two files, pairs their
functions, and prints a short list, the most likely fix first.
It finds the fix, on the first rows
On published security fixes, the fixed function is on the first rows of the report:
| Fix | Binary | Row of the fix | Rows in the report |
|---|---|---|---|
| CVE-2026-30874 | procd, OpenWrt, stripped | 1 | 24 |
| CVE-2026-35385 | dropbear scp, OpenWrt, stripped | 1 | 19 |
| CVE-2024-23265 | AppleDiskImages2, iOS kernelcache | 1 | 33 |
| CVE-2021-30883 | AppleCLCD, iOS kernelcache | 1 | 333 |
See Real-Life Examples for all of them, each with the commands to do it again.
It shows less noise than other tools
On 35 stripped libraries, each built before and after one upstream fix:
| Tool | Fixes found | Fix on row 1 | Rows the fix did not touch |
|---|---|---|---|
| hereticus | 35 | 20 | 0 |
| radiff2 | 20 | 9 | 2418 |
| Ghidra | 33 | 18 | 629 |
This is a narrow test, which hereticus was developed with. See
Comparison With Other Tools for how it scores each
tool, and for its limits.
It finds the functions of stripped code
Before a tool can compare functions, it must find them. In one raw OpenWrt
kernel, with no symbols and no sections, hereticus finds
19277 of the
20988 real function
starts, and Ghidra finds
13210. The
Ghidra extension adds the functions that Ghidra misses.
It is made for coding agents too
Ghidra, IDA and BinDiff are made for a person at a GUI. An agent can use
them only through a server or a plugin. hereticus is one command that
gives one answer, so an agent runs it as it runs grep:
- The answer comes first. The most likely fix is on the first rows,
so
| headis enough, and the agent reads a few rows, not a project. - The output is short.
--format compactgives one line for each pair, and--format jsongives one object for each pair. - Each name works as input. The report names a stripped function
sub_7630, and--diff sub_7630shows its diff. - The diff reads alike on each architecture.
--view ilshows a compare, a call or a text in one form, asif (w0 == 0) goto ...andcall strncmp, so the agent does not need to know each instruction set. - An error tells what to do next. With no
--base, the error gives the base that the calls of the image show, where they show one. A failure exits with 2. - No setup. One static binary, with no IDA, Ghidra, Python, server or
project.
--exit-codelets a CI job or a script stop on a change.
It reads what firmware ships
- Architectures: A32, Thumb-2, AArch64, MIPS32, MIPS16e, x86-64 and 32-bit x86.
- Files: ELF (also with no section headers, as
sstripleaves it), Mach-O and iOS kernelcaches, and raw flash dumps. - Firmware images: zImage, FIT, uImage, sysupgrade, lzma-loader and
TP-Link containers, with
--unpack.
Who It Is For
- Security researchers who find the bug that an update fixed.
- Vendors and integrators who check that a backport of a fix is in a build.
- Malware analysts who compare two builds of one family. See Malware.
Where to Go Next
- Installation: get the binary.
- Usage: read the report, and the options.
- Examples: a raw firmware stub, and the diff of one function.
- Real-Life Examples: published fixes, step by step.
- Methods: how it finds and pairs the functions, and why each method is there.