Три уровня защиты в одном бинаре: TLS callback с
IsDebuggerPresent, CPUID-запрос на наличие гипервизора, кастомный XOR с динамическим ключом поверх. Стандартная картина для категории reverse на средних и крупных CTF. Ручной разбор каждого слоя в Ghidra - от получаса. angr-скрипт на 15 строк выдаёт флаг за секунды. Разница между «решил два таска за восемь часов» и «закрыл шесть за то же время» определяется не талантом, а пониманием паттернов anti-analysis и умением автоматизировать рутину.Здесь - конкретные техники, которые авторы CTF-заданий тащат из реального malware, и инструменты для обхода каждой из них.
Место reverse/malware-заданий в цепочке CTF-атаки
Перед тем как препарировать конкретные приёмы, зафиксируем операционный контекст: где reversing сидит в «kill chain» CTF-соревнования и как навыки переносятся в реальный malware analysis.Типичная цепочка решения reverse-таска на Jeopardy CTF:
- Триаж -
file ./binary,strings,checksec. Определяем архитектуру (x86/x64/ARM), тип линковки, наличие символов. 30 секунд работы, но экономит минуты дальше. - Статический анализ - загрузка в Ghidra или IDA, поиск
main, анализ Control Flow Graph, идентификация anti-analysis барьеров и шифрования. - Динамический анализ - x64dbg для Windows, GDB + pwndbg для Linux. Breakpoints на ключевые проверки, обход anti-debug.
- Автоматизация - angr для символьного выполнения, Frida для перехвата и патчинга. Включается, когда ручной анализ жрёт слишком много времени.
- Извлечение флага - расшифровка, декодирование, получение строки формата
flag{...}.
Техники из статьи работают в CTF-заданиях от новичковых (picoCTF, HTB Easy) до финалов крупных соревнований. В реальном malware analysis - на загрузчиках (droppers), инфостилерах и ransomware. Разница в масштабе, не в подходе.
Anti-debug техники в CTF reverse engineering заданиях
Debugger Evasion (T1622, Defense Evasion / Discovery) - первый барьер, который выстраивают авторы CTF-заданий. Примечание: в каталоге MITRE ATT&CK техника T1622 описана преимущественно для Windows; для Linux аналогичные приёмы (ptrace-проверки, /proc/self/status) формально относятся к другим техникам. Цель: заставить бинарь вести себя иначе под отладчиком - выдавать фальшивый результат, зацикливаться или молча завершаться.Windows API: IsDebuggerPresent и NtQueryInformationProcess
Самая частая проверка на easy-medium уровне - вызовIsDebuggerPresent() через Windows Native API (T1106, Execution; техника T1106 в каталоге MITRE описана для Windows, macOS и Linux, но этот пример - только Windows). Функция читает поле BeingDebugged из PEB (Process Environment Block). В дизассемблере выглядит как вызов kernel32!IsDebuggerPresent, за которым следует test eax, eax / jnz exit_or_fake_flag. Ghidra декомпилирует это в if (IsDebuggerPresent() != 0) { goto fail; }.Вариант посерьёзнее -
NtQueryInformationProcess с классами ProcessDebugPort (0x7) и ProcessDebugObjectHandle (0x1E). Эти проверки идут через syscall, и простым патчем PEB их не обойти.Обход в x64dbg: breakpoint на
IsDebuggerPresent, после срабатывания - EAX = 0. Универсальнее - плагин ScyllaHide (GitHub: x64dbg/ScyllaHide, проект активен), который автоматически патчит PEB и перехватывает NtQueryInformationProcess.ScyllaHide покрывает большинство стандартных Windows anti-debug приёмов, но бессилен против кастомных timing checks на
RDTSC в inline assembly - для них нужен Frida или ручной патч.Timing checks и TLS callbacks
Timing checks - измерение времени между двумя точками выполнения. Под отладчиком single-step занимает на порядки больше. Реализуется через инструкциюRDTSC (Read Time-Stamp Counter), вызовы QueryPerformanceCounter или GetTickCount. Дельта выше порога - бинарь решает, что его отлаживают.TLS callbacks - функции, которые исполняются до
main(). Авторы CTF суют туда anti-debug проверки: к моменту, когда реверсер ставит breakpoint на main, данные уже испорчены. В IDA/Ghidra TLS callbacks видны в секции .tls или через PE-заголовок (IMAGE_TLS_DIRECTORY).Обход timing checks: в x64dbg - Hardware Breakpoint на адрес
RDTSC и подмена значения TSC в регистрах. Через Frida - перехват QueryPerformanceCounter с возвратом фиксированного значения.Обход TLS callbacks: x64dbg: Options → Events → галочка на TLS Callbacks. Breakpoint на callback, NOP-инг anti-debug кода. Для Linux ELF аналогичную роль играют
.init_array и [B]attribute[/B]((constructor)) (эти приёмы в MITRE ATT&CK не покрываются T1622 - она описана только для Windows).Decision tree обхода anti-debug
| Что видим в дизассемблере | Техника (MITRE) | Обход |
|---|---|---|
Вызов IsDebuggerPresent | PEB flag (T1622) | EAX = 0 или ScyllaHide |
NtQueryInformationProcess с классом 0x7/0x1E | Kernel-level debug check (T1622) | ScyllaHide или Frida hook на ntdll |
RDTSC / QueryPerformanceCounter между блоками | Timing check (T1622) | Hardware BP + подмена или Frida hook |
Код в .tls / .init_array до main | TLS/init callback (T1622) | Break on TLS + NOP |
int 2Dh / OutputDebugString | Exception-based (T1622) | SEH analysis + NOP |
Эти техники встречаются в CTF категорий reverse и malware на платформах HTB, CTFtime, picoCTF. В боевом malware analysis тот же набор используют инфостилеры и загрузчики первого этапа. Продвинутый malware (APT-уровень) уходит глубже - прямые syscalls,
NtSetInformationThread(ThreadHideFromDebugger) - но в CTF на уровне medium это редкость.Anti-VM обход в CTF: System Checks и их нейтрализация
Virtualization/Sandbox Evasion (T1497) и конкретно System Checks (T1497.001) в CTF встречаются реже anti-debug, но на medium-hard уровне - регулярно. Бинарь проверяет, запущен ли он в виртуальной машине, и меняет поведение.Типичные проверки:
- CPUID с EAX=1 - бит 31 регистра ECX (hypervisor present bit). Установлен - гипервизор обнаружен.
- CPUID с EAX=0x40000000 (доступен при установленном hypervisor present bit CPUID.1:ECX[31]) - возвращает 12-байтовый vendor ID гипервизора:
VMwareVMware,Microsoft Hv,KVMKVMKVM. - Реестр Windows - ключи
HKLM\SOFTWARE\VMware, Inc.\VMware Tools,HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions. - MAC-адрес - OUI:
00:05:69,00:0C:29,00:1C:14,00:50:56(VMware, последний - manually assigned/ESXi),08:00:27(VirtualBox),00:15:5D(Hyper-V). - Имена процессов -
vmtoolsd.exe,VBoxService.exe. - RDTSC delta - в VM выполняется медленнее из-за VM-exit.
Если задание проверяет гипервизор через CPUID - проще запустить бинарь на bare metal. Нет такой возможности - в VirtualBox:
--paravirtprovider none скрывает только paravirt-интерфейс (KVM/Hyper-V leaves), но hypervisor present bit (CPUID.1:ECX[31]) остаётся; для его скрытия нужна маскировка CPUID через VBoxManage setextradata "VM" "VBoxInternal/CPUM/HostCPUID/Standard/0x00000001/ecx" <значение с очищенным битом 31>. Для VMware: hypervisor.cpuid.v0 = FALSE в VMX - этот флаг действительно скрывает hypervisor bit.| Проверка | Что ищет | Обход в CTF |
|---|---|---|
| CPUID hypervisor bit | Бит 31 ECX при EAX=1 | Bare metal или CPUID-маскировка (VBoxManage setextradata HostCPUID leaf / VMware: hypervisor.cpuid.v0=FALSE) |
| Registry keys | VMware Tools, VBox GA | Frida hook на RegOpenKeyEx |
| MAC OUI | 00:05:69, 00:0C:29, 00:1C:14, 00:50:56, 08:00:27 | Смена MAC в настройках VM |
| Process names | vmtoolsd, VBoxService | Остановить сервисы перед запуском |
| RDTSC delta | Замедление в VM | Frida hook + фиксированный return |
Современные песочницы (Cuckoo, Any.Run, Triage) скрывают большинство артефактов гипервизора. Продвинутый malware (APT-группы) комбинирует десятки проверок - отдельная тема, выходящая за рамки CTF.
Кастомные шифры в реверс инжиниринге CTF
Шифрование флага - ядро большинства reverse-заданий. Авторы используют Obfuscated Files or Information (T1027) для сокрытия строки, а задача реверсера - Deobfuscate/Decode Files or Information (T1140).Типовые паттерны и как их распознать
XOR с фиксированным ключом (easy). Флаг XOR-ится одно- или многобайтовым ключом. Распознаётся по паттернуxor reg, imm8 в цикле. Однобайтовый ключ - brute force 256 вариантов, Python-скрипт справляется за секунды.XOR с динамическим ключом (medium). Ключ генерируется в рантайме - хэш от имени процесса, результат системного вызова, производное от другой переменной. Статический анализ тут не вывозит - нужен angr или Frida.
RC4 (medium-hard). Распознаётся по инициализации S-box: массив 256 байт, два вложенных цикла. Первый - заполнение значениями 0–255, второй - перемешивание с ключом (KSA). Видите такую структуру в Ghidra - скорее всего RC4, но проверьте через Findcrypt.
TEA/XTEA (hard). Feistel-сеть с характерной константой
0x9E3779B9 (golden ratio). Если эта константа торчит в бинаре - практически однозначный маркер.Кастомные конструкции - авторы CTF комбинируют примитивы:
((flag[i] ^ key) + offset) % 256, вложенные циклы, lookup-таблицы. Готового алгоритма нет - реверсить полностью.Быстрая идентификация алгоритма
- Искать характерные константы -
0x9E3779B9(TEA),0x61707865(ChaCha/Salsa), статический массив 256 элементов (RC4, AES S-box). - Использовать Findcrypt - плагин для IDA и Ghidra, автоматически определяет криптографические константы в бинаре.
- Искать циклы с XOR -
for+xorв теле = почти наверняка шифрование или обфускация. - Обратить внимание на размер блока - операции над 8-байтовыми блоками = блочный шифр (DES, TEA), побайтовая обработка = потоковый (RC4, XOR).
Angr: символьное выполнение для CTF reverse engineering
angr (версия 9.2.208, GitHub: angr/angr, 7000+ звёзд, активно поддерживается) - фреймворк символьного выполнения. Вместо конкретных значений переменных использует символические, исследует все пути выполнения и находит ввод, который приводит к нужному состоянию - адресу сputs("Correct!").Когда angr решает задачу мгновенно
angr идеален для CTF-заданий с линейной проверкой ввода (посимвольное сравнение), множеством условных переходов и кастомными шифрами, где нужен ключ по известному выходу.Типичный скрипт (пример для демонстрации концепции):
Python:
import angr
proj = angr.Project('./crackme', auto_load_libs=False)
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)
# find= адрес "Correct!", avoid= адрес "Wrong!"
simgr.explore(find=0x401234, avoid=0x401250)
if simgr.found:
print(simgr.found[0].posix.dumps(0))
auto_load_libs=False не загружает библиотеки - ускоряет анализ. Адреса find и avoid берутся из Ghidra: find - адрес строки «Correct», avoid - адрес «Wrong» или exit(). На простых CrackMe (100–200 строк кода) результат появляется за 5–40 секунд.Когда angr не работает
Path explosion - бинарь с глубокими циклами, хэш-функциями (SHA256, MD5), криптографическими операциями. Количество путей растёт экспоненциально, angr зависает или падает по памяти. Типичный признак: RAM растёт до 8+ ГБ без результата.Тяжёлое взаимодействие с ОС - файловые операции, сеть, GUI. Нужна ручная настройка SimProcedures - замена системных вызовов заглушками.
Anti-angr задания - opaque predicates, хэш-проверки, VM-based обфускация. Встречается на hard-уровне крупных CTF.
Практическое правило: если angr не выдал результат за 2–3 минуты - переключайтесь на Frida или ручной анализ. Ковыряние с constraints для сложных случаев отнимает больше времени, чем динамический подход.
Frida для динамического анализа малвари в CTF
Frida (GitHub: frida/frida, 14000+ звёзд, активно поддерживается) - фреймворк Dynamic Binary Instrumentation. Инъектирует JavaScript-код в работающий процесс: перехват вызовов функций, чтение и модификация памяти, патч кода в рантайме. Установка:pip install frida frida-tools.Перехват крипто-функций и строковых сравнений
Если бинарь шифрует флаг и сравнивает с эталоном - перехват момента сравнения выдаёт обе строки. Frida-скрипт для перехватаstrcmp (пример для демонстрации концепции):
JavaScript:
Interceptor.attach(Module.getExportByName(null, "strcmp"), {
onEnter: function(args) {
console.log("strcmp: " +
args[0].readUtf8String() + " vs " +
args[1].readUtf8String());
}
});
frida -l hook.js -f ./crackme. Одна из строк, переданных в strcmp, может быть расшифрованным флагом или эталоном для сравнения. Аналогично перехватываются memcmp, strncmp, bcrypt и любые пользовательские функции сравнения по адресу.Патчинг anti-debug и anti-VM в рантайме
Вместо статического патча бинаря (который может проверять собственную целостность через CRC) Frida меняет поведение в памяти:
JavaScript:
// Работает только если бинарь вызывает IsDebuggerPresent через IAT.
// Если MSVC инлайнит чтение PEB (mov eax, fs:[30h]; movzx eax, [eax+2]),
// hook на экспорт не сработает - нужно патчить PEB напрямую
// или ставить Interceptor.attach на конкретный адрес инструкции.
Interceptor.replace(
Module.getExportByName("kernel32.dll", "IsDebuggerPresent"),
new NativeCallback(function() { return 0; }, 'int', [])
);
NtQueryInformationProcess, CheckRemoteDebuggerPresent, GetTickCount для обхода timing checks, RegOpenKeyExA для обхода anti-VM проверок реестра.Если бинарь слинкован статически,
Module.getExportByName не найдёт функцию. Определяйте адрес через Ghidra и используйте Interceptor.attach(ptr("0xADDRESS"), ...).Для CTF обнаружение Frida несущественно, но в реальном malware analysis - учитывайте: инъекция создаёт модуль
frida-agent в списке загруженных библиотек. Продвинутый malware палит Frida через перечисление модулей или имён потоков. Альтернативы - Pin, DynamoRIO или кастомные сборки Frida с переименованными модулями.Обфускация кода и пакеры в CTF-заданиях
Помимо anti-debug, anti-VM и шифрования, авторы CTF используют обфускацию кода (T1027) и пакинг (Software Packing, T1027.002).UPX - самый частый пакер в CTF. Распаковка:
upx -d packed_binary. Если заголовок UPX модифицирован (изменены magic bytes) - ручная распаковка: в x64dbg hardware breakpoint на запись в секцию .text, ждём завершения распаковки, дамп через Scylla. На Linux - аналогичный подход через GDB.Opaque predicates - условные переходы с предопределённым результатом, которые статический анализ не может разрешить. Пример:
if ((x * x + x) % 2 == 0) - всегда true, но Ghidra покажет два пути. Засоряют CFG, мешают angr. Обход: NOP ненужной ветки в IDA или параметр avoid в angr для мёртвых путей.Control Flow Flattening - все базовые блоки перемещаются в один switch-case, порядок определяется переменной-диспетчером. Встречается на hard-уровне. Для CTF часто проще пустить Frida для трассировки реального порядка исполнения (
Stalker API), чем восстанавливать CFG вручную.VM-based обфускация - бинарь содержит собственную виртуальную машину с кастомным байткодом. Самый злой тип заданий. Подход: реверсить диспетчер (fetch-decode-execute loop), восстановить набор инструкций, написать дизассемблер для кастомного байткода. На крупных CTF (CTFtime рейтинг топ-50) такие задачи регулярно дают максимальные баллы.
Когда что применять: decision tree и trade-off автоматизации реверс инжиниринга
Главный вопрос на CTF при ограниченном времени - какой инструмент доставать первым.Decision tree для типичного reverse-задания:
file+strings- определить тип, наличие читаемых строк и флагов «в открытую».- Ghidra - найти
main, оценить сложность. Есть anti-debug? Переход к п.3. Нет? К п.4. - Anti-debug/anti-VM - ScyllaHide (Windows) или Frida hook. Возврат к п.4.
- Логика проверки линейная (серия
ifили посимвольное сравнение)? - angr. Нет? К п.5. - Логика сложная (хэши, криптография, lookup-таблицы)? - Frida для перехвата результатов + ручной анализ.
- angr работает дольше 3 минут? - Остановить, переключиться на Frida или ручной подход.
Trade-off: angr vs Frida vs ручной анализ
| Критерий | angr | Frida | Ручной (Ghidra + отладчик) |
|---|---|---|---|
| Скорость на простых CrackMe | Секунды | Минуты | Десятки минут |
| Path explosion (циклы, хэши) | Зависает | Не применимо | Единственный вариант |
| Anti-debug обход | Не нужен (нет ОС) | Hook в 3 строки | ScyllaHide или ручной патч |
| Перехват крипто в рантайме | Нет | Основная сила | Breakpoint + dump |
| Статически слинкованные бинари | Работает | Ограничено (нет экспортов) | Работает |
| VM-based обфускация | Плохо | Трассировка через Stalker | Единственный полноценный подход |
| Кривая обучения | Средняя (Python, концепция SMT) | Низкая (JavaScript) | Высокая (asm, архитектура) |
| Когда использовать | Линейные проверки, keygen | Anti-debug, API-мониторинг, крипто | Сложная логика, VM-обфускация |
| Когда НЕ использовать | Хэши, тяжёлая криптография | Статические бинари без символов | 20 тасков и 4 часа до конца CTF |
Требования к окружению
ОС: Windows 10/11 x64 для PE-бинарей, Linux Ubuntu 22.04+ для ELF. Рекомендация: FlareVM (Windows, набор для malware analysis) и REMnux (Linux). Для чисто CTF-фокуса хватит одной Linux VM.RAM: минимум 8 ГБ на хостовой машине с одной VM. 16 ГБ, если запускаете angr на бинарях с широким пространством состояний - символьное выполнение активно жрёт память.
Инструменты:
| Инструмент | Версия | Установка | Назначение |
|---|---|---|---|
| Ghidra | 11.x | Бесплатно, ghidra-sre.org | Статический анализ |
| x64dbg / GDB + pwndbg | Актуальные | Бесплатно | Отладка |
| angr | 9.2.208 | pip install angr | Символьное выполнение |
| Frida | Актуальная | pip install frida frida-tools | DBI, перехват, патчинг |
| Python | 3.10+ | - | Скрипты |
Сетевые требования: все инструменты работают offline. Для pip-установки нужен интернет однократно.
angr и Frida конфликтуют с некоторыми зависимостями. Используйте виртуальное окружение:
python -m venv rev-env && source rev-env/bin/activate && pip install angr frida frida-tools.Точка зрения
Основная ошибка тех, кто приходит в CTF reverse, - попытка решить всё вручную. Сидят в IDA, трассируют каждый байт, тратят час на таск, который angr закрывает за 30 секунд. Вторая ошибка - противоположная: пытаются засунуть в angr задачу с SHA256-проверкой и удивляются, что он «завис». Инструмент - не замена пониманию, а мультипликатор. Без базового чтения ассемблера и понимания, что делает код, ни angr, ни Frida не помогут - вы просто не будете знать, какой адрес передать вfind или какую функцию перехватить.Те, кто утверждает, что «CTF бесполезен для реальной работы» - скорее всего, решали задачи по гайдам и не анализировали паттерны. Боевой malware использует те же anti-debug приёмы (
IsDebuggerPresent, timing checks, TLS callbacks), те же пакеры (UPX, модифицированный UPX, кастомные), те же шифры (XOR, RC4, AES). Программа FLARE Team на REcon 2026 построена ровно на этом: shellcode analysis, anti-analysis bypass, ransomware cryptography - каждый модуль начинается с того же, что делает CTF-игрок категории reverse. Разница в том, что после CTF вы переходите к следующему таску, а в malware analysis - пишете отчёт и YARA-правило. Но навык декомпозиции бинаря на слои защиты и последовательного снятия каждого - идентичен. На WAPT эту механику - разбор защит, автоматизация, переход от теории к эксплуатации - проходят с лабами, где каждый вектор нужно отработать самому, а не прочитать в writeup.