
Ghidra — бесплатный фреймворк от NSA, вышедший в открытый доступ в 2019 году. За шесть лет стал стандартом CTF-категории reverse. Поддержка x86, ARM, MIPS, PowerPC, AVR, SPARC и других архитектур — это хорошо, но главное оружие — встроенный декомпилятор, которого нет в бесплатной IDA Free. Вместо чистого ассемблера можно читать C-подобный псевдокод, и это снимает тот самый барьер, о который разбиваются новички.
Но первая встреча с интерфейсом выглядит у всех одинаково: десятки панелей, переменные iVar1 и uVar2, глобальные данные с именами DAT_00104020 и ноль подсказок, куда вообще тыкать. Этот Ghidra туториал — путь от установки до решённой CTF-задачи: куда кликнуть, что искать в псевдокоде и как превратить стену нечитаемого кода в понятную логику.
Прежде чем открывать дизассемблер, соберите минимальный набор. Подробнее — в нашем руководстве по бинарный анализ уязвимостей.
Системные требования:
sudo apt install openjdk-21-jdk, на Windows — установщик с adoptium.net.ghidraRun (Linux) или ghidraRun.bat (Windows).Виртуальная машина обязательна при анализе незнакомых бинарников. На CTF-задачах начального уровня это перестраховка, но привычка правильная: запуск неизвестного бинарника на хостовой ОС — прямой путь к переустановке системы. VirtualBox или VMware Workstation Player, сеть внутри ВМ отключена.
Вопрос «Ghidra или IDA?» задаёт каждый, кто начинает обратную разработку программ. Для обучения реверс-инжинирингу Ghidra — оптимальный старт. Причина одна: декомпилятор. IDA Free его не даёт (Hex-Rays доступен только в платной версии за ощутимые деньги). Без декомпилятора придётся читать чистый ассемблер — навык полезный, но на старте это как учить язык по словарю без разговорной практики. Держать оба инструмента — нормальная практика: каждый подсвечивает то, что второй пропускает. Вот сравнение по критериям, которые реально влияют на работу:
| Критерий | Ghidra | IDA Free | radare2 (6.2.3) |
|---|---|---|---|
| Декомпилятор | Встроенный | Нет | Через плагин r2ghidra |
| Интерфейс | GUI (Java) | GUI (нативный) | CLI |
| Скриптинг | Java, Python (Jython) | Ограничен в бесплатной версии | r2pipe, Python |
| Главный плюс | Декомпилятор + мультиархитектурность | Скорость работы с PE-файлами | Автоматизация, встраивание в pipeline |
| Когда НЕ подходит | Бинарники больше 100 МБ — JVM тормозит | Нужен декомпилятор или расширенный скриптинг | Требует привыкания к CLI — не для первого знакомства с реверсом |
На CTF держите оба: Ghidra — основной инструмент, IDA Free — для экспресс-проверки гипотез. radare2 отложите до момента, когда захочется автоматизировать рутину скриптами.
Каждую CTF-задачу по reverse стоит начинать не с Ghidra, а с командной строки. Три команды экономят от минут до часов.
file crackme покажет формат (ELF/PE/Mach-O), архитектуру (x86-64, ARM, MIPS), тип линковки (static/dynamic), stripped или нет. Если file показывает stripped — готовьтесь к тому, что main в Symbol Tree не будет (об этом ниже). Если statically linked — бинарник содержит библиотечные функции внутри себя, и список символов в Ghidra окажется заметно длиннее обычного.
strings crackme выводит все читаемые строки из бинарника: сообщения пользователю, пути к файлам, иногда — пароли открытым текстом. По опыту решения задач на picoCTF и crackmes.one, удивительное количество задач начального уровня закрывается этой командой. Видите строку формата flag{...} или CTF{...} — проверяйте сразу, задача может решиться за секунды. Даже когда флаг не лежит в открытом виде, строки "Wrong password", "Try again", "Correct!" подскажут, какие функции потом искать внутри Ghidra через перекрёстные ссылки.
ltrace ./crackme перехватывает вызовы библиотечных функций при запуске. Если бинарник сравнивает ввод с эталоном через strcmp, ltrace покажет оба аргумента: strcmp("ваш_ввод", "настоящий_пароль"). Тут есть нюанс: ltrace работает только с динамически слинкованными бинарниками — он перехватывает вызовы через PLT. Для статически слинкованных (которые file покажет как statically linked) ltrace не покажет ничего — библиотечные функции встроены в код напрямую. В этом случае сразу переходите к Ghidra или используйте strace для анализа системных вызовов. Ещё один подводный камень: компилятор иногда инлайнит strcmp, и ltrace этого не увидит. Дополнительно попробуйте strace ./crackme — перехватывает системные вызовы, полезно, если программа читает пароль из файла. Обе проверки занимают секунды — пропускать их нет смысла.
Эти шаги — не ритуал. На CTF-блицах разница между первым и десятым местом измеряется секундами. Кто начал с strings — решает задачу за 10 секунд. Кто сразу открыл Ghidra — за 10 минут.
Запустите Ghidra. При первом старте появится окно менеджера проектов. Всплывающее «Tip of the Day» закрывайте — ничего полезного там нет.
Создание проекта: File → New Project → Non-Shared Project → выберите директорию и имя проекта. Один проект — одна CTF-задача. Все комментарии, переименования переменных и аннотации сохраняются между сессиями. Можно вернуться к задаче через неделю и увидеть ровно тот контекст, который наработали — не сравнить с одноразовым анализом в терминале.
Импорт файла: File → Import File → указываете бинарник (или перетаскиваете прямо в окно проекта). Ghidra автоматически определит формат, архитектуру, разрядность, предполагаемый компилятор. Для типовых ELF и PE менять настройки не нужно — жмите OK.
Code Browser: двойной клик по файлу в проекте открывает основное рабочее окно. Ghidra спрашивает «Analyze now?» — соглашайтесь с настройками по умолчанию. Автоанализ займёт от секунд до пары минут. Инструмент определит границы функций, построит перекрёстные ссылки (xrefs), вытащит текстовые строки, идентифицирует библиотечные вызовы.
Для Windows PE-файлов стоит проверить в окне анализа опции пропагации параметров внешних функций (название может отличаться в зависимости от версии Ghidra). Ghidra подставит параметры импортированных API прямо в декомпилированный код — читать станет заметно проще.
После автоанализа на экране — интерфейс с десятком панелей. Для решения CTF-задач нужны четыре окна. Остальные смело закрывайте — только мешают.
Левая панель. Дерево всех символов программы: функции, импорты, экспорты, метки. Разверните ветку Functions — здесь ищите main. Двойной клик перенесёт Listing и Decompiler к этой функции.
Разверните Imports — увидите API-вызовы. Это моментальный снимок того, что бинарник умеет. WriteProcessMemory, VirtualAlloc — код может модифицировать себя в рантайме. socket, connect — сетевая активность. CreateFile, ReadFile — работа с файловой системой. На CTF начального уровня импорты обычно минимальны (stdio, string), но привычка проверять их формируется здесь. В реальном анализе исполняемых файлов и вредоносного ПО этот взгляд — первое, что делаешь.
Listing (центральная панель) — ассемблерный код: адреса слева, мнемоники справа (MOV, CMP, JNE, CALL). Decompile (правая панель) — C-подобный псевдокод от декомпилятора. Вот это — главное оружие новичка при работе с Ghidra.
Ключевая связка: подсветка строки в декомпиляторе подсвечивает соответствующий ассемблер в Listing, и наоборот. Видите if (iVar1 == 0) справа — смотрите CMP и JNZ слева. Эта синхронизация бесценна для обучения: после десятка таких сопоставлений ассемблер перестаёт казаться случайным набором символов. Связь между if в декомпиляторе и парой CMP + условный переход в листинге становится осязаемой — это тот момент, когда реверс начинает «щёлкать».
Навигация в Listing: двойной клик на адресе CALL переносит внутрь вызываемой функции. Alt+← возвращает назад. Правый клик → Patch Instruction позволяет менять инструкции прямо в листинге — пригодится для патчинга условных переходов.
Function Graph (Window → Function Graph) — граф потока управления, аналог Graph View в IDA. По умолчанию открывается в уменьшенном масштабе — ничего не разобрать. Правый клик → Properties → Start Fully Zoomed In исправляет это. Нажатие F1 при наведении на любой элемент интерфейса вызывает контекстную справку — пользуйтесь, пока осваиваетесь.
Меню Window → Defined Strings. Все текстовые строки из бинарника в одном месте. Двойной клик на строке переносит к ней в Listing. Правый клик → References → Show References to Address — список всех мест, где строка используется. Перекрёстная ссылка (xref) приведёт к функции-обработчику. Это ваша целевая функция — та, в которой происходит проверка пароля или флага. Часто задача решается одним взглядом на Defined Strings. Не преувеличиваю — на CTF начального уровня это работает раз за разом.
Декомпилятор понятия не имеет, как автор назвал переменные. Вместо password вы увидите local_28. Вместо char input[100] — char local_78[104]. Глобальные данные именуются по адресу: DAT_00104020 — ячейка памяти по адресу 0x00104020. Тип undefined8 — 8 байт, тип которых Ghidra не определила автоматически. Для возвращаемого значения main это почти всегда int.
Функции вида FUN_00401234 — те, которые Ghidra не смогла сопоставить с известными библиотечными вызовами. Двойной клик покажет их декомпилированный код. Часто назначение очевидно по контексту: какие API вызываются внутри, какие строки используются.
Переименование — ключевой навык. Правый клик на переменной → Rename Variable (или клавиша L). Увидели, что local_28 передаётся вторым аргументом в strcmp? Переименуйте в user_input. Строка DAT_00104020 содержит "Enter password:"? Переименуйте метку в prompt_msg. Ghidra синхронизирует переименования между Decompile и Listing автоматически. После 5–10 переименований нечитаемый псевдокод превращается в понятную программу. Ощущение буквально как туман рассеивается — вдруг видишь, что делает код.
Retype Variable (правый клик → Retype Variable или Ctrl+L) — исправление типа данных. undefined4 → int, undefined8 → long или char *. Не обязательно для решения задачи, но делает код заметно понятнее.
Edit Function Signature (правый клик на имени функции) — исправление прототипа. Если Ghidra ошиблась с количеством или типами параметров, правильная сигнатура мгновенно улучшит весь декомпилированный код функции. Декомпилятор бинарного кода в Ghidra не идеален — но при систематических переименованиях и правках типов он выдаёт результат, достаточный для восстановления логики подавляющего большинства CTF-задач.
Допустим, strings и ltrace не дали результата — пароль зашифрован или логика нестандартная. Переходим к полному статическому анализу.
Импортируем файл, запускаем автоанализ. В Symbol Tree → Functions находим main. Двойной клик — декомпилятор показывает псевдокод. Типичный password checker после автоанализа (пример для демонстрации — так выглядит реальный вывод декомпилятора Ghidra):
void main(void) {
char local_78[104];
int iVar1;
puts("Enter the key:");
fgets(local_78, 100, stdin);
iVar1 = strcmp(local_78, "r3v3rs3_m3");
if (iVar1 == 0) {
puts("Flag: CTF{well_done}");
} else { puts("Try again."); }
}
Пароль — строковый литерал "r3v3rs3_m3", видимый прямо в декомпиляторе. Задача закрыта за секунды. Но на CTF среднего уровня эталон не лежит открытым текстом — ввод трансформируется перед сравнением. Классический паттерн — XOR:
void main(void) {
char local_48[32];
fgets(local_48, 32, stdin);
for (int i = 0; i < 16; i++) {
local_48[i] = local_48[i] ^ 0x37;
}
if (memcmp(local_48, &DAT_00402010, 16) == 0)
puts("Correct!");
else puts("Wrong!");
}
Логика: программа XOR-ит каждый символ ввода с ключом 0x37, сравнивает результат с массивом байт по адресу DAT_00402010. Пример упрощён — в реальном CTF-бинарнике обычно есть проверка длины ввода перед XOR, чтобы не обрабатывать мусор. XOR — обратимая операция: encrypted ^ key = original. Чтобы получить пароль, берём байты эталона и XOR-им каждый с 0x37.
Как добраться до байтов эталона: двойной клик на DAT_00402010 в декомпиляторе перенесёт Listing к этому адресу. Увидите сырые байты в hex. Выделите 16 байт, правый клик → Copy Special → Byte String — скопируете последовательность. Дальше — скрипт на Python из пяти строк: итерация по массиву с XOR 0x37 на каждый элемент, вывод результата как строки. Операция настолько частая на CTF, что шаблон стоит держать под рукой.
Два подхода к решению:
Чистый — восстановить логику, написать обратный алгоритм, вычислить правильный ввод. Даёт полное понимание и работает на любой сложности.
Патчинг — найти в Listing инструкцию JNZ (jump if not zero) после memcmp и заменить на JZ (jump if zero) через правый клик → Patch Instruction. Программа примет любой ввод. На CTF оба подхода засчитываются.
Грязный путь — не позор. Позор — четыре часа ковырять алгоритм, когда задача закрывалась инверсией одного байта.
В задачах среднего уровня и выше бинарники часто собраны без отладочных символов — stripped. В реальных атаках удаление символов из payload'а классифицируется как T1027.008 (Stripped Payloads, тактика Defense Evasion по MITRE ATT&CK). В CTF stripped-бинарник — просто результат сборки с флагом strip, но эффект тот же: в Symbol Tree не будет main — только набор функций вида FUN_00401xxx. Команда file заранее покажет stripped в выводе — и вы будете готовы.
Три способа найти главную функцию:
Через entry и __libc_start_main. В Symbol Tree всегда есть entry — точка входа ELF-файла. Двойной клик покажет код, вызывающий __libc_start_main. Штука в том, что динамически слинкованные stripped-бинарники сохраняют символы импортов — libc нуждается в них для корректной работы. Для статически слинкованных stripped-бинарников символ __libc_start_main тоже может отсутствовать — тогда потребуется поиск по паттерну кода или FunctionID. Один из аргументов __libc_start_main — адрес main. Ghidra покажет его как первый параметр вызова. Двойной клик на этом адресе — и вы в целевой функции. Переименуйте через L в main.
Через строки. Откройте Defined Strings. Найдите строки, связанные с логикой задачи: "Enter password", "Correct", "Wrong". Правый клик → References → перейдите к функции, которая их использует. Даже если она называется FUN_00401234 — это ваша цель. Переименуйте, все xrefs обновятся автоматически.
Через Function Call Graph. Window → Function Call Graph строит граф вызовов. Функция, которая обращается к puts, scanf/fgets, strcmp/memcmp — почти наверняка содержит основную логику задачи. Ищите узлы с наибольшим количеством исходящих рёбер к библиотечным вызовам.
Password checker — ввод сравнивается с эталоном. Эталон хранится открытым текстом, зашифрован XOR, захеширован или собран на стеке побайтово.
Flag checker — программа проверяет формат флага (flag{...}) и пропускает его через серию проверок: длина, допустимые символы, математические соотношения между символами. Чистое решение требует восстановления всех условий.
Обфускация строк. Распространённый приём — сборка строк на стеке побайтово (в реальных атаках классифицируется как T1027, Obfuscated Files or Information по MITRE ATT&CK, в CTF — просто способ спрятать строку от strings): local_20[0] = 0x66; local_20[1] = 0x6c; ... — это ASCII-коды символов. Утилита strings такие строки не находит, потому что в секции данных их нет — они существуют только в рантайме. В декомпиляторе Ghidra это выглядит как цепочка присваиваний. Выделите hex-значение → правый клик → Convert → Char покажет символ.
Многошаговые трансформации. На уровне medium ввод проходит через цепочку: base64 → XOR → ROT13 → сравнение. Восстанавливайте каждый шаг последовательно, начиная с конца — от точки сравнения к вводу. Декомпилятор покажет всю цепочку в одной функции (или в нескольких — переходите по CALL).
| Ошибка | Что видите | Как исправить |
|---|---|---|
| Не запустили автоанализ | Пустой Symbol Tree, нет функций | Analysis → Auto Analyze |
| Не переименовали переменные | Стена из local_28 и iVar1 |
Систематически именуйте через L |
Начали с entry вместо main |
Утонули в коде инициализации libc | Ищите main в Functions или через строки |
| Игнорируете Defined Strings | Часы анализа вместо секунд | Window → Defined Strings — первое действие |
Не проверили file/strings |
Открыли Ghidra для задачи, решаемой без неё | Всегда начинайте с терминала |
| Function Graph нечитаем | Мелкий масштаб, блоки не видно | ПКМ → Properties → Start Fully Zoomed In |
Ghidra — инструмент статического анализа бинарных файлов. Программу не нужно запускать, чтобы восстановить логику. Для CTF начальных уровней этого хватает за глаза, а для анализа вредоносного ПО статический подход — необходимость: запускать неизвестный код опасно. Но у статического анализа есть границы: самомодифицирующийся код, динамически генерируемые данные, сложные условные ветвления — всё это лучше видно при отладке.
Следующий уровень — комбинация Ghidra с динамическим анализом. На Linux — gdb (с расширениями pwndbg или GEF, без них gdb — боль), на Windows — x64dbg. Рабочая схема: находите интересную функцию в Ghidra, ставите breakpoint по её адресу в отладчике, запускаете бинарник и смотрите реальные значения регистров и переменных в момент исполнения. Два инструмента вместе работают кратно эффективнее каждого по отдельности.
Для автоматизации рутины Ghidra поддерживает скрипты на Java и Python (через Jython). Window → Script Manager открывает библиотеку готовых скриптов. Плагин FindCrypt автоматически находит криптографические константы в бинарнике: AES S-Box, SHA-256 init values и подобное. Для оценки энтропии секций (выявление упакованного или зашифрованного кода) используйте binwalk -E снаружи Ghidra.
Основы реверс-инжиниринга — не знание мнемоник MOV и CMP. Это выработанный рефлекс: strings → ltrace → Defined Strings → xref к целевой функции → переименование переменных → восстановление логики. Конвейер одинаков для задачи на 50 очков и для задачи на 500. Разница — в количестве слоёв обфускации, но стартовая точка всегда та же.
За последние пару лет CTF-задачи reverse всё чаще совмещают статический анализ с элементами pwn и crypto. Чистые password checkers с strcmp уходят в warmup. Средний уровень уже требует понимания heap layout, custom allocators, нестандартных шифров. Ghidra с декомпилятором закрывает первый барьер — чтение кода. Но без привычки сверять псевдокод с ассемблерным листингом выше среднего уровня не подняться.
Декомпилятор ошибается — и ошибается регулярно. Путает знаковые и беззнаковые типы, неправильно восстанавливает оптимизированные циклы, пропускает блоки кода при нестандартных calling conventions. Навык чтения ассемблера — не атавизм и не бонус для перфекционистов, а то, что отделяет решающего easy от решающего hard. Полагаться исключительно на окно Decompile — значит добровольно поставить себе потолок. И он станет ощутим уже через десяток-другой решённых задач.
Попробуйте взять любой crackme с crackmes.one уровня 2-3 и пройти весь конвейер: file → strings → ltrace → Ghidra → переименование → восстановление логики. Если на WAPT интересует связка реверса с веб-эксплуатацией — там эту цепочку разбирают в нескольких модулях с лабами.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...