Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Herramientas/GitHubGitHub/1rhino2/fnprint
Análisis de VulnerabilidadesAnálisis Dinámico de Código (DAST)Ingeniería InversaAnálisis de MalwareAnálisis de BinariosAnálisis de Firmware
GitHub1rhino2/fnprint

fnprint

Compara funciones en binarios por lo que hacen, no por cómo se ven sus bytes. Fingerprinting conductual de funciones mediante microejecución.

Ver Repositorio
44437hace 19h 44mAún no revisado
Sitio web

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

fnprint

fnprint empareja funciones en binarios por lo que hacen, no por cómo se ven sus bytes o sus grafos de flujo de control. ejecuta cada función en un pequeño emulador con entradas inventadas, registra los efectos secundarios que produce y convierte ese comportamiento en una huella digital. dos funciones que se comportan igual obtienen huellas similares, incluso si fueron compiladas por un compilador distinto o con un nivel de optimización diferente.

el objetivo de hacerlo así: las firmas de bytes (FLIRT, FunctionID) se rompen en el momento en que el código se recompila, y los emparejadores de CFG (BinDiff, Diaphora) se vuelven frágiles entre -O0 y -O3. el comportamiento sobrevive mucho mejor a ambos.

solo ELF x86-64 por ahora. consulta limitaciones antes de confiar en él.

muéstrame

fnprint nombrando funciones en un binario stripped compilado de forma diferente

apúntalo a un binario stripped y a un corpus de cosas para las que ya tienes nombres:

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
  ...

esa ejecución es un build -O0 completamente stripped nombrado a partir de un corpus -O2. distinto nivel de optimización, cero símbolos restantes, y los nombres vuelven correctos. nombra aquello de lo que está seguro y se queda callado sobre el resto.

la otra cosa que hace es comparar dos builds y decirte qué funciones cambiaron de comportamiento, lo cual es útil cuando un proveedor publica un nuevo firmware y quieres saber qué se movió realmente:

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 trabajo real de n-day existe triage. construye un corpus a partir de la versión vulnerable conocida de una función y otro a partir de la versión parcheada, luego clasifica un build desconocido contra ambos. una función cercana al lado vulnerable y claramente separada del lado parcheado es lo que quieres poner delante de un humano, no solo una única puntuación de coincidencia que tengas 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

las 42 funciones que son idénticas en ambas versiones vuelven como inconclusas a propósito, no pueden asignarse a ninguno de los dos lados y no deberían marcarse. --margin y --min-sim controlan cuánto tienen que separarse los dos lados antes de que se decida.

cómo funciona

para cada función:

  • mapea el binario y salta a la función con basura en los registros de argumentos.
  • cualquier lectura de memoria que no hayamos preparado devuelve un valor determinista y la página se mapea sobre la marcha. los punteros salvajes nunca hacen fallar la ejecución, y la misma entrada siempre da la misma traza. este es el truco de microejecución de Godefroid.
  • las llamadas a otras funciones se sustituyen por stubs (se registran y luego se omiten) para que nunca nos sumerjamos en libc y la ejecución se mantenga sobre esta función.
  • registramos un flujo de efectos neutral respecto a la arquitectura: qué búferes de argumentos y campos de struct lee y escribe, qué clases de valores escribe (una copia de una entrada, una constante pequeña, un puntero), las llamadas que hace, las ramas que toma, qué devuelve. las direcciones absolutas se descartan, solo se conservan los offsets y las formas.
  • ese flujo se convierte en shingles y una firma minhash. la similitud es la fracción de slots minhash coincidentes, que estima cuánto se solapa el comportamiento de dos funciones. un índice de bandas LSH evita que las consultas comparen todo contra todo.

sin datos de entrenamiento, sin modelo. la misma idea aparece en la literatura como Blanket Execution (Egele et al, USENIX Security 2014); fnprint es una versión práctica y mantenida de ella con una CLI que realmente puedes usar.

instalación

necesita un toolchain de rust y las librerías 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 y query aceptan tanto un ELF como un .db que hayas construido con index, así que puedes generar la huella de un corpus una vez y reutilizarlo.

salida para máquinas

cada comando acepta un --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 es un esquema estable para scripts (y el stub de importación de Ghidra en contrib/), r2 emite comandos de renombrado afn que ejecutas dentro de rizin/radare2 con . fnprint.r2. los nombres de símbolos del objetivo se sanean antes de llegar a cualquiera de los dos, así que un nombre manipulado no puede inyectar comandos de r2. los esquemas y la configuración están en docs/integrations.md.

la indexación es de un solo proceso por defecto. FNPRINT_SHARDS=N fnprint index ... reparte un índice grande entre N workers enjaulados; el corpus es idéntico byte a byte sea cual sea N. solo ayuda en binarios grandes y ricos en funciones, así que está desactivado a menos que lo pidas.

precisión

la precisión rank-1 es: para una función en el build A, clasifica cada función del build B por similitud, ¿es el primer resultado el correcto? esa es exactamente la tarea de nombrar stripped. medido en zlib 1.3.1 (84 funciones), reproducible con bench/run.sh:

pairrank-1precision
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%

tabla completa más una segunda librería (lua) en bench/NUMBERS.md.

eval también informa recall@3 / recall@5 y una tasa de abstención, ya que solo el rank-1 oculta mucho. en gcc O0 -> O2 el primer resultado es correcto el 93% de las veces pero la función correcta está en el top 5 el 96.6% de las veces, así que un pequeño presupuesto de revisión cierra la mayor parte de la brecha. también se abstiene (rechaza una llamada confiada de "misma") en los pares de los que no está seguro en lugar de adivinar, por eso la precisión se mantiene alta mientras que el recall en el mismo umbral es bajo.

la lectura honesta: cuando al menos un lado tiene cierta riqueza de comportamiento (cualquier cosa con -O0/-O1, o un par entre compiladores en -O0) aterriza en el rango del 80-98%. cuando ambos lados están fuertemente optimizados el comportamiento que podemos observar se vuelve escaso y cae hacia el cara o cruz. esa es la frontera difícil para un emparejador de una sola pasada y sin entrenamiento y esto no pretende lo contrario.

en qué es malo

  • funciones diminutas. los thunks y los accesores de una línea no hacen lo suficiente para generar una huella, así que los omite (esos son los recuentos de "low-signal" y "with enough signal").
  • cómputo puro. dos checksums que ambos leen un búfer y devuelven un número se parecen, porque desde fuera casi lo son.
  • lógica profunda detrás de una precondición real. la microejecución con entrada basura ejercita el comportamiento de entrada de una función. un cambio enterrado en un estado al que nunca llegamos con entrada basura no aparecerá en match. captura cambios estructurales y de rutas tempranas, no todos los ajustes profundos.
  • optimización intensa en ambos lados, como muestran los números anteriores.
  • ofuscación intensa (especialmente basada en vm) lo destrozará.

arte previo, y dónde encaja esto

  • FLIRT / FunctionID / Lumina: firmas de bytes. exactas, rápidas, se rompen al recompilar.
  • BinDiff / Diaphora: estructura de grafo. buenas, pero frágiles entre optimizaciones y arquitecturas.
  • Ghidra BSim: vectores de características del decompilador. más cercano en espíritu, más o menos de una sola arquitectura.
  • Asm2Vec / SAFE / jTrans: embeddings aprendidos. potentes, pero necesitan entrenamiento y no generalizan a arquitecturas sobre las que nadie entrenó.
  • microejecución (Godefroid, 2014) y Blanket Execution (Egele et al, 2014): las raíces académicas de este enfoque. ninguna herramienta mantenida lo distribuyó.

fnprint es la opción sin entrenamiento y centrada en el comportamiento. el modelo de efectos ya es neutral respecto a la arquitectura, lo cual es la base para emparejar entre CPUs.

hoja de ruta

  • arm64 y mips, para que puedas generar la huella de una función en x86 y encontrarla en un firmware de router stripped. el modelo de efectos ya es neutral respecto a la arquitectura, esto es sobre todo fontanería del emulador por arquitectura.
  • cobertura de rutas más inteligente. el volteo ingenuo de ramas está en el código (explore_depth, desactivado por defecto) pero añade ruido específico del build en rutas imposibles y perjudicó la precisión entre builds en las pruebas, así que necesita un filtro de consistencia de rutas antes de ganarse su lugar.
  • cargadores de pe y mach-o.
  • la exportación a rizin/radare2 ya se distribuye (--format r2); un plugin real de ghidra es lo siguiente. mientras tanto hay un stub de importación jython experimental en contrib/.

licencia

MIT. consulta LICENSE.

Descargar herramienta