fnprint

behavioral function fingerprinting

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.

$ cargo install fnprint

show me

fnprint naming functions in a stripped, differently-compiled binary

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.

what it does

name stripped functions

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.

rank n-day patch diffs

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.

survives recompiles

It compares behavior, so a compiler swap or an optimization-level gap that wrecks a CFG diff (bindiff, diaphora) matters much less here.

runs untrusted code safely

Microexecution happens in a seccomp + rlimit jailed worker on a no-JIT emulator, so nothing the target does can escape or execute.

how it works

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.

a real n-day: CVE-2018-25032

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.

what it is bad at