Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
fnprint — Corresponder funções em binários pelo que elas fazem, não pela aparência de seus bytes. Impressão digital comportamental de funções via microexecução. | Kitploit
Ferramentas/GitHubGitHub/1rhino2/fnprint
Análise de VulnerabilidadesAnálise Dinâmica de Código (DAST)Engenharia ReversaAnálise de MalwareAnálise de BináriosAnálise de Firmware
GitHub1rhino2/fnprint

fnprint

Corresponder funções em binários pelo que elas fazem, não pela aparência de seus bytes. Impressão digital comportamental de funções via microexecução.

Ver Repositório
44437há 19h 43mAinda não revisado
Site

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

fnprint

fnprint faz correspondência de funções em binários pelo que elas fazem, não pela aparência dos seus bytes ou grafos de fluxo de controle. ele executa cada função em um pequeno emulador com entradas inventadas, registra os efeitos colaterais que ela produz e transforma esse comportamento em um hash de impressão digital. duas funções que se comportam da mesma forma recebem impressões digitais semelhantes, mesmo que tenham sido compiladas por um compilador diferente ou em um nível de otimização diferente.

o ponto de fazer isso dessa forma: assinaturas de bytes (FLIRT, FunctionID) quebram no momento em que o código é recompilado, e comparadores de CFG (BinDiff, Diaphora) ficam instáveis entre -O0 e -O3. o comportamento sobrevive a ambos muito melhor.

apenas ELF x86-64 por enquanto. veja limitações antes de confiar nele.

me mostre

fnprint nomeando funções em um binário stripped compilado de forma diferente

aponte-o para um binário stripped e um corpus de coisas para as quais você já tem nomes:

root@kitploit:~
$ strip --strip-all mystery.so
$ nm mystery.so
nm: mystery.so: no symbols

$ fnprint index libz.so -o corpus.db          # a build you have symbols for
$ fnprint query mystery.so --corpus corpus.db
named 9 function(s):
  0x000022f9  100.0%  adler32_z
  0x00002a8d  100.0%  compress2
  0x0000ad5d  100.0%  inflateBackEnd
  0x0000adc4   86.7%  inflate_fast
  0x00002e67   80.5%  crc32_z
  ...

essa execução é um build -O0 totalmente stripped nomeado a partir de um corpus -O2. nível de otimização diferente, zero símbolos restantes, e os nomes voltam corretos. ele nomeia aquilo sobre o que tem confiança e fica quieto sobre o resto.

a outra coisa que ele faz é comparar dois builds e dizer quais funções mudaram de comportamento, o que é útil quando um fornecedor lança um novo firmware e você quer saber o que realmente mudou:

root@kitploit:~
$ fnprint match old.so new.so
compared 84 functions present in both
  unchanged:  53
  changed:    1
  low-signal: 31 (too small to judge)

changed behavior (lowest similarity first):
   61.7%  deflate_stored

para trabalho real de n-day existe o triage. construa um corpus a partir da versão conhecida como vulnerável de uma função e outro a partir da versão corrigida, depois classifique um build desconhecido contra ambos. uma função próxima do lado vulnerável e claramente separada do lado corrigido é o que você quer colocar na frente de um humano, não apenas uma única pontuação de correspondência que você tem que interpretar:

root@kitploit:~
$ fnprint index vuln.so    -o vuln.db
$ fnprint index patched.so -o patched.db
$ fnprint triage mystery.so --vuln vuln.db --patched patched.db
43 functions triaged: 1 look vulnerable, 0 patched, 42 inconclusive

review queue (vuln-leaning, strongest first):
   addr         vuln%  patched%  margin  matches
   0x000022f9  100.0     27.3   +72.7  adler32_z vs crc32_z

as 42 funções que são idênticas em ambas as versões voltam como inconclusivas de propósito, elas não podem ser atribuídas a nenhum dos lados e não deveriam ser sinalizadas. --margin e --min-sim controlam o quão fortemente os dois lados precisam se separar antes que ele se comprometa.

como funciona

para cada função:

  • mapeia o binário e salta para a função com lixo nos registradores de argumento.
  • qualquer leitura de memória que não configuramos retorna um valor determinístico e a página é mapeada em tempo real. ponteiros selvagens nunca travam a execução, e a mesma entrada sempre produz o mesmo trace. esse é o truque de microexecução de Godefroid.
  • chamadas para outras funções são substituídas por stubs (registradas, depois ignoradas) para que nunca mergulhemos na libc e a execução permaneça sobre esta função.
  • registramos um fluxo de efeitos neutro em relação à arquitetura: quais buffers de argumento e campos de struct ela lê e escreve, quais classes de valor ela escreve (uma cópia de uma entrada, uma constante pequena, um ponteiro), chamadas que faz, desvios que toma, o que retorna. endereços absolutos são descartados, apenas offsets e formas são mantidos.
  • esse fluxo é transformado em shingles e uma assinatura minhash. a similaridade é a fração de slots minhash correspondentes, o que estima o quanto o comportamento de duas funções se sobrepõe. um índice de bandas LSH evita que as consultas comparem tudo contra tudo.

sem dados de treinamento, sem modelo. a mesma ideia aparece na literatura como Blanket Execution (Egele et al, USENIX Security 2014); fnprint é uma abordagem prática e mantida dela com uma CLI que você pode realmente usar.

instalação

precisa de um toolchain rust e das bibliotecas unicorn + capstone.

root@kitploit:~
# debian/ubuntu/kali
sudo apt install libunicorn-dev libcapstone-dev

cargo install --path cli
# or just
cargo build --release   # binary at target/release/fnprint

uso

root@kitploit:~
fnprint index <binary> [-o out.db]      fingerprint every function, optionally to a db
fnprint match <a> <b>                   diff two binaries (or .db files) by behavior
fnprint query <target> --corpus <db>    name unknown functions from a corpus
fnprint triage <t> --vuln <db> --patched <db>   rank a build against vuln vs patched corpora
fnprint eval <a> <b>                     accuracy metrics using symbol names as truth
fnprint dump <binary> <func>            print the recorded effect trace (debugging)

match e query aceitam tanto um ELF quanto um .db que você construiu com index, então você pode gerar a impressão digital de um corpus uma vez e reutilizá-lo.

saída para máquina

todo comando aceita um --format global:

root@kitploit:~
fnprint query mystery.so --corpus corpus.db --format json > names.json
fnprint query mystery.so --corpus corpus.db --format r2   > fnprint.r2

json é um schema estável para scripts (e o stub de importação do Ghidra em contrib/), r2 emite comandos de renomeação afn que você executa dentro do rizin/radare2 com . fnprint.r2. nomes de símbolos do alvo são sanitizados antes de chegarem a qualquer um dos dois, então um nome criado maliciosamente não pode injetar comandos r2. schemas e configuração estão em docs/integrations.md.

a indexação é de processo único por padrão. FNPRINT_SHARDS=N fnprint index ... distribui um índice grande entre N workers isolados; o corpus é byte-idêntico qualquer que seja o N. isso só ajuda em binários grandes e ricos em funções, então fica desligado a menos que você peça.

precisão

a precisão rank-1 é: para uma função no build A, classifique cada função no build B por similaridade, o acerto no topo é o correto. essa é exatamente a tarefa de nomeação de stripped. medido no zlib 1.3.1 (84 funções), reproduzível com bench/run.sh:

parrank-1precisão
gcc O0 -> O197.1%93.8%
gcc O0 -> O293.1%83.3%
gcc O0 -> O391.3%100.0%
gcc/clang O097.7%97.1%
gcc O2 -> O356.5%66.7%
gcc/clang O259.1%50.0%

tabela completa mais uma segunda biblioteca (lua) em bench/NUMBERS.md.

eval também reporta recall@3 / recall@5 e uma taxa de abstenção, já que o rank-1 sozinho esconde muita coisa. no gcc O0 -> O2 o acerto no topo está correto 93% das vezes, mas a função correta está no top 5 em 96.6% das vezes, então um pequeno orçamento de revisão fecha a maior parte da lacuna. ele também se abstém (recusa uma chamada confiante de "mesmo") nos pares sobre os quais não tem certeza em vez de adivinhar, e é por isso que a precisão permanece alta enquanto o recall no mesmo limiar é baixo.

a leitura honesta: quando pelo menos um lado tem alguma riqueza comportamental (qualquer coisa com -O0/-O1, ou um par cross-compiler em -O0) ele fica na faixa de 80-98%. quando ambos os lados são fortemente otimizados, o comportamento que conseguimos observar fica escasso e ele cai para quase uma moeda ao ar. essa é a fronteira difícil para um matcher de passagem única e sem treinamento, e isto não finge o contrário.

no que ele é ruim

  • funções minúsculas. thunks e acessores de uma linha não fazem o suficiente para gerar impressão digital, então ele os retém (essa é a contagem de "low-signal" e "with enough signal").
  • computação pura. dois checksums que ambos leem um buffer e retornam um número se parecem, porque do lado de fora eles quase são.
  • lógica profunda atrás de uma pré-condição real. a microexecução com entrada lixo exercita o comportamento de entrada de uma função. uma mudança enterrada em um estado que nunca alcançamos com entrada lixo não aparecerá em match. ele captura mudanças estruturais e de caminho inicial, não toda alteração profunda.
  • otimização pesada em ambos os lados, como os números acima mostram.
  • ofuscação pesada (especialmente baseada em vm) vai destruí-lo.

arte anterior, e onde isto se situa

  • FLIRT / FunctionID / Lumina: assinaturas de bytes. exatas, rápidas, quebram na recompilação.
  • BinDiff / Diaphora: estrutura de grafo. boas, mas frágeis entre otimização e arquitetura.
  • Ghidra BSim: vetores de características do decompilador. mais próximo em espírito, meio mono-arquitetura.
  • Asm2Vec / SAFE / jTrans: embeddings aprendidos. fortes, mas precisam de treinamento e não generalizam para arquiteturas nas quais ninguém treinou.
  • microexecução (Godefroid, 2014) e Blanket Execution (Egele et al, 2014): as raízes acadêmicas desta abordagem. nenhuma ferramenta mantida a entregou.

fnprint é a opção sem treinamento e com comportamento em primeiro lugar. o modelo de efeitos já é neutro em relação à arquitetura, o que é a base para correspondência entre CPUs.

roadmap

  • arm64 e mips, para que você possa gerar a impressão digital de uma função em x86 e encontrá-la em um firmware de roteador stripped. o modelo de efeitos já é neutro em relação à arquitetura, isto é principalmente encanamento de emulador por arquitetura.
  • cobertura de caminhos mais inteligente. a inversão ingênua de desvios está no código (explore_depth, desligado por padrão), mas ela adiciona ruído específico do build em caminhos impossíveis e prejudicou a precisão entre builds nos testes, então precisa de um filtro de consistência de caminhos antes de valer a pena.
  • loaders de pe e mach-o.
  • a exportação para rizin/radare2 já sai agora (--format r2); um plugin real para ghidra é o próximo. há um stub experimental de importação jython em contrib/ enquanto isso.

licença

MIT. veja LICENSE.

Baixar ferramenta