Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
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.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
fnprint — Compara funciones en binarios por lo que hacen, no por cómo se ven sus bytes. Fingerprinting conductual de funciones mediante microejecución. | Kitploit
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
55468hace 12 díasRevisado por Kitploit
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 hash. dos funciones que se comportan igual obtienen huellas similares, incluso si fueron compiladas por un compilador diferente, con un nivel de optimización distinto o para una CPU 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 inestables entre -O0 y -O3 y se rinden entre arquitecturas. el comportamiento sobrevive a todo eso mucho mejor. sin datos de entrenamiento, sin modelo, un único binario estático.

tres cosas que hace que los demás no:

  • es seguro apuntarlo a malware. el emulador se ejecuta en un worker con privilegios separados bajo una jaula seccomp que prohíbe exec, sockets, aperturas de archivos y memoria ejecutable directamente (unicorn está compilado como intérprete sin JIT para que nunca necesite una). un binario manipulado que reviente el emulador obtiene un proceso que puede calcular y nada más. ver SECURITY.md. BinDiff, Diaphora, BSim, FLIRT y los emparejadores ML analizan bytes hostiles en el mismo proceso.
  • empareja entre arquitecturas. indexa la compilación x86-64 de la que tienes símbolos, nombra el firmware aarch64 sin símbolos. el modelo de efectos nunca ve el ISA. zlib x86-64 -O0 vs aarch64 -O0: 97.7% de funciones nombradas correctamente.
  • triage te da un veredicto, no un diff. dos corpus (vulnerable conocido, parcheado), un margen y una cola de revisión de las funciones que se inclinan a vulnerable. ese es el flujo de trabajo n-day como un solo comando.

ELF x86-64 y aarch64. ver límites antes de confiar en él.

muéstrame

fnprint nombrando funciones en un binario sin símbolos y compilado de forma diferente

apúntalo a un binario sin símbolos y a un corpus de cosas para las que ya tienes nombres:

$ 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 --threshold 0.6
named 27 function(s):
  addr        sim    graph  name
  0x000022f9  100.0%     0%  adler32_z  (libz.so)
  0x00002a8d  100.0%     0%  compress2  (libz.so)
  0x00004500  100.0%     0%  deflateStateCheck  (libz.so)
  0x0000ad5d  100.0%     0%  inflateBackEnd  (libz.so)
  0x0000b69c  100.0%     0%  inflateStateCheck  (libz.so)
  0x00012f8c  100.0%     0%  _tr_tally  (libz.so)
  ...

esa ejecución es una compilación -O0 totalmente sin símbolos nombrada desde un corpus -O2. nivel de optimización diferente, cero símbolos restantes, 27 nombres vuelven y los 27 son correctos. nombra aquello de lo que está seguro y se calla sobre el resto. (el gif de arriba es de la 0.5, que encontró 9; la 0.6 también recupera las funciones estáticas que un .so sin símbolos esconde detrás de sus exportaciones.)

lo mismo entre CPUs. corpus de la compilación x86-64, el objetivo es la compilación aarch64 de la misma fuente, sin símbolos:

$ fnprint index libz_x86_64.so -o corpus.db
$ fnprint query libz_arm64_stripped.so --corpus corpus.db
named 42 function(s):
  addr        sim    graph  name
  0x00001af4  100.0%     0%  adler32_z  (libz_x86_64.so)
  0x0000263c  100.0%     0%  compress2  (libz_x86_64.so)
  0x00002958  100.0%   100%  x2nmodp  (libz_x86_64.so)
  0x00002a98  100.0%     0%  crc32_z  (libz_x86_64.so)
  0x000034c4  100.0%   100%  crc32_combine64  (libz_x86_64.so)
  ...

42 nombradas, 42 correctas, desde un corpus construido para una CPU diferente.

la otra cosa que hace es comparar dos compilaciones 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. funciona con o sin nombres de símbolos: con ellos alinea por nombre, sin ellos alinea las dos compilaciones por comportamiento más el grafo de llamadas, así que dos imágenes de firmware totalmente sin símbolos también se comparan:

$ fnprint match old.so new.so
aligned 38 function pairs by behavior + call graph (no symbol names needed)
  unchanged:  31
  changed:    7
  low-signal: 57 (too small to judge)

changed behavior (lowest similarity first):
   30.5%  compress_block
   43.0%  deflate_rle -> deflate_huff
   68.8%  deflate_slow

para trabajo n-day real está 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 una compilación desconocida 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 puntuación de coincidencia única que tengas que interpretar:

$ 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 se pueden fijar 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 comprometerse.

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 configurado 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, 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), llamadas que hace, ramas que toma, qué devuelve. las direcciones absolutas se descartan, solo se conservan los desplazamientos 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.
  • las llamadas se nombran solo desde la tabla de importaciones (stubs plt, slots got), porque esa es la parte que sobrevive a strip. las llamadas internas son anónimas en la impresión y se convierten en aristas del grafo de llamadas, a partir de un barrido estático del cuerpo.
  • nombrar y comparar es una alineación global 1:1, no una búsqueda del mejor acierto por función: una función del corpus puede reclamarse una vez, primero el mejor par, luego la asignación se re-puntúa dos veces con un término de grafo de llamadas (cuántos de los llamadores y llamados emparejados de un par están emparejados entre sí). eso es lo que distingue a dos gemelos de comportamiento, y lo que impide que 27 funciones -O2 que todas insertan la misma comprobación de estado la reclamen todas.
Descargar herramienta