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

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

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

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

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

Категории

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

fnprint

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

Репозиторий
5546811 дней назадПроверено Kitploit
Сайт

Популярное

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

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

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

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

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

fnprint

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

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

Три вещи, которые он делает, а другие — нет:

  • его безопасно натравливать на вредоносное ПО. Эмулятор работает в привилегированно изолированном воркере под seccomp-песочницей, которая наотрез запрещает exec, сокеты, открытие файлов и исполняемую память (unicorn собран как интерпретатор без JIT, поэтому она ему никогда не нужна). Специально сконструированный бинарник, который взломает эмулятор, получит процесс, способный только вычислять, и ничего больше. См. SECURITY.md. BinDiff, Diaphora, BSim, FLIRT и ML-сопоставители — все разбирают враждебные байты в собственном процессе.
  • он сопоставляет между архитектурами. Проиндексируйте сборку x86-64, для которой у вас есть символы, и назовите лишённую символов прошивку aarch64. Модель эффектов вообще не видит ISA. zlib x86-64 -O0 против aarch64 -O0: 97.7% функций названы правильно.
  • triage даёт вам вердикт, а не diff. Два корпуса (известно уязвимый, пропатченный), запас и очередь на проверку функций, склоняющихся к уязвимым. Это рабочий процесс n-day одной командой.

x86-64 и aarch64 ELF. См. ограничения, прежде чем доверять ему.

покажите мне

fnprint называет функции в лишённом символов бинарнике, собранном иначе

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

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

Этот запуск — полностью лишённая символов сборка -O0, названная по корпусу -O2. Другой уровень оптимизации, ноль оставшихся символов, возвращаются 27 имён, и все 27 верны. Он называет то, в чём уверен, и молчит об остальном. (gif выше — из 0.5, где нашлось 9; 0.6 также восстанавливает статические функции, которые лишённый символов .so прячет за своими экспортами.)

То же самое между процессорами. Корпус из сборки x86-64, цель — сборка aarch64 того же исходника, символы удалены:

$ 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 названы, 42 верны, по корпусу, собранному для другого процессора.

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

$ 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

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

$ 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-индекс не даёт запросам сравнивать всё со всем.
  • вызовы именуются только из таблицы импорта (plt-заглушки, слоты got), потому что именно эта часть переживает strip. Внутренние вызовы анонимны в отпечатке и вместо этого становятся рёбрами графа вызовов из статического обхода тела.
  • именование и сравнение — это глобальное взаимно-однозначное выравнивание, а не поиск лучшего попадания для каждой функции: функция корпуса может быть занята только один раз, сначала лучшая пара, затем назначение дважды переоценивается с учётом члена графа вызовов (сколько парных вызывающих и вызываемых у пары спарены друг с другом). Именно это отличает двух поведенческих близнецов и не даёт 27 функциям -O2, которые все инлайнят одну и ту же проверку состояния, претендовать на неё все сразу.
Скачать инструмент