
Stripped ELF без единого символа, file показывает лаконичное «ELF 64-bit LSB executable, x86-64» — стандартное начало CTF-задачи категории reverse на площадках уровня picoCTF или HTB Cyber Apocalypse. Новичок запускает objdump, видит стену ассемблера и закрывает терминал. Опытный участник открывает тот же файл в Ghidra, через минуту читает псевдокод на C и через пять минут сдаёт флаг. Разница не в знании x86-мнемоник, а в инструменте и подходе. Дальше — пошаговый разбор: от первой команды strings до Python-солвера для XOR-обфускации, с разбором антиотладочных трюков и их классификацией по MITRE ATT&CK.
Прежде чем переходить к практике — убедитесь, что рабочая машина закрывает минимум. Подробнее — в нашем материале про бинарный анализ уязвимостей.
Ghidra — open-source фреймворк для реверс-инжиниринга от NSA, выпущенный в 2019 году. Репозиторий на GitHub обновляется регулярно, сообщество растёт. Это не заброшенная утилита — полноценная промышленная платформа для бинарного анализа.
Дополнительно потребуются: утилита strings (предустановлена в Linux/macOS, на Windows входит в Sysinternals), команда file и Python 3 для написания солверов.
[Применимо: CTF reverse, beginner-intermediate, ELF/PE]
Правило CTF reverse — не запускать тяжёлый инструмент, пока не исчерпаны лёгкие. Три команды в терминале могут сэкономить десятки минут и сделать Ghidra вообще ненужной.
file — определяет тип файла. Команда file ./challenge покажет архитектуру (x86, ARM, MIPS), формат (ELF, PE, Mach-O), разрядность и наличие/отсутствие отладочных символов (stripped vs not stripped). PE-файл — Windows, ELF — Linux. Если file показывает просто «data» без конкретного формата — скорее всего файл упакован или зашифрован, и это уже подсказка к стратегии.
strings — вытаскивает читаемые строки из бинарника. Синтаксис элементарен: strings ./challenge. Как пишет автор блога swanandx (один из самых цитируемых RE-гайдов для CTF-новичков): «you will find a lot of challenges that can be solved using this single command». Флаг может лежать прямо в строках — форматы CTF{...}, flag{...}, pico{...} распознаются мгновенно. Даже если флага нет, strings выдаст подсказки: сообщения об ошибках вроде «Wrong password!», названия функций, а иногда — сам пароль в открытом виде.
Фильтрация вывода: strings ./challenge | grep -i flag покажет только строки с «flag», а strings -n 20 ./challenge отфильтрует короткий мусор и оставит строки длиной от 20 символов.
ltrace — трассирует вызовы библиотечных функций. Если бинарник использует strcmp() для сравнения пароля, ltrace ./challenge покажет оба аргумента — введённое значение и эталонное. Работает не всегда (зависит от способа компиляции), но попытка занимает две секунды.
Цепочка при получении бинарника: file → strings → ltrace → Ghidra. Каждый следующий шаг — только если предыдущий не дал результата. На реальных CTF половина задач beginner-уровня решается до Ghidra. Серьёзно — половина.
Установка Ghidra из готового дистрибутива занимает пять минут. Скачайте последний релиз из раздела Releases на GitHub (github.com/NationalSecurityAgency/ghidra/releases), распакуйте архив и запустите ghidraRun (Linux/macOS) или ghidraRun.bat (Windows). Единственная зависимость — JDK.
На Linux: sudo apt install openjdk-17-jdk (или версия из README репозитория). На macOS и Windows — скачайте JDK с Adoptium. Сборка из исходников возможна (через Gradle), но для CTF избыточна — готовый релиз функционально идентичен.
Создание проекта и импорт бинарника. При первом запуске Ghidra откроет Project Manager. File → New Project → Non-Shared Project. Укажите директорию и имя — например, «CTF_2025». Один проект хранит несколько бинарников, что удобно для серии задач с одного соревнования.
Импортируйте файл: File → Import File. Ghidra сама определит формат (ELF, PE, raw) и предложит параметры импорта. В подавляющем большинстве случаев дефолтные настройки работают без изменений.
Автоанализ — шаг, который новички иногда пропускают, а потом мучаются. После импорта двойной клик по файлу откроет CodeBrowser — основное рабочее окно. Появится диалог «Analyze now?». Соглашайтесь и принимайте все опции по умолчанию. Автоанализ восстановит таблицу символов, определит границы функций, распознает строковые литералы и построит граф перекрёстных ссылок. На типичном CTF-бинарнике размером 100–500 КБ это занимает 10–30 секунд. Без автоанализа декомпилятор покажет мусор — не пропускайте.
[Применимо: CTF reverse, все уровни, ELF/PE]
Окно CodeBrowser — три панели. Слева — Symbol Tree: список функций, переменных, импортов. По центру — Listing: ассемблерный код с адресами, байтами и мнемониками. Справа — Decompiler: псевдокод C, сгенерированный из ассемблера. Декомпилятор — основное оружие в CTF. Ассемблер нужен как резервный вариант, когда декомпилятор споткнулся.
В нормальном (не-stripped) бинарнике всё просто: в Symbol Tree раскройте Functions, найдите main. Двойной клик — и в Decompiler появится псевдокод входной функции.
Stripped-бинарники — другая история. Компиляция с strip удаляет таблицу символов: имена функций и переменных исчезают. В терминологии MITRE ATT&CK это Stripped Payloads (T1027.008, тактика Defense Evasion): удаление отладочной информации затрудняет анализ. В CTF задачах среднего уровня stripped — стандарт, а не исключение.
Ghidra справляется: даже в stripped-бинарнике автоанализ находит функцию entry. Она вызывает __libc_start_main, и первый аргумент этого вызова — адрес main. Ghidra пометит её как FUN_00401xxx или аналогично. Кликните по адресу-аргументу — попадёте в декомпилированный main.
Альтернативный маршрут — поиск по строкам. Если программа при запуске печатает «Enter password:» или «Wrong!», откройте Window → Defined Strings, найдите строку и перейдите по перекрёстной ссылке: правый клик → References → Show References to Address. Ссылка приведёт к функции, которая использует строку — почти наверняка это main или функция проверки ввода.
Декомпилятор выдаёт имена вроде local_48, iVar1, param_1. Читать такое — боль. Переименуйте: правый клик по переменной → Rename Variable. Назовите local_48 → user_input, iVar1 → check_result — и через минуту псевдокод читается как оригинальный исходник.
То же для функций: правый клик → Rename Function. Бинарник не меняется — только проект Ghidra. Привычка переименовывать всё понятное экономит массу времени на сложных задачах, где десятки вызовов FUN_004011xx сливаются в кашу.
Перекрёстные ссылки (xrefs) — ещё один мощный инструмент навигации. Нашли подозрительную строку или константу? Правый клик → References → Show References покажет все места в коде, где она используется. За секунды — от строки «Correct!» к функции, которая проверяет флаг.
Большинство CTF-задач на реверс-инжиниринг для начинающих делятся на два типа. Понимание разницы определяет стратегию ещё до запуска Ghidra.
Поток: принять ввод → сравнить с эталоном → если совпало — напечатать флаг. В декомпиляторе Ghidra это выглядит как вызов strcmp (или побайтовое сравнение) с последующим ветвлением. Если пароль хранится в открытом виде — достаточно прочитать аргумент strcmp, и задача закрыта.
Даже если пароль обфусцирован, флаг можно вытянуть через отладчик: в GDB или x64dbg ставьте breakpoint после сравнения, меняйте результат (регистр EAX) на 0 — и программа пойдёт по «правильной» ветке, напечатав флаг. Или замените инструкцию условного перехода jne на nop — обход проверки без знания пароля.
Поток: принять ввод → трансформировать (XOR, хеш, арифметика) → сравнить с эталонным массивом. При совпадении — «Correct!», иначе — «Wrong!».
Обход проверки тут бесполезен: флаг — это сам ввод, и программа его не печатает. Нужно прочитать алгоритм трансформации в декомпиляторе и обратить его. Именно для этого типа задач критически важен навык чтения псевдокода и написания Python-солверов.
| Признак | Password checker | Flag checker |
|---|---|---|
| Программа печатает флаг | Да, при верном вводе | Нет, только «Correct!» |
| Обход проверки в отладчике | Даёт флаг | Бесполезен |
| Нужно понимать алгоритм | Не обязательно | Обязательно |
| Основной подход | Найти строку или патчить ветвление | Реверсировать трансформацию |
| Типичный уровень | Beginner | Intermediate |
[Применимо: CTF reverse, intermediate, ELF/PE]
XOR — самый частый алгоритм в CTF-задачах beginner-intermediate уровня. В терминологии MITRE ATT&CK это Obfuscated Files or Information (T1027): авторы задач шифруют строки и данные, чтобы strings не показала ответ. Процесс решения — Deobfuscate/Decode Files or Information (T1140): восстановление исходных данных.
Типичная картина в Ghidra декомпиляторе: цикл, который берёт каждый символ пользовательского ввода, XOR-ит его с ключом, прибавляет (или вычитает) константу и сравнивает результат с массивом байт. Все элементы совпали — ввод корректен.
Допустим, декомпилятор показал: функция XOR-ит каждый символ с 0x5, прибавляет 0x5 и сравнивает с flag_bytes. Чтобы получить исходный ввод — выполняем операции в обратном порядке. В Ghidra двойной клик по имени массива покажет его содержимое. Записываем байты и пишем солвер:
flag_bytes = [0x48, 0x4e, 0x49, 0x47, 0x83,
0x82, 0x3a, 0x7c, 0x5f, 0x82,
0x6f, 0x7c, 0x5f, 0x82, 0x3a,
0x7c, 0x7d]
flag = ""
for b in flag_bytes:
flag += chr((b - 5) ^ 5)
print(flag)
Шесть строк — и флаг в терминале. Этот паттерн (прочитать трансформацию в Ghidra → написать обратную операцию в Python) покрывает большинство задач на flag checker.
Когда в Ghidra видите массив hex-чисел, присваиваемых переменным — проверьте их ASCII-значения. Иногда флаг записан побайтово и виден без декодирования. Правый клик по числу → Convert → Char покажет символ.
Вариант посложнее — программа хеширует ввод кастомной функцией и сравнивает с эталоном. Декомпилятор Ghidra покажет алгоритм хеширования. Если пространство ввода ограничено (4–8 символов, только цифры) — Python brute force за секунды. Копируете хеш-функцию из псевдокода, переводите в Python и перебираете.
Ещё один частый паттерн — система уравнений. В writeup задачи «Крестики» с Codeby Games (опубликован на Habr) описан бинарник, который берёт четыре числа из ввода, применяет к каждому комбинацию XOR и арифметики и сравнивает результат с константами. Решение — обратить каждую операцию по цепочке. Отладчик тут не помощник: нужно решить уравнения руками или скриптом.
Для задач повышенной сложности есть фреймворк angr (версия 9.2.208, Python) — он автоматизирует поиск ввода через символьное выполнение (symbolic execution). Подаёте angr бинарник, указываете адрес «правильной» ветки и адрес «неправильной» — он сам находит нужный ввод. Инструмент мощный, но требует отдельного погружения.
Ghidra — не единственный дизассемблер. Вот trade-off таблица для CTF-контекста:
| Критерий | Ghidra (NSA) | IDA Free 8.x | Cutter (Rizin) |
|---|---|---|---|
| Цена | Бесплатно | Бесплатно | Бесплатно |
| Декомпилятор | Встроенный, локальный | Отсутствует (Hex-Rays — только в коммерческой IDA Pro) | Через плагин r2ghidra |
| Архитектуры | 30+ (x86, ARM, MIPS, PPC) | x86/x64 | 30+ через Rizin |
| Скриптинг | Java, Python (Jython) | Ограничен в Free-версии | Python, r2pipe |
| Совместная работа | Ghidra Server (встроенный) | Нет | Нет |
| RAM | Минимум 4 ГБ | Минимум 2 ГБ | Минимум 2 ГБ |
| Когда использовать | CTF любого уровня, malware, firmware | Быстрый просмотр x86-бинарников | Привычен к консольному radare2 |
| Когда НЕ использовать | Машина с менее 4 ГБ RAM | Не-x86 архитектуры, нужен декомпилятор (отсутствует в Free) | Нужен полноценный GUI-декомпилятор с навигацией |
Встроенный декомпилятор, работающий локально и поддерживающий десятки архитектур — главное преимущество Ghidra перед IDA Free для CTF reverse engineering. В IDA Free 8.x декомпилятора нет: Hex-Rays Decompiler доступен только в коммерческой IDA Pro. Ранние версии IDA Freeware экспериментировали с облачным декомпилятором, но эту возможность свернули. Для CTF отсутствие декомпилятора — приговор. Cutter — достойная альтернатива для тех, кто привык к radare2 (версия 6.1.9, активная разработка), но его GUI-декомпилятор работает на том же движке Ghidra Decompiler через плагин r2ghidra.
Когда Ghidra недостаточно. Для .NET-сборок — ILSpy или dnSpy. Для Android .apk — jadx. Для сложных алгоритмов с constraint solving — angr. Ghidra покрывает ELF, PE и большинство архитектур, но для специализированных форматов узкоспециализированные инструменты работают лучше.
Ограничения декомпилятора. Ghidra декомпилятор иногда врёт: путает типы данных, генерирует нечитаемые конструкции с указателями, некорректно обрабатывает оптимизированный C++ с виртуальными функциями. Когда псевдокод выглядит бессмысленно — переключайтесь на Listing и читайте ассемблер. Полезное упражнение: скомпилируйте свою тестовую программу на C и откройте в Ghidra. Увидите, как ваш собственный for превращается в while с указателями — и паттерны чужих бинарников станут читаемыми.
[Применимо: CTF reverse, intermediate-advanced]
В задачах среднего уровня авторы усложняют анализ. Техники и их классификация по MITRE ATT&CK — те же, что используют реальные вредоносные программы.
Упаковка (T1027.002 — Software Packing): UPX и аналоги сжимают секции бинарника. Ghidra покажет мусор вместо кода. Диагностика: file ./challenge — если упаковщик не скрыт, утилита покажет «UPX compressed». Распаковка одной командой: upx -d ./challenge. После этого Ghidra нормально декомпилирует файл.
Вариант потоньше — утилита Detect It Easy (DIE), которая распознаёт десятки упаковщиков и протекторов, включая те, которые file пропускает. Загружаете бинарник — DIE показывает, чем упаковано.
Обфускация строк (T1027): если strings не показывает ничего полезного — строки закодированы. XOR с фиксированным ключом — самый простой вариант. Встречаются также base64, побайтовое построение строки в стеке (когда каждый символ записывается отдельной инструкцией mov), и кастомные шифры. Ghidra декомпилятор покажет процесс сборки строки в псевдокоде — ищите циклы и побитовые операции. Видите цепочку local_28 = 0x43; local_27 = 0x54; local_26 = 0x46? Проверьте ASCII-таблицу: это «CTF».
Debugger Evasion (T1622): программа определяет, запущена ли она под отладчиком, и меняет поведение. На Windows — IsDebuggerPresent(), на Linux — ptrace(PTRACE_TRACEME, 0, 0, 0). Использование ptrace как антиотладки задокументировано в описании T1622 MITRE, однако Atomic Red Team на момент написания содержит тесты T1622 только для Windows (IsDebuggerPresent, NtQueryInformationProcess) — Linux-вариант придётся воспроизводить вручную. Программа может завершиться, выдать ложный флаг или уйти в бесконечный цикл. В CTF-задаче «Крестики» (Codeby Games, writeup на Habr) IsDebuggerPresent присутствовал в импортах, но не влиял на логику — автор оставил его как отвлекающий манёвр (и многие на него купились). В других задачах это реальное препятствие.
В Ghidra антиотладку видно в декомпилированном коде. Найдите вызов IsDebuggerPresent через перекрёстные ссылки (выберите функцию в Symbol Tree → References → Show References). Проследите, куда уходит поток при обнаружении отладчика — обычно достаточно патчить условный переход или игнорировать проверку при статическом анализе.
Virtualization/Sandbox Evasion (T1497): проверка запуска в виртуальной машине — поиск специфичных MAC-адресов (VMware, VirtualBox), артефактов в реестре, имён процессов. В CTF встречается реже, но на продвинутых соревнованиях и в задачах категории malware analysis — регулярно. Ghidra покажет строковые сравнения с «VMware», «VBox», «qemu» — ищите их в Defined Strings.
В работе (анализ вредоносного ПО, исследование уязвимостей) реверс-инжиниринг не изолированный навык. Он встраивается в цепочку: получение образца → статический анализ (strings, Ghidra) → динамический анализ (sandbox, отладчик) → извлечение IoC → написание правил детекции. В CTF цепочка короче (получить бинарник → понять логику → вытащить флаг), но навыки переносятся один в один. Кто научился читать XOR-обфускацию в Ghidra на CTF — без труда разберёт аналогичный приём в реальном sample малвари.
Тренировочный подход, который я рекомендую: скомпилируйте простую программу на C сами (gcc -o test test.c), откройте в Ghidra и сравните декомпилированный код с исходником. Когда увидите, как ваш for (int i = 0; i < 10; i++) превращается в while (local_c < 10) с указателями — чужие бинарники перестанут казаться магией. На GitHub доступен проект Flag Hunt от 0x57origin — набор из пяти мини-задач, каждая учит отдельному паттерну: hardcoded PIN, XOR, математические условия, кастомный хеш, составная фраза. Проект спроектирован для первых десяти минут в Ghidra — то что нужно для старта.
Мне часто говорят: «Хочу в реверс, начинаю учить ассемблер». Каждый раз отвечаю одинаково: начни с Ghidra и конкретной задачи, а не с мнемоник. Ассемблер x86 содержит сотни инструкций, из которых в типичном CTF-бинарнике встречается два десятка. Учить их абстрактно — всё равно что зубрить словарь вместо чтения книг. Декомпилятор Ghidra выдаёт псевдокод, который читается без знания mov и lea. Да, декомпилятор ошибается — путает типы, пропускает оптимизации, генерирует кашу с указателями на оптимизированном C++. Но для 80% задач beginner-intermediate уровня его вывода достаточно. Ассемблер приходит потом, инкрементально, когда декомпилятор споткнулся и нужно заглянуть в Listing. Именно так работает практический подход: от задачи к инструменту, от инструмента к теории — не наоборот. За два года на CTF-площадках я наблюдаю одну картину: участники, которые начали с Ghidra и реальных бинарников, решают первые задачи за часы. Те, кто полгода учил ассемблер по книге — месяцами не переходят к практике, потому что «ещё не готовы». Готовность не наступает от чтения — она приходит от первого strings | grep flag, от первого переименования local_48 в user_input, от первого работающего Python-солвера. Если CTF уже не хватает и хочется системной подготовки к OSCP — WAPT даёт ту же глубину разбора с лабами на каждый модуль.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...