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

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

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

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

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

Категории

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

fnprint

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

Репозиторий
2021519 ч 13 мин назадЕщё не проверено

Популярное

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

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

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

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

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

fnprint

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

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

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

демонстрация

направьте его на бинарный файл с удалёнными символами и на корпус того, для чего у вас уже есть имена:

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

точность

точность rank-1: для функции из сборки A ранжируем все функции из сборки B по сходству — попадает ли верхний результат в правильную функцию. это ровно та задача, что и именование бинарника без символов. измерено на zlib 1.3.1 (84 функции), воспроизводимо с помощью bench/run.sh:

полная таблица, а также вторая библиотека (lua), — в bench/NUMBERS.md.

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

честная оценка: когда хотя бы одна сторона обладает достаточной поведенческой насыщенностью (всё, что собрано с -O0/-O1, или кросс-компиляторная пара на -O0), результат попадает в диапазон 80–98%. когда обе стороны сильно оптимизированы, наблюдаемое поведение становится слишком бедным, и точность падает до подбрасывания монетки. это тяжёлый рубеж для однопроходного матчера без обучения, и инструмент не делает вид, что это не так.

где он слаб

  • крошечные функции. переходники (thunks) и однострочные аксессоры слишком мало делают, чтобы получить отпечаток, поэтому он их пропускает (вот откуда счётчики «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 — это вариант без обучения и с приоритетом поведения. модель эффектов уже архитектурно-нейтральна, что закладывает основу для сопоставления между разными CPU.

дорожная карта

  • arm64 и mips, чтобы можно было получить отпечаток функции на x86 и найти её в прошивке роутера без символов. модель эффектов уже архитектурно-нейтральна, так что это в основном обвязка эмулятора для каждой архитектуры.
  • более умное покрытие путей. в коде есть наивное переключение ветвей (explore_depth, выключено по умолчанию), но оно добавляет специфичный для сборки шум на невозможных путях и в тестах вредит точности сравнения между сборками, так что ему нужен фильтр согласованности путей, прежде чем он себя оправдает.
  • загрузчики PE и Mach-O.
  • плагины для ghidra / ida, которые вызывают cli и переименовывают сопоставленные функции на месте.

лицензия

MIT. см. LICENSE.

Скачать инструмент
параrank-1точность
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%