Name functions in stripped x86-64 ELF binaries by what they do, not their bytes. Built for n-day triage when the builds don't line up.
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.
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. Matching is behavioral similarity between fingerprints, not byte or structure matching. 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.