Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
fnprint — Сопоставляйте функции в бинарных файлах по тому, что они делают, а не по тому, как выглядят их байты. Поведенческое фингерпринтирование функций через микроисполнение. | Kitploit
Инструменты/GitHubGitHub/1rhino2/fnprint
Анализ уязвимостейДинамический анализ кода (DAST)Обратная инженерияАнализ вредоносных программАнализ Бинарных ФайловАнализ Прошивок
GitHub1rhino2/fnprint

fnprint

Сопоставляйте функции в бинарных файлах по тому, что они делают, а не по тому, как выглядят их байты. Поведенческое фингерпринтирование функций через микроисполнение.

Репозиторий
4443719 ч 45 мин назадЕщё не проверено
Сайт

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

fnprint

fnprint сопоставляет функции в бинарниках по тому, что они делают, а не по тому, как выглядят их байты или графы потока управления. Он выполняет каждую функцию в крошечном эмуляторе с выдуманными входными данными, записывает производимые ею побочные эффекты и хеширует это поведение в отпечаток. Две функции, ведущие себя одинаково, получают похожие отпечатки, даже если они были собраны другим компилятором или с другим уровнем оптимизации.

Смысл такого подхода: байтовые сигнатуры (FLIRT, FunctionID) ломаются в момент перекомпиляции кода, а сопоставители по CFG (BinDiff, Diaphora) становятся ненадёжными при переходе от -O0 к -O3. Поведение переживает и то, и другое гораздо лучше.

Пока только x86-64 ELF. Прежде чем доверять, см. ограничения.

покажите

fnprint naming functions in a stripped, differently-compiled binary

Наведите его на бинарник без символов и корпус того, для чего у вас уже есть имена:

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

Этот запуск — полностью очищенная от символов сборка -O0, названная по корпусу -O2. Другой уровень оптимизации, ноль оставшихся символов, и имена возвращаются правильными. Он называет то, в чём уверен, и молчит об остальном.

Ещё он умеет сравнивать две сборки и сообщать, какие функции изменили поведение, что удобно, когда вендор выпускает новую прошивку и вы хотите знать, что реально изменилось:

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

Для реальной работы по n-day есть triage. Постройте один корпус из известной уязвимой версии функции и один из пропатченной, затем ранжируйте неизвестную сборку относительно обоих. Функция, близкая к уязвимой стороне и явно отделённая от пропатченной, — это то, что вы хотите показать человеку, а не просто единственная оценка совпадения, которую нужно интерпретировать:

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

42 функции, идентичные в обеих версиях, намеренно возвращаются как inconclusive — их нельзя привязать ни к одной из сторон, и их не следует помечать. --margin и --min-sim управляют тем, насколько сильно должны разойтись две стороны, прежде чем он вынесет решение.

как это работает

Для каждой функции:

  • отображаем бинарник и переходим к функции с мусором в регистрах аргументов.
  • любое чтение из памяти, которое мы не настроили, возвращает детерминированное значение, а страница отображается на лету. Дикие указатели никогда не роняют запуск, и одни и те же входные данные всегда дают одну и ту же трассу. Это трюк микроисполнения Годфруа.
  • вызовы других функций заглушаются (записываются, затем пропускаются), так что мы никогда не погружаемся в libc и запуск остаётся посвящён этой функции.
  • мы регистрируем архитектурно-нейтральный поток эффектов: какие буферы аргументов и поля структур она читает и пишет, какие классы значений записывает (копия входных данных, небольшая константа, указатель), какие вызовы делает, какие ветвления выбирает, что возвращает. Абсолютные адреса отбрасываются, сохраняются только смещения и формы.
  • этот поток превращается в шинглы и minhash-сигнатуру. Схожесть — это доля совпадающих слотов minhash, что оценивает, насколько перекрывается поведение двух функций. Индекс полос LSH не даёт запросам сравнивать всё со всем.

Никаких обучающих данных, никакой модели. Та же идея встречается в литературе как Blanket Execution (Egele et al, USENIX Security 2014); fnprint — это практичная, поддерживаемая реализация этого подхода с CLI, которым действительно можно пользоваться.

установка

Нужен инструментарий Rust и библиотеки 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

использование

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 и query принимают либо ELF, либо .db, который вы построили с помощью index, так что можно один раз снять отпечатки корпуса и переиспользовать их.

машинный вывод

Каждая команда принимает глобальный --format:

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 — это стабильная схема для скриптов (и заглушка импорта для Ghidra в contrib/), r2 выдаёт команды переименования afn, которые вы выполняете внутри rizin/radare2 с помощью . fnprint.r2. Имена символов из цели санитизируются, прежде чем попасть в любой из них, так что специально сконструированное имя не может внедрить команды r2. Схемы и настройка — в docs/integrations.md.

Индексация по умолчанию однопроцессная. FNPRINT_SHARDS=N fnprint index ... распределяет большой индекс по N изолированным воркерам; корпус побайтово идентичен при любом N. Это помогает только на больших, богатых функциями бинарниках, поэтому отключено, пока вы сами не попросите.

точность

Точность rank-1 — это: для функции в сборке A проранжировать все функции в сборке B по схожести; является ли верхнее совпадение правильным. Это в точности задача именования без символов. Измерено на zlib 1.3.1 (84 функции), воспроизводится с помощью 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%

Полная таблица плюс вторая библиотека (lua) в bench/NUMBERS.md.

eval также сообщает recall@3 / recall@5 и долю воздержаний, поскольку один rank-1 скрывает многое. На gcc O0 -> O2 верхнее совпадение верно в 93% случаев, но правильная функция попадает в топ-5 в 96.6% случаев, так что небольшой бюджет на проверку закрывает большую часть разрыва. Он также воздерживается (отказывается от уверенного вердикта «то же самое») на парах, в которых не уверен, вместо угадывания — поэтому точность остаётся высокой, тогда как полнота при том же пороге низкая.

Честное прочтение: когда хотя бы одна сторона обладает некоторой поведенческой насыщенностью (что угодно с -O0/-O1, или кросс-компиляторная пара на -O0), он попадает в диапазон 80-98%. Когда обе стороны сильно оптимизированы, наблюдаемое нами поведение становится скудным, и он падает почти до подбрасывания монеты. Это трудный фронтир для однопроходного сопоставителя без обучения, и он не притворяется иным.

в чём он плох

  • крошечные функции. Трампилины и однострочные аксессоры делают слишком мало для отпечатка, поэтому он их удерживает (это счётчики «low-signal» и «with enough signal»).
  • чистые вычисления. Две контрольные суммы, которые обе читают буфер и возвращают число, выглядят похоже, потому что снаружи они почти таковы и есть.
  • глубокая логика за реальным предусловием. Микроисполнение с мусорным вводом прорабатывает поведение функции на входе. Изменение, погребённое в состоянии, до которого мы никогда не добираемся с мусорным вводом, не проявится в match. Он ловит структурные изменения и изменения на ранних путях, но не каждую глубокую правку.
  • тяжёлая оптимизация с обеих сторон, как показывают числа выше.
  • тяжёлая обфускация (особенно на основе VM) его разрушит.

предшествующие работы и место этого проекта

  • FLIRT / FunctionID / Lumina: байтовые сигнатуры. Точные, быстрые, ломаются при перекомпиляции.
  • BinDiff / Diaphora: структура графа. Хороши, но хрупки при смене оптимизации и архитектуры.
  • Ghidra BSim: векторы признаков декомпилятора. Ближе по духу, но в основном под одну архитектуру.
  • Asm2Vec / SAFE / jTrans: обученные эмбеддинги. Сильны, но требуют обучения и не обобщаются на архитектуры, на которых никто не обучался.
  • микроисполнение (Godefroid, 2014) и Blanket Execution (Egele et al, 2014): академические корни этого подхода. Ни один поддерживаемый инструмент его не реализовал.

fnprint — это вариант без обучения, ставящий поведение на первое место. Модель эффектов уже архитектурно-нейтральна, что является основой для сопоставления между процессорами.

план развития

  • arm64 и mips, чтобы можно было снять отпечаток функции на x86 и найти её в прошивке роутера без символов. Модель эффектов уже архитектурно-нейтральна, это в основном обвязка эмулятора под каждую архитектуру.
  • более умное покрытие путей. Наивное переключение ветвлений есть в коде (explore_depth, по умолчанию выключено), но оно добавляет специфичный для сборки шум на невозможных путях и в тестировании ухудшало кросс-сборочную точность, поэтому ему нужен фильтр согласованности путей, прежде чем оно оправдает себя.
  • загрузчики pe и mach-o.
  • экспорт в rizin/radare2 уже поставляется (--format r2); настоящий плагин для Ghidra — следующий. Пока же в contrib/ есть экспериментальная заглушка импорта на jython.

лицензия

MIT. см. LICENSE.

Скачать инструмент