Name functions in stripped ELF binaries by what they do, not their bytes. x86-64 and aarch64, across compilers, optimization levels and CPUs. Built for n-day triage when the builds don't line up, safe to point at malware.
Point it at a stripped binary, get names back from a corpus you built. The demo names functions in a clang -O3 build using a gcc -O0 corpus, so the byte-level code is completely different.
Build a corpus from binaries you have symbols for, then query an unknown stripped build. fnprint returns the closest-behaving named function for each unknown one.
Give it a vuln build and a patched build. It ranks which functions in an unknown target lean vulnerable, so you open the right ones first.
It compares behavior, so a compiler swap or an optimization-level gap that wrecks a CFG diff (bindiff, diaphora) matters much less here. Index the x86-64 build, name the aarch64 firmware: zlib -O0, 42 named, 42 right.
Microexecution happens in a seccomp + rlimit jailed worker on a no-JIT emulator, so nothing the target does can escape or execute.
fnprint microexecutes each function on a no-JIT unicorn fork, records the effects it produces (memory reads and writes, calls, syscalls, return behavior), and turns that effect trace into a MinHash/LSH fingerprint. Naming and diffing are a global 1:1 alignment of the two function sets, rescored with the call graph, so two behavioral twins get told apart by who calls them. The emulated code never runs with executable memory and never leaves a locked-down worker process.
The zlib memory-corruption bug fixed between 1.2.11 and 1.2.12. Corpora built at -O0, the target at -O2, so a CFG diff burns its time on optimization noise. Actual triage output:
52 functions triaged: 5 look vulnerable, 0 patched, 47 inconclusive review queue (vuln-leaning, strongest first): addr vuln% patched% margin matches 0x00002d40 100.0 69.5 +30.5 crc32_big vs adler32_z 0x0000d7e0 100.0 40.6 +59.4 _tr_tally vs _tr_tally 0x00004270 79.7 66.4 +13.3 deflate_stored vs deflate_stored 0x0000ce50 78.9 66.4 +12.5 _tr_stored_block vs _tr_stored_block 0x0000c860 68.8 30.5 +38.3 compress_block vs compress_block
Four of the five it surfaced (_tr_tally, deflate_stored, _tr_stored_block, compress_block) are exactly the CVE-changed functions, found across the -O0 to -O2 gap. The 47 unchanged functions stayed inconclusive instead of cluttering the queue. Full writeup with reproduce steps.