Статья Динамический анализ бинарных файлов: Frida, PIN и DynamoRIO — от трассировки до распаковки

Матрёшка, разобранная на слои на тёмном антистатическом коврике. Внешняя оболочка с гравировкой, внутренние фигуры меньшего размера подсвечены тёплой настольной лампой в окружении глубоких сине-сер...


Упакованный Themida бинарь. Ghidra выдаёт шум вместо кода, декомпилятор IDA Pro разваливает границы функций, x64dbg натыкается на антиотладочные проверки каждые десять инструкций. Три часа статического анализа - ноль полезного выхлопа. Подключаю Frida-скрипт на перехват VirtualAlloc, жду момент записи распакованного кода в RWX-страницу, дамплю - через двадцать минут чистый payload с читаемыми строками. Этот сценарий повторяется на каждом втором упакованном семпле, и именно тут динамический анализ бинарных файлов через инструментацию делает за минуты то, что статика не способна дать в принципе. Три фреймворка - Frida, Intel PIN и DynamoRIO - покрывают весь спектр задач, но каждый заточен под своё. Разберём архитектуру, практические сценарии и границы применимости каждого.

Как устроена динамическая инструментация бинарей​

Динамическая бинарная инструментация (DBI) - механизм, который позволяет вставлять аналитический код в скомпилированный бинарь прямо во время исполнения. В отличие от классического отладчика, DBI-фреймворк не останавливает программу на breakpoint и не ждёт ввода оператора. Он автоматически перехватывает поток исполнения, прогоняет через callback-функции и возвращает управление - прозрачно для анализируемой программы.

Три кита, на которых всё стоит:
  • Механизм инструментации - то, как фреймворк вмешивается в поток исполнения: через JIT-компиляцию, подмену инструкций трамплинами или ловушки.
  • Интроспекция - чтение архитектурного состояния цели: регистры, память, стек, флаги. Без неё инструментация бесполезна - нужно не просто перехватить управление, а понять, что происходит.
  • Манипуляция исполнением - изменение регистров, подмена возвращаемых значений, перенаправление потока управления. Это превращает DBI из наблюдателя в активный инструмент.
На практике основных подходов к динамической инструментации бинарей два.

JIT-assisted Dynamic Binary Translation - подход DynamoRIO и Intel PIN. Фреймворк перехватывает базовые блоки целевой программы до их исполнения, копирует в отдельный code cache, модифицирует (вставляет инструментирующий код) и перенаправляет исполнение на модифицированную копию. Оригинальный код никогда не выполняется напрямую - процессор работает только с code cache. По данным Cisco Talos, DynamoRIO дополнительно строит trace cache из часто исполняемых цепочек базовых блоков, что снижает overhead за счёт меньшего количества переключений контекста между целевым кодом и ядром фреймворка.

Dynamic Probe Injection - подход Frida. Вместо полного копирования кода в cache фреймворк точечно заменяет инструкции в целевом процессе на трамплины - переходы в обработчики. Это даёт меньший overhead при редких перехватах (хук на одну функцию), но не позволяет эффективно инструментировать каждую инструкцию в потоке.

Разница между подходами критична для выбора инструмента. JIT-DBT видит весь поток инструкций - идеален для трассировки выполнения кода, построения CFG, coverage-guided фаззинга. DPI эффективен для точечных перехватов: hook конкретной функции, подмена результата, мониторинг API-вызовов. Путать одно с другим - терять часы на задаче, которая решается за минуты правильным инструментом.

Frida, DynamoRIO и PIN - когда что брать​

Выбор DBI-фреймворка - не вопрос «какой лучше», а вопрос «какую задачу решаю прямо сейчас».

КритерийFridaDynamoRIOIntel PIN
Overhead при полной трассировкеВысокий (не предназначен)НизкийСредний
Overhead при точечном hookingМинимальныйИзбыточен для задачиИзбыточен для задачи
Язык инструментацииJavaScript / TypeScriptC / C++C / C++
ПлатформыWindows, Linux, macOS, Android, iOSWindows, Linux, Android (эксперим.)Windows, Linux (macOS - устаревшая поддержка, до PIN 3.7)
Порог входаНизкий (часы)Высокий (дни)Средний (дни)
Self-modifying кодОграниченно (Interceptor не обрабатывает SMC; Stalker перекомпилирует блоки при полной трассировке)Нативно (code cache пересобирается)Нативно
Прозрачность для anti-debugСредняяВысокаяСредняя
Подходит для coverage-фаззингаНетДа (AFL++ / WinAFL)Частично
Открытый исходный кодДаДаНет
Статус поддержкиАктивные релизы, GitHubv11.3.0, активный (2025)Обновления Intel, закрытый цикл

Задача - перехватить 1-5 API-вызовов и подменить результат (обход IsDebuggerPresent, патч проверки лицензии, мониторинг сетевых вызовов): берите Frida. Скрипт за 10 минут, результат мгновенный.

Задача - построить полную трассировку (анализ упакованного семпла, поиск скрытого кода, восстановление CFG после деобфускации): DynamoRIO. Code cache и trace cache дают минимальный overhead при полном покрытии. Coverage-guided фаззинг - DynamoRIO интегрируется с AFL++ и WinAFL.

Задача - инструментация на уровне отдельных инструкций (подсчёт исполненных инструкций, профилирование, taint-анализ): Intel PIN. Его Pintool API заточен под instruction-level callbacks, а готовые инструменты экономят время на типовых задачах.

Задача - runtime анализ приложений на мобильных платформах (Android, iOS): только Frida. Ни PIN, ни DynamoRIO не поддерживают iOS, а Android-поддержка DynamoRIO экспериментальна.

Frida инструментация: перехват вызовов и обход антиотладки​

Требования к окружению​

  • ОС: Windows 10/11, Linux (Ubuntu 20.04+), macOS 12+
  • RAM: 4 ГБ минимум, 8 ГБ при анализе крупных бинарей
  • Зависимости: Python 3.8+, установка через pip install frida frida-tools
  • Права: для инъекции в чужой процесс - администратор (Windows) или root (Linux)
  • Режим: локальный, online для установки пакетов
Frida работает по модели «контрольный скрипт + инструментационный скрипт». Контрольный скрипт на Python подключается к целевому процессу через Frida API. Инструментационный скрипт на JavaScript инъецируется в адресное пространство цели и выполняется внутри неё. Коммуникация - через send()/recv().

Классический сценарий при анализе вредоносного ПО - обход антиотладочных проверок. Малварь вызывает IsDebuggerPresent (Debugger Evasion, T1622) и завершается при обнаружении отладчика. Frida-скрипт перехватывает вызов и подменяет результат:
JavaScript:
// Module.findExportByName deprecated в Frida 16+; используйте Module.getExportByName
Interceptor.attach(Module.getExportByName("kernel32.dll", "IsDebuggerPresent"), {
    onLeave: function(retval) {
        retval.replace(0);
        console.log("[*] IsDebuggerPresent patched -> 0");
    }
});
Шесть строк - и антиотладочная проверка нейтрализована для всех вызовов за весь runtime. В x64dbg пришлось бы вручную ставить breakpoint, менять EAX после каждого возврата и повторять на каждом вызове. Разница - минуты против часов ручной работы.

Frida-инструментация работает через механизм API hooking - тот же самый, который малварь использует для перехвата API (Hijack Execution Flow, T1574; Credential API Hooking, T1056.004). Разница в цели: аналитик перехватывает для наблюдения, малварь - для перехвата управления или кражи данных. Interceptor.attach модифицирует пролог целевой функции трамплином на callback - это и есть Dynamic Probe Injection.

Типичные задачи, которые Frida-инструментация решает за минуты: мониторинг обращений к CreateFile/WriteFile для анализа файловых операций, перехват CryptDecrypt для получения расшифрованных данных, подмена результатов GetTickCount/QueryPerformanceCounter для обхода timing-based антианализа (Virtualization/Sandbox Evasion, T1497). По данным Cisco Talos, DBI позволяет обнаруживать криптографические константы, скрытые в Mixed-Boolean-Arithmetic функциях и восстанавливаемые только в runtime - Frida-хук на точку после вычисления позволяет считать эти значения напрямую.

Ограничения Frida. Если малварь проверяет целостность пролога своих функций (сверяет первые байты с эталоном), трамплин будет обнаружен. Kernel-level антиотладка через NtQueryInformationProcess с ProcessDebugFlags требует более сложных скриптов. И главное - Frida не подходит для полной трассировки. Overhead Stalker.follow() на каждой инструкции значительно выше, чем у DynamoRIO при аналогичной задаче. Я видел, как люди пытаются натянуть Stalker на задачи полного покрытия - получается скрипт на 500 строк, который делает плохо то, что DynamoRIO-клиент на 30 строк C делает идеально.

DynamoRIO анализ: трассировка и покрытие кода​

Требования к окружению​

  • ОС: Windows 11 (рекомендовано Talos), Windows 10, Linux (Ubuntu 20.04+)
  • RAM: 8 ГБ минимум, 16 ГБ для анализа крупных семплов с полной трассировкой
  • Зависимости: Visual Studio 2019 (Windows), CMake, DynamoRIO 11.3.0
  • Установка: распаковать релиз в рабочую директорию, например C:\tools\DynamoRIO-Windows-11.3.0
  • Режим: локальный, offline-совместим после установки
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

PIN tool Intel: динамическое профилирование на уровне инструкций​

Intel PIN использует тот же JIT-DBT подход, что и DynamoRIO: базовые блоки копируются в code cache и модифицируются. Ключевое отличие - API заточен под instruction-level granularity. PIN позволяет зарегистрировать callback для каждой инструкции определённого типа (вызовы, memory-операции, ветвления), и API для этого компактнее.

Pintool - стандартная единица инструментации. Готовые pintools покрывают типовые задачи: inscount (подсчёт инструкций), pinatrace (трассировка memory-операций), itrace (полная трассировка). Для реверс-инженера, который не хочет писать собственный инструмент, готовые pintools - быстрый вход в инструментацию на уровне инструкций. Запустил - получил результат - пошёл дальше.

Когда PIN лучше альтернатив: тонкое профилирование конкретных типов инструкций, анализ паттернов доступа к памяти, построение трассировок для символьного исполнения. Если задача - подать трассировку в Triton или angr, PIN-формат часто подходит из коробки.

Ограничения PIN. Закрытый JIT-движок (pintools и API headers открыты, но ядро инструментации проприетарно) - если PIN неправильно обрабатывает экзотическую инструкцию (бывает с AVX-512 или специфичными расширениями), отладка превращается в гадание на кофейной гуще. Overhead выше, чем у DynamoRIO при аналогичных задачах. Нет поддержки iOS и macOS (поддержка macOS прекращена с PIN 3.7, 2019), ограниченная поддержка Android.

Место DBI в цепочке анализа вредоносного ПО​

DBI - не замена дизассемблеру или отладчику. Это инструмент конкретного этапа в аналитической цепочке.

Этап 1: первичная сортировка. Семпл попадает в автоматизированный sandbox - файловые операции, сетевые подключения, изменения реестра. DBI здесь не нужен.

Этап 2: статический анализ. IDA Pro / Ghidra → определяем упаковщик / обфускатор → строим предварительную картину. Если бинарь не упакован и читается - DBI может не понадобиться вовсе.

Этап 3: DBI вступает в игру. Семпл упакован (Obfuscated Files or Information, T1027), содержит антиотладочные проверки (T1622), проверяет наличие VM (T1497), использует runtime-деобфускацию строк (Deobfuscate/Decode Files or Information, T1140). DBI даёт наблюдение за поведением кода после распаковки без ручного взлома протектора.

Типичная цепочка для упакованного семпла:
  1. Frida-скрипт перехватывает VirtualAlloc/VirtualProtect, логирует все выделения с флагом PAGE_EXECUTE_READWRITE
  2. При обнаружении записи и последующего исполнения из выделенной области - дамп содержимого
  3. Дамп загружается в IDA Pro / Ghidra для статического анализа распакованного кода
  4. При необходимости - DynamoRIO-трассировка для построения полного CFG распакованного payload
Этап 4: глубокий анализ. Если семпл содержит сложную логику с множеством ветвлений - DynamoRIO строит трассировку реального пути исполнения, которая показывает код, статически недоступный из-за runtime-генерации. PIN-трассировка подаётся в символьный движок для поиска альтернативных путей.

Ограничения DBI-фреймворков и антиинструментация​

Ни один DBI-фреймворк не невидим. Малварь может обнаружить инструментацию несколькими способами.

Timing-based детект. Инструментированный код исполняется медленнее нативного. Малварь замеряет время через rdtsc или QueryPerformanceCounter, сравнивает с эталоном - overhead DBI выдаёт присутствие фреймворка. rdtsc исполняется нативно из code cache во всех трёх фреймворках - автоматической подмены нет. Подмена возможна через кастомный клиент, перехватывающий инструкцию (emulation event в DynamoRIO, INS_AddInstrumentFunction в PIN). Frida может перехватить API-вызовы, но rdtsc - инструкция процессора, и через Dynamic Probe Injection перехватить её нельзя.

Проверка целостности кода. Малварь вычисляет хеш собственных функций и сверяет с эталоном. JIT-DBT (DynamoRIO, PIN) здесь выигрывают: оригинальный код в памяти не модифицируется, исполнение идёт из code cache. Frida модифицирует прологи перехваченных функций трамплинами - это обнаруживается проверкой целостности. Если семпл считает CRC32 своего .text-сегмента при каждом вызове критической функции, Frida-хук спалится.

Артефакты в памяти. DynamoRIO и PIN создают дополнительные потоки, выделяют память для code cache, загружают свои DLL. Малварь, перечисляющая модули через PEB или потоки процесса, обнаружит артефакты. По данным исследования arxiv, прозрачность DBI остаётся нерешённой проблемой: авторы показывают, что ни одна техника не является универсально лучшей.

Kernel-level проверки. Все три фреймворка работают в user space. Малварь, использующая direct syscalls через Native API (T1106) в обход user-mode API, получает информацию о процессе минуя инструментацию. Тут DBI бессилен - нужны whole-system подходы.

Вектор детектаFridaDynamoRIOPIN
Timing (rdtsc)Не перехватываетВозможна через клиентВозможна через клиент
Целостность кодаОбнаруживаетсяНе обнаруживаетсяНе обнаруживается
Артефакты в памятиОбнаруживаемыОбнаруживаемыОбнаруживаемы
Direct syscallsНе перехватываетНе перехватываетНе перехватывает

Для семплов с серьёзной антиинструментацией ни один process-level фреймворк не даст полной прозрачности. Whole-system подходы (QEMU full-system emulation, PANDA) существуют, но overhead возрастает на порядок, и применимость ограничена глубоким исследованием, а не ежедневным анализом.

Мой подход к выбору DBI-стека за последний год устаканился: Frida - для быстрого triage и точечных хуков, DynamoRIO - для полной трассировки и фаззинга, PIN - когда нужна трассировка для символьного движка. Три фреймворка не конкурируют - они закрывают разные этапы одной цепочки.

Но вот что удивляет: большинство коллег по CTF используют максимум один DBI-фреймворк. Обычно Frida - порог входа низкий, JavaScript удобен. И натягивают её на задачи, для которых она не предназначена: полная трассировка через Stalker с overhead x50, ручное построение CFG из логов, попытки подсчитать coverage для фаззинга. Результат - скрипты на 500 строк, которые криво делают то, что DynamoRIO-клиент на 30 строк C делает чисто. Проблема не в инструментах - проблема в том, что DBI воспринимается как «один инструмент на все задачи». Порог входа в DynamoRIO и PIN выше, но инвестиция окупается на первом же упакованном семпле с серьёзной антиотладкой. Если вы анализируете бинари чаще раза в месяц - освоить все три не опционально. Путать шлицевую отвёртку с крестовой - терять часы анализа. На WAPT эту цепочку «Frida-хук → дамп → DynamoRIO-трассировка» проходят в течение двух модулей с лабами.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab