
На отборочном CTF наша команда потеряла три флага в категории reverse — каждый решался за 15 минут. Двое ребят впервые открыли Ghidra прямо на турнире: увидели стену ассемблерного кода в листинге, не нашли окно декомпилятора и переключились на web-задачи. После турнира я провёл для них двухчасовой воркшоп — на следующем соревновании оба закрыли первые reverse-таски сами. Эта статья — тот самый воркшоп в текстовом виде: от установки до извлечения флага из типового CTF-бинарника. Знания ассемблера не нужны.
Ghidra — фреймворк для реверс-инжиниринга с открытым кодом, который NSA выложили публично в марте 2019-го на конференции RSA. Репозиторий на GitHub обновляется регулярно, актуальная версия на начало 2025 — 11.x (начиная с поздних сборок 11.x требуется JDK 21 — сверяйтесь с install notes в архиве). Подробнее — в нашем обзоре создание ctf заданий.
Для начинающих главный козырь Ghidra — встроенный бесплатный декомпилятор. Он превращает машинный код обратно в псевдокод на C. Результат не идентичен исходнику, но его хватает, чтобы восстановить логику программы без глубокого знания ассемблера. В IDA Free декомпилятор Hex-Rays недоступен — он входит только в платную IDA Pro стоимостью тысячи долларов. Для новичка на CTF разница критическая: без декомпилятора придётся читать чистый ассемблер с первого дня. Удачи с этим.
Ghidra — инструмент статического анализа бинарного файла. Программу не нужно запускать, чтобы понять, что она делает. Для CTF-задач это удобно: загрузили бинарник, разобрали логику, нашли флаг. Для анализа вредоносного ПО — необходимость: запускать неизвестный исполняемый файл на рабочей машине чревато переустановкой ОС. В терминах MITRE ATT&CK статический анализ позволяет разбирать техники вроде Obfuscated Files or Information (T1027, Defense Evasion) без риска выполнения вредоносного кода.
Инструмент поддерживает десятки архитектур: x86 (16/32/64-бит), ARM (32/64-бит), MIPS, PowerPC, AVR и другие. Написана на Java, работает на Windows, Linux и macOS — для CTF это означает, что к конкретной ОС вы не привязаны.
Минимальные требования: 8 ГБ оперативки (с 4 ГБ будет тесно — JVM жрёт ресурсы при автоанализе крупных бинарников), JDK 21 и ZIP-архив Ghidra.
На Linux (Ubuntu/Debian) установка сводится к трём операциям: sudo apt update && sudo apt install openjdk-21-jdk для JDK, затем скачивание архива с github.com/NationalSecurityAgency/ghidra/releases, распаковка через unzip ghidra_*_PUBLIC_*.zip и запуск ./ghidraRun. На Windows — аналогично: JDK 21 от Adoptium, ZIP-архив, распаковка, запуск ghidraRun.bat. Никакого инсталлятора, никаких зависимостей.
Проверьте версию Java командой java -version — должна показать 21.x. Если версия старая или JDK не стоит, Ghidra не запустится. При первом запуске выскочит окно «Tip of the Day» — советы полезные, но можно закрыть. Перед вами — менеджер проектов Ghidra, пока пустой.
Прежде чем лезть в дизассемблер, потратьте 30 секунд на командную строку. Этот этап разведки пропускают почти все новички — и зря: иногда задача на CTF закрывается ещё до запуска Ghidra.
Команда file crackme покажет тип файла: ELF 64-bit или PE 32-bit, статическая или динамическая линковка, архитектуру процессора. Эта информация определяет, какие инструменты для реверс-инжиниринга применимы дальше и в каком режиме загружать бинарник.
strings crackme выведет все читаемые текстовые строки из бинарника. По опыту решения задач на PicoCTF и crackmes.one — удивительное количество CTF reverse заданий начального уровня решается одной этой командой. Пароль или флаг лежит в бинарнике открытым текстом. Видите в выводе строку формата flag{...} или CTF{...} — проверяйте сразу, задача может закрыться за секунды. Для фильтрации: strings crackme | grep -i flag — отсечёт шум библиотечных строк.
Третий приём — ltrace ./crackme. Утилита перехватывает вызовы библиотечных функций в реальном времени. Если программа сравнивает ваш ввод с захардкоженным паролем через strcmp, ltrace покажет оба аргумента: strcmp("ваш_ввод", "реальный_пароль"). Вот вам и пароль.
Ограничения: ltrace может отсутствовать в репозиториях свежих дистрибутивов (Ubuntu 22.04+, Debian 12) — ставьте вручную. Со статически слинкованными бинарниками (частый случай на CTF) утилита не работает — для них strace или gdb с breakpoint на plt. Ещё нюанс: современные версии gcc иногда инлайнят strcmp, и ltrace их не видит. Но проверка занимает две секунды — пропускать нет смысла. strace тоже полезен: показывает системные вызовы, что пригодится, если программа читает флаг из файла или делает сетевые запросы.
Ghidra организует работу через проекты — контейнер для бинарника и всех результатов его анализа. Один проект на одну CTF-задачу.
Последовательность: File → New Project → Non-Shared Project (вы работаете один) → указываете директорию и имя проекта → Finish. Дальше File → Import File → выбираете бинарник.
Ghidra автоматически определит формат: ELF для Linux, PE для Windows, Mach-O для macOS. Появится окно Import Results с метаданными: архитектура, разрядность, предполагаемый компилятор. Менять ничего не нужно — жмите OK.
Двойной клик по импортированному файлу открывает CodeBrowser — основное рабочее пространство Ghidra для анализа бинарника. Инструмент спросит: «Analyze now?» — соглашайтесь, опции оставляйте по умолчанию. Для PE-файлов стоит глянуть список анализаторов в Auto Analysis Options — набор чекбоксов зависит от версии Ghidra; включение PE-специфичных анализаторов может улучшить подстановку параметров импортированных функций. Автоанализ занимает от нескольких секунд до пары минут. За это время Ghidra определяет границы функций, строит перекрёстные ссылки (xref), вытаскивает текстовые строки и опознаёт библиотечные вызовы.
Нажатие F1 при наведении курсора на любой элемент интерфейса вызывает контекстную справку. Пока осваиваетесь — пользуйтесь, сэкономит время на ковыряние в меню.
После автоанализа интерфейс CodeBrowser выглядит устрашающе: десятки панелей, кнопок, деревьев. На практике для CTF-задач нужны четыре окна. Остальные закрывайте — ничего не потеряете.
Левая панель — дерево всех символов программы: функции, импорты, экспорты, метки. Здесь ищете функцию main — точку входа в пользовательскую логику. Разверните ветку Functions, найдите main, двойной клик — Listing с Decompiler переместятся к этой функции.
Отдельно загляните в ветку Imports: это список используемых библиотек и API-вызовов, моментальный снимок возможностей бинарника. Видите WriteProcessMemory или VirtualAlloc — программа может модифицировать собственный код в рантайме, и чисто статического анализа может не хватить.
Центральная панель отображает дизассемблированный код: адреса слева, мнемоники справа (MOV, CMP, JNE, CALL). Для начинающего этот вид пугает — и это нормально. В 90% задач начального уровня читать ассемблер напрямую не придётся, потому что рядом есть декомпилятор. Но одна штука из Listing критически полезна: правый клик → Patch Instruction — менять инструкции прямо в листинге. Пригодится для быстрого решения задач через патчинг.
Навигация: двойной клик на адресе CALL переносит внутрь вызываемой функции, Alt+← возвращает назад. Эти два действия — основа перемещения по бинарнику.
Правая панель — декомпилированный код, ваше главное оружие при работе с Ghidra. Инструмент берёт ассемблерный код функции и генерирует C-подобный псевдокод. Не идеальный, но читаемый.
Подсветка строки в декомпиляторе подсвечивает соответствующие инструкции в Listing — и наоборот. Видите if (iVar1 == 0) справа → слева подсветятся CMP и JNZ. Через десяток таких сопоставлений связь между C-кодом и ассемблером становится интуитивной. Для тех кто только начинает анализ исполняемых файлов — бесценный механизм обучения. Буквально «ассемблер на пальцах».
Открывается через Window → Defined Strings. Показывает все текстовые строки из бинарника: сообщения пользователю, пароли, пути к файлам, иногда сам флаг. Двойной клик на строке перемещает к её расположению в бинарнике. Правый клик → References → Show References to Address покажет, какие функции используют строку. Разбор бинарника CTF часто начинается именно отсюда: нашли «Wrong password» → перешли по xref → оказались в целевой функции.
Декомпилятор бинарного кода не знает, как автор назвал переменные и типы данных. Вместо password вы увидите local_28, вместо char input[100] — что-то вроде char local_78[104]. Глобальные переменные получают имена по адресу памяти: DAT_00104020 — ячейка по адресу 0x00104020. Тип undefined8 — это 8 байт данных, тип которых Ghidra не определила автоматически (для возвращаемого значения main это практически всегда int).
Переименование переменных — первое, что нужно делать после открытия функции в декомпиляторе. Правый клик на переменную → Rename Variable (или клавиша L). Увидели, что local_28 передаётся вторым аргументом в strcmp? Переименуйте в user_input. Строка по адресу DAT_00104020 содержит текст «Enter password:»? Переименуйте в prompt_msg. Ghidra синхронизирует имена между декомпилятором и листингом автоматически.
После 5–10 переименований псевдокод из нечитаемой каши превращается в понятную программу. Буквально ощущение, будто туман рассеивается.
Ещё два приёма, ускоряющих работу:
FUN_00401234 — те, которые Ghidra не смогла сопоставить с известными библиотечными вызовами. Двойной клик переносит к их декомпилированному коду. Часто это пользовательские функции проверки пароля или шифрования.Код из окна декомпилятора копируется напрямую — удобно для CTF-отчётов и writeup-ов.
Типичная задача категории reverse для начинающих: дан бинарник, нужно найти флаг. Программа либо проверяет пароль и выводит флаг при правильном вводе (password checker), либо валидирует введённый флаг на корректность (flag checker). Разберём оба сценария.
Допустим, strings и ltrace не дали результата: пароль не лежит открытым текстом, ltrace показал вызов strcmp с нечитаемым аргументом. Переходим к Ghidra.
Импортируем бинарник, запускаем автоанализ. В Symbol Tree → Functions находим main. Двойной клик — декомпилятор показывает псевдокод. Вот как может выглядеть простой password checker после переименования переменных:
void main(void) {
char user_input[104];
puts("Enter password:");
gets(user_input); // gets() удалена из glibc 2.32+; в современных CTF чаще fgets()
result = strcmp(user_input, "S3cr3tP@ss");
if (result == 0) {
puts("Correct! Flag: flag{r3v3rs3_101}");
} else {
puts("Wrong!");
}
}
Логика очевидна: программа принимает ввод через gets, сравнивает со строкой S3cr3tP@ss через strcmp. Совпало — выводит флаг. Задача решена чтением одной функции в декомпиляторе. Без Ghidra пришлось бы разбирать ассемблерные инструкции MOV, LEA, CALL — для новичка это на порядок сложнее.
Если main в Symbol Tree нет — не паникуйте. Откройте Defined Strings, найдите строку «Wrong» или «Correct», перейдите по xref к функции, которая её использует. Вот ваша целевая функция.
Второй путь к флагу — инверсия условия проверки. В листинге после инструкции CMP (сравнение) стоит JNZ или JNE (jump if not zero/not equal). Правый клик → Patch Instruction → замените JNZ на JZ (jump if zero). Экспортируйте пропатченный бинарник: File → Export Program → формат Original File. Запустите — программа примет любой пароль и выведет флаг.
На CTF оба подхода засчитываются. Грязный путь — не позорный. Позорный — это когда два часа ковыряете алгоритм, а можно было инвертировать один байт. На блиц-раундах время решает: видите простую проверку — патчите и двигайтесь дальше. Разобрать алгоритм можно после турнира, для writeup-а.
Задачи посложнее не хранят пароль открытым текстом — strings тут бессильна. Частый паттерн в CTF reverse — XOR-кодирование: каждый байт ввода побитово XOR-ится с ключом и сравнивается с массивом эталонных байт.
В декомпиляторе Ghidra это выглядит как цикл, где каждый символ ввода проходит XOR с константой:
// Типичный XOR-checker (псевдокод декомпилятора)
char encoded[] = {0x32, 0x26, 0x30, 0x2b};
for (i = 0; i < len; i++) {
if ((user_input[i] ^ 0x41) != encoded[i]) {
puts("Wrong!");
return;
}
}
Чтобы восстановить пароль, применяем обратную операцию: encoded[i] ^ key. XOR обратим — A ^ K = B означает B ^ K = A. Массив закодированных байт и ключ копируете прямо из окна декомпилятора: двойной клик на глобальной переменной (например, DAT_00104040) покажет содержимое в hex-виде в листинге. Восстановление в Python — три строчки:
encoded = [0x32, 0x26, 0x30, 0x2b]
key = 0x41
print(''.join(chr(b ^ key) for b in encoded))
Помимо XOR, в CTF-задачах начального уровня встречаются: сдвиг символа на фиксированное число (ROT-N, частный случай — ROT13), побайтовое сложение или вычитание, побитовые сдвиги. Подход одинаковый: из декомпилятора извлекаете алгоритм преобразования и эталонные данные, затем пишете обратное преобразование. Иногда эталонные данные хранятся не в коде, а в секции данных — ищите их через перекрёстные ссылки из целевой функции.
Нюанс: если вместо простого strcmp или побайтового сравнения вы видите вызов FUN_00401300 — загляните внутрь двойным кликом. Часто это самописная функция сравнения, и её анализ раскрывает логику всей проверки.
В ряде CTF-задач бинарник скомпилирован с флагом strip — из него удалены символы отладки. В Symbol Tree нет main, нет осмысленных имён функций — только FUN_00401XXX и entry. Stripped Payloads (T1027.008, Defense Evasion по MITRE ATT&CK) — это не только CTF-приём: реальное вредоносное ПО почти всегда stripped.
Как найти main в stripped ELF-бинарнике:
entry — это _start, точка входа ELF-файла. Она вызывает __libc_start_main.entry содержит вызов вида __libc_start_main(FUN_00401156, ...). Первый аргумент — адрес main.FUN_00401156 двойным кликом, правый клик → Rename Function → задайте имя main.Для PE-файлов подход аналогичный: entry вызывает runtime-инициализацию CRT, которая в конце передаёт управление в пользовательскую функцию. Ищите CALL к функции, которая не является частью стандартной библиотеки.
Более быстрый способ (и часто более надёжный): откройте Defined Strings, найдите характерные строки задачи («Enter password», «Correct», «Wrong»), перейдите по xref к функции-обработчику. Перекрёстная ссылка работает независимо от наличия символов — даже в полностью stripped бинарнике строки остаются читаемыми, если не применено дополнительное шифрование.
Вопрос «Ghidra или IDA?» задаёт каждый, кто начинает реверс-инжиниринг для начинающих. Короткий ответ — Ghidra для старта, IDA как дополнение.
| Критерий | Ghidra | IDA Free |
|---|---|---|
| Цена | Бесплатно, open source | Бесплатно (Pro — платная) |
| Декомпилятор | Да, встроенный | Нет (Hex-Rays только в Pro) |
| Скорость запуска | Медленнее из-за JVM | Быстрее, нативное приложение |
| Поддержка архитектур | x86, ARM, MIPS, PPC, AVR и др. | x86, ARM, MIPS и др. |
| Скриптинг | Java, Python | IDAPython |
| Graph View | Через Window → Function Graph | По умолчанию |
IDA Free заметно шустрее при анализе небольших CTF-бинарников: загрузка типового PE-файла занимает секунды, Ghidra на том же файле тратит больше времени из-за старта JVM. На блиц-раундах CTF это ощутимо. Но отсутствие декомпилятора в бесплатной IDA — для новичков критический недостаток: без псевдокода придётся читать чистый ассемблер.
Отдельно стоит упомянуть radare2 (текущая версия 6.2.3 в master-ветке) — консольный фреймворк с крутой кривой обучения. Для первого знакомства с анализом бинарников — не лучший выбор. Но когда понадобится автоматизировать рутину через скрипты, radare2 станет незаменим.
На CTF держите оба инструмента. Ghidra — для декомпиляции и глубокого статического анализа, IDA Free — для быстрой проверки гипотез и навигации по графу. Ограничивать себя одним дизассемблером на старте — ошибка: каждый подсвечивает то, что второй может пропустить.
За время менторства в CTF-команде собрался устойчивый список ошибок. Совершает их каждый новичок — без исключений.
Паника при виде ассемблера. Открыли Listing, увидели MOV, CMP, LEA — и решили, что нужно выучить весь набор инструкций x86 прежде чем продолжать. Не нужно. Откройте декомпилятор и работайте с псевдокодом. Понимание ассемблера придёт постепенно — через сопоставление двух окон.
Пропуск разведки. Прыгать сразу в Ghidra без file и strings — потеря времени. На задачах начального уровня strings решает задачу в 20–30% случаев. Тридцать секунд в терминале экономят тридцать минут в дизассемблере.
Работа с «грязным» псевдокодом. Видите local_28, iVar1, DAT_00104020 — и пытаетесь понять логику, не переименовывая ничего. Через пять минут — каша в голове. Переименование — не косметика. Пять минут на осмысленные имена экономят полчаса на анализ.
Игнорирование Defined Strings. Окно со строками — самый быстрый путь к целевой функции при разборе бинарника CTF. Нашли «Wrong», «Correct», «Access Denied» → перешли по xref → вы внутри нужной функции. Не начинайте с main, начинайте со строк — особенно в stripped-бинарниках.
Попытка понять всё. В CTF-бинарнике может быть 200 функций, из которых 190 — стандартная библиотека. Ваша цель — одна-две пользовательские функции. Не тратьте время на __libc_start_main, __do_global_dtors_aux или deregister_tm_clones. Они не имеют отношения к задаче.
Отказ от патчинга. На CTF нет штрафа за «грязное» решение. Видите JNZ после сравнения и понимаете, что инверсия условия даст флаг — патчите, не раздумывая. Алгоритм разберёте для writeup-а после турнира.
Отсутствие заметок. Через 20 минут анализа вы забудете, зачем заходили в FUN_00401300. Ghidra позволяет добавлять комментарии: правый клик → Set Pre-Comment или Set Post-Comment. Используйте — это не тест на память.
Реверс-инжиниринг — навык, который строится через практику, и никак иначе. На crackmes.one лежат сотни задач с фильтрацией по сложности — начинайте с уровня 1 и 2, переходите к 3 после десятка решённых. На PicoCTF секция reverse обновляется ежегодно и заточена под тех, кто держит дизассемблер в руках впервые.
Большинство людей бросают реверс после первых двух-трёх неудач. Загрузили бинарник, не поняли вывод декомпилятора, закрыли Ghidra и забыли. Проблема не в сложности реверса и не в инструменте — а в отсутствии повторяемой методологии. Последовательность «строки → xref → целевая функция → переименование → логика» закрывает 80% задач начального и среднего уровня. Отработайте её до автоматизма на пяти задачах — и шестая пойдёт за десять минут.
Ещё момент, который редко проговаривают: реверс не требует знания ассемблера наизусть. Нужно умение читать псевдокод декомпилятора и задавать правильные вопросы бинарнику. Что принимает на вход эта функция? С чем сравнивает результат? Какое преобразование применяет к вводу? Три вопроса — и подавляющее большинство CTF reverse заданий раскрываются. Если только начинаете путь в ИБ и хочется пройти его системно, а не собирать по обрывкам — на IB Basics есть трек с нуля до первых практических задач: codeby.school/ib-basics.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...