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


На разборе инцидента в финансовой организации команда получила PE-файл размером 847 КБ: ни одного экспортируемого символа, энтропия секции .text - 7.92 из 8, IAT содержит ровно две записи - LoadLibraryA и GetProcAddress. Ghidra на автоанализе выдал один гигантский блок с арифметическими операциями вместо осмысленного кода, а IDA Pro показала jmp на вычисляемый адрес в первом же базовом блоке. Дизассемблер и декомпилятор не врали - они честно показали то, что лежало в файле. Реальный код ещё не существовал: полезная нагрузка распаковывалась в рантайме через цепочку из четырёх техник по классификации MITRE ATT&CK. Именно такие бинарники составляют основу работы malware-аналитика, и на них подход «загрузить в дизассемблер и почитать» ломается за первую минуту.

Эта статья - системный workflow reverse engineering бинарного кода, построенный вокруг реальных антианализных техник: от триажа до восстановления C2-логики из распакованного payload. Не «ещё один гайд по Ghidra», а методология, привязанная к конкретным MITRE-идентификаторам.

Триаж бинарных файлов: первые пять минут формируют стратегию​

Анализ бинарных файлов начинается не с дизассемблера, а с триажа - быстрой классификации, которая определяет дальнейший workflow. За первые пять минут нужно ответить на три вопроса: что за формат, упакован ли семпл, какие антианализные техники предположительно применены. Если ошибиться здесь - потеряешь час, ковыряя stub-код упаковщика вместо реального payload.

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

  • ОС: Windows 10/11 в изолированной ВМ (VirtualBox 7.x / VMware Workstation), сеть отключена или host-only adapter
  • RAM: минимум 8 ГБ для ВМ (Ghidra + x64dbg одновременно съедают 4-6 ГБ)
  • Диск: 60 ГБ минимум (Ghidra-проекты разрастаются быстро)
  • Инструменты триажа: Detect-It-Easy (DiE) 3.x, PE-bear 0.6+, radare2 6.1.5 (rabin2), file/readelf для ELF
  • Обязательно: снапшот «чистого» состояния ВМ до начала работы с семплом - откат за 30 секунд вместо переустановки. Не пренебрегайте этим, один раз я потерял полдня из-за криво отработавшего семпла, который покорёжил системные библиотеки

Decision tree: выбор подхода к анализу исполняемых файлов​

Выбор между статическим и динамическим анализом - не вопрос предпочтений, а следствие результатов триажа:

УсловиеПодходИнструмент первого выбора
Энтропия .text < 6.5, IAT > 20 записей, пакер не обнаруженСтатический анализGhidra / IDA Pro
Энтропия > 7.0, IAT пустой или минимальный (1-5 записей), DiE определяет UPX/Themida/VMProtectДинамический - распаковка - статическийx64dbg, затем Ghidra для дампа
PE с CLR-заголовком (.NET), обфускатор не обнаруженСтатический анализ ILdnSpy / ILSpy
.NET + ConfuserEx / Reactor / другой обфускаторДеобфускация, затем статическийde4dot, затем dnSpy
ELF stripped, нет символов, не упакованСтатический с ручной аннотациейGhidra + radare2 для скриптового анализа
Любой формат с антиотладочными проверкамиДинамический с обходом антиотладкиx64dbg + ScyllaHide

Первый шаг триажа - определение формата и упаковки. Команда rabin2 -I <file> из набора radare2 выдаёт тип файла, архитектуру, эндианность и энтропию за секунду. DiE (Detect-It-Easy) дополняет сигнатурным определением компилятора и пакера - для PE-файлов DiE распознаёт сотни сигнатур упаковщиков и протекторов.

Ключевой индикатор - импортная таблица. Если IAT содержит только LoadLibraryA, GetProcAddress и, может быть, VirtualAlloc - перед вами с высокой вероятностью Dynamic API Resolution (T1027.007, Defense Evasion). Реальные API-вызовы скрыты и будут резолвиться в рантайме через хеширование имён функций. Статический анализ в чистом виде здесь бесполезен: декомпилятор покажет вызовы через вычисляемые указатели без имён, и вы будете смотреть на call [eax] до посинения.

Второй индикатор - секции. Стандартный PE, скомпилированный MSVC, содержит .text, .rdata, .data, .rsrc. Секции с именами UPX0, .vmp0, .themida или произвольными строками - признак Software Packing (T1027.002, Defense Evasion). Упаковщик сжимает или шифрует оригинальный код, а при запуске stub-код распаковывает его в память.

[Применимо: триаж универсален для IR, threat hunting, malware research и CTF-категории RE. Контекст значения не имеет - это отправная точка любого анализа.]

Инструменты реверс инжиниринга: когда декомпилятор врёт​

Русскоязычные гайды сводят выбор инструментов к «Ghidra бесплатный, IDA платный». Это упрощение скрывает архитектурные различия, которые определяют качество анализа в конкретных сценариях. На практике аналитик переключается между несколькими инструментами - и это нормально, не нужно выбирать «один на всю жизнь».

Сравнение по архитектурным критериям​

КритерийIDA Pro 9.x (2024)Ghidra 11.x (2024)radare2 6.1.5 (активная разработка)Binary Ninja 4.x (2024)
Декомпилятор x86/x64Hex-Rays - лучшая точность на оптимизированном кодеВстроенный, бесплатный, корректен в ~85% случаевЧерез r2ghidra-плагин (движок Ghidra)Встроенный HLIL/MLIL, альтернативный подход через уровни IL
Обфусцированный кодgooMBA - деобфускация MBA-выражений из коробкиТребует кастомных скриптовr2pipe для автоматизацииЧастично через SSA-форму
Скриптовый APIIDAPython (зрелая среда, сотни плагинов)Ghidra Script (Java + Jython)r2pipe (Python/JS/Go) + встроенная командная оболочкаPython API с типизацией
Встроенная отладкаДа (win/linux/mac/embedded)НетДа (CLI через r2 debug)Нет
FLIRT/FID (идентификация библиотек)FLIRT - зрелая технология с большой базой сигнатурFunction ID - аналог, но база меньшеЧерез сигнатуры zignaturesЧерез signature libraries
Цена (Named license)от $1975/годБесплатно (Apache 2.0)Бесплатно (LGPL3)от $299 (personal)

[Применимо: для single-sample IR или CTF - Ghidra хватит за глаза. Для ежедневной работы с обфусцированными семплами в коммерческом SOC/DFIR - IDA Pro окупается за первый месяц. radare2 - для CLI-автоматизации и скриптовых пайплайнов, когда нужно прогнать 50 семплов за ночь.]

Конкретные сценарии, где декомпилятор генерирует некорректный псевдокод​

Непрямые вызовы через вычисляемые адреса. При Dynamic API Resolution (T1027.007) декомпилятор видит call [eax], но понятия не имеет, какая функция вызывается. Результат: ([I](void ([/I]*)(void))local_18)() вместо осмысленного CreateProcessW(...). Решение: в x64dbg дойти до вызова, зафиксировать реальный адрес, затем в Ghidra/IDA вручную задать тип функции через «Edit Function Signature». Муторно, но другого пути нет.

Самомодифицирующийся код. Если код перезаписывает себя в рантайме - типовой приём в распаковщиках - статический декомпилятор анализирует stub, а не payload. Единственный выход: дать коду отработать распаковку в отладчике, сдампить память, загрузить дамп заново. Пересказывать спеку дизассемблера в сотый раз не вижу смысла - он тут бессилен.

Оптимизация tail calls. Компиляторы с -O2 и выше заменяют call + ret на jmp, что ломает boundary detection. Ghidra может объединить две функции в одну или «потерять» хвостовой вызов. IDA Pro обрабатывает это точнее за счёт эвристик Hex-Rays, но тоже не безупречна. Workaround: вручную определить границу функции (P в IDA, «Create Function» в Ghidra).

Control flow flattening. Превращает граф потока управления в один switch-case с диспетчером. Декомпилятор честно показывает switch - но восстановить исходную логику без скриптовой деобфускации практически невозможно. IDA Pro поставляется с плагином gooMBA для MBA-выражений; у Ghidra из коробки аналога нет, придётся писать свой или искать на GitHub.

Статический анализ вредоносного ПО: упаковка, стриппинг и скрытие API

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

Software Packing (T1027.002, Defense Evasion)​

Упаковка - самая распространённая техника сокрытия payload. Семпл содержит stub-код (распаковщик) и зашифрованные или сжатые данные. При запуске stub выделяет память через VirtualAlloc, расшифровывает payload, копирует его в выделенную область и передаёт управление. Классическая матрёшка.

Идентификация при триаже: энтропия > 7.0, минимальный IAT, нестандартные имена секций. DiE определяет известные пакеры по сигнатуре - UPX, ASPack, Themida, VMProtect. Кастомные пакеры (а в APT-малвари они почти все кастомные) сигнатурно не детектируются - нужен ручной анализ stub-кода.

Для UPX достаточно upx -d <file>, но авторы малвари часто модифицируют UPX-заголовок (меняют магические байты UPX!), чтобы штатная распаковка не сработала. В этом случае: восстановить заголовок вручную в HxD или распаковывать динамически через x64dbg. Второй вариант надёжнее - он работает для любого пакера.

[Ограничения: VMProtect и Themida используют виртуализацию опкодов - реальные инструкции заменены байткодом кастомной VM. Статическая распаковка неэффективна. Подход: только динамический анализ с дампом после полной распаковки. EDR-решения (CrowdStrike Falcon, SentinelOne) флагают аллокацию RWX-памяти как подозрительную, но это не блокирует распаковку - только сигнализирует аналитику.]

Stripped Payloads (T1027.008, Defense Evasion)​

Stripped-бинарники - файлы, из которых удалена отладочная информация и таблица символов. Для ELF - strip -s, для PE - компиляция без PDB. В Ghidra вместо осмысленных имён: FUN_00401230, FUN_004013a0. Тоска.

Стратегия: восстановление сигнатур стандартных библиотек. IDA Pro использует FLIRT (Fast Library Identification and Recognition Technology) - сравнивает байтовые паттерны с базой сигнатур известных компиляторов и runtime-библиотек. Ghidra предлагает Function ID (FID) с аналогичным принципом, но база поменьше.

После FLIRT/FID-идентификации стандартных функций остаются только авторские. Их именование - ручная работа: анализ аргументов, возвращаемых значений, вызываемых API. Это самая трудоёмкая часть исследования вредоносных программ, и ускорить её можно только опытом - со временем начинаешь узнавать характерные паттерны: вот это похоже на инициализацию сокета, а вот это - на обход каталогов.

Dynamic API Resolution (T1027.007, Defense Evasion)​

Вместо явного импорта CreateProcessW малварь в рантайме вычисляет хеш от имени функции, перебирает экспортную таблицу DLL и находит совпадение. В IAT - только LoadLibraryA/GetProcAddress или вообще ничего (при ручном парсинге PEB → LDR → InMemoryOrderModuleList).

Распространённые хеш-алгоритмы: ROR13 (rotate right 13, классика Metasploit shellcode), DJB2, CRC32, MurmurHash. В дизассемблере паттерн выглядит как цикл, внутри которого movzx читает байт за байтом, затем ror/xor/add, и на выходе cmp с 32-битной константой. Эта константа - хеш искомой API-функции. Узнаёте паттерн один раз - потом видите его везде.

Практический подход к обратной разработке программ с Dynamic API Resolution: опознать алгоритм хеширования, написать скрипт на Python, который вычисляет хеши всех функций из kernel32.dll, ntdll.dll, user32.dll, построить lookup-таблицу, затем через Ghidra Script или IDAPython автоматически заменить константы на комментарии с именами API. Один раз написал скрипт - используешь на десятках семплов.

Deobfuscate/Decode Files or Information (T1140, Defense Evasion)​

Данные - строки, конфигурация C2, дополнительные модули - хранятся в зашифрованном виде и расшифровываются в рантайме. Типичные алгоритмы: XOR с однобайтовым или многобайтовым ключом (самый частый случай), RC4, base64 + XOR в комбинации.

В статическом анализе XOR-шифрование обнаруживается по паттерну: загрузка ключа в регистр, цикл по буферу с xor byte [esi+ecx], al, инкремент счётчика. Для автоматической расшифровки - идентифицируем функцию дешифровки (её вызывают многократно, перед каждым использованием строки), извлекаем алгоритм и ключ, пишем скрипт для bulk-расшифровки. Скриптовая расшифровка строк - один из финальных навыков в reverse engineering, потому что она требует полного понимания calling convention и layout памяти. Но когда скрипт заработает и выплюнет все C2-адреса разом - это того стоит.

Динамический анализ бинарников: обход антиотладки и трассировка кода​

Динамический анализ - запуск семпла в контролируемой среде с отладчиком. Плюс: видите код после распаковки и расшифровки, ground truth вместо догадок. Риск: малварь активно противодействует отладке и трассировке кода, и порой делает это изобретательно.

Debugger Evasion (T1622, Defense Evasion / Discovery)​

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

IsDebuggerPresent - читает флаг BeingDebugged из PEB (Process Environment Block). В дизассемблере: call IsDebuggerPresenttest eax, eaxjnz <exit_branch>. Обход в x64dbg: breakpoint на IsDebuggerPresent, при срабатывании - изменить EAX на 0 в панели регистров. Или проще: плагин ScyllaHide (активно поддерживается), который патчит PEB автоматически.

NtQueryInformationProcess с ProcessDebugPort (class 0x7) - проверка через ntdll, более надёжная, чем IsDebuggerPresent. В дизассемблере: push 7 перед вызовом NtQueryInformationProcess. ScyllaHide перехватывает этот вызов и подменяет результат.

Timing checks - замер времени через rdtsc, QueryPerformanceCounter или GetTickCount. Отладчик замедляет выполнение, и если дельта между двумя замерами превышает порог - малварь считает, что её отлаживают. Обход сложнее: патчить проверку (заменить условный jnz на безусловный jmp или nop), либо использовать hardware breakpoints вместо программных (они не вносят int 3 в код и менее детектируемы). Но продвинутые пакеры проверяют DR-регистры через GetThreadContext - ScyllaHide с DRx protection блокирует и эту проверку. Гонка вооружений, как обычно.

[Применимо: антиотладка встречается в реальной малвари всех классов - от массовых стилеров до APT-загрузчиков. В CTF-задачах RE антиотладка - стандартный элемент средней и высокой сложности.]

System Checks (T1497.001, Defense Evasion / Discovery)​

Обнаружение виртуальных машин и песочниц. Цель: определить, что код работает не на реальной машине жертвы, а в аналитической среде.

Типичные проверки:
  • cpuid с eax=1, бит 31 ecx - Hypervisor present (бит выставляется гипервизором по соглашению; VMware/VirtualBox/Hyper-V его устанавливают, но продвинутые конфигурации, например KVM с cpu-pt, могут его очистить)
  • MAC-адреса: префиксы VMware (00:0C:29, 00:50:56), VirtualBox (08:00:27)
  • Процессы: поиск vmtoolsd.exe, VBoxService.exe, wireshark.exe, procmon.exe
  • Реестр: HKLM\SOFTWARE\VMware, Inc., HKLM\SOFTWARE\Oracle\VirtualBox
  • Файловая система: C:\Windows\System32\drivers\vmmouse.sys
Обход при анализе: переименовать процессы VMware Tools или удалить Guest Additions, подменить MAC-адрес, спуфить ключи реестра. Продвинутый вариант - KVM/QEMU с <hidden state='on'/> в конфигурации libvirt, что скрывает большинство гипервизорных артефактов. На практике я предпочитаю именно KVM - возни больше на этапе настройки, зато потом не нужно думать о каждой проверке отдельно.

[Ограничения: продвинутые sandbox-evasion техники (проверка количества ядер CPU < 2, RAM < 4 ГБ, пустая история документов, отсутствие принтеров) обходятся только настройкой «живой» ВМ с пользовательской активностью. Придётся создать иллюзию обитаемой машины - документы, история браузера, пара принтеров.]

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

Обратная разработка программ, использующих code injection, требует понимания цепочки API-вызовов и их аргументов в контексте полной kill chain - от allocation до execution. Тут нельзя смотреть на отдельный вызов - нужна вся последовательность.

Process Hollowing (T1055.012, Defense Evasion / Privilege Escalation)​

Создание легитимного процесса в suspended-состоянии, замена его образа в памяти на вредоносный, возобновление выполнения. Результат: малварь работает под именем svchost.exe или explorer.exe. Красивый фантик - внутри совсем другое.

Цепочка API-вызовов для поиска в дизассемблере:
Код:
CreateProcessW(..., CREATE_SUSPENDED, ...)   // создание suspended-процесса
NtUnmapViewOfSection(hProcess, imageBase)    // выгрузка легитимного образа
VirtualAllocEx(hProcess, imageBase, ...)     // выделение памяти под наш PE
WriteProcessMemory(hProcess, imageBase, ...) // запись вредоносного PE
SetThreadContext(hThread, &ctx)              // установка нового entry point
ResumeThread(hThread)                        // поехали
В Ghidra: Symbol Tree → Imports → kernel32.dll. Совместный импорт CreateProcessW и NtUnmapViewOfSection - сильный индикатор. Если API резолвится динамически (T1027.007) - сначала восстановите имена через хеш-таблицу, иначе будете смотреть на call [eax] и гадать.

В x64dbg: breakpoint на WriteProcessMemory - в момент вызова в аргументах будет указатель на записываемые данные. Это и есть распакованный payload, который можно сдампить через плагин Scylla.

CrowdStrike Falcon и Microsoft Defender for Endpoint (MDE) детектируют Process Hollowing через kernel callbacks и ETW-TI (Event Tracing for Windows - Threat Intelligence). Hook-first EDR перехватывают NtUnmapViewOfSection на user-mode уровне. Знание этого помогает при threat hunting - аналитик SOC может построить правило на последовательность CreateProcess(SUSPENDED)NtUnmapViewOfSectionWriteProcessMemory.

Native API (T1106, Execution)​

Вызов функций ntdll.dll напрямую вместо kernel32.dll - способ обойти мониторинг user-mode хуков. EDR-решения с hook-first архитектурой перехватывают CreateProcessW в kernel32, но не обязательно NtCreateUserProcess в ntdll. Малварь просто «проскальзывает» уровнем ниже.

В дизассемблере: прямые вызовы Nt[I]/Zw[/I] функций - NtAllocateVirtualMemory, NtWriteVirtualMemory, NtCreateThreadEx. Если малварь не импортирует их явно - резолвит через парсинг EAT (Export Address Table) ntdll.dll, что возвращает нас к T1027.007.

В последних семплах встречается ещё более агрессивный подход: direct syscalls - копирование syscall-stub из ntdll и вызов syscall напрямую из памяти малвари. Это обходит любые user-mode хуки, но детектируется через kernel-level телеметрию. Elastic EDR 8.x+ с Elastic Defend отслеживает аномальные syscall-источники (call stack не из ntdll), SentinelOne детектирует исполняемый код из unbacked memory regions. Гонка продолжается - но kernel-level телеметрия пока выигрывает.

[Применимо: понимание Native API критично для threat hunting и IR. При пентесте - знание детектируемости помогает выбирать evasion-вектор под конкретный EDR-стек целевой инфраструктуры.]

Пошаговый workflow: от упакованного семпла до C2-конфигурации​

Объединим описанные техники в практическую последовательность. Сценарий: получен PE-файл, подозрение на загрузчик, задача - извлечь адреса C2.
🔓 Часть контента скрыта: Эксклюзивный контент для зарегистрированных пользователей.


За последние два года я наблюдаю устойчивый сдвиг: авторы малвари всё реже используют «классическую» упаковку (UPX, ASPack) и всё чаще применяют кастомные загрузчики с многоступенчатой распаковкой - shellcode → reflective DLL → final payload. Каждая ступень задействует собственный набор антианализных проверок. Подход «найти OEP и сдампить» перестаёт работать, потому что OEP как единой точки не существует: код собирается из разных аллокаций по частям, как конструктор.

Из этого следует неудобный вывод: инструменты реверс инжиниринга менее важны, чем методология. Ghidra или IDA - второстепенный выбор по сравнению с умением правильно расставить breakpoints на memory allocation и отследить цепочку VirtualAlloc → WriteProcessMemory → execution. Аналитик, который распознаёт паттерны MITRE ATT&CK на уровне дизассемблированного кода, работает быстрее того, кто знает все горячие клавиши IDA, но не видит T1027.007 в потоке инструкций.

Этот разрыв стоит закрывать - и начинать имеет смысл не с очередного туториала по Ghidra, а с построения workflow вокруг конкретных антианализных техник. Попробуйте взять любой семпл с MalwareBazaar, прогнать через описанный pipeline и посмотреть, на каком шаге застрянете. Именно это место и нужно прокачивать. На WAPT эту цепочку проходят в течение двух модулей с лабами.
 
Мы в соцсетях:

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

Похожие темы

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

HackerLab