
На последнем CTF нашей команде попался reverse-таск на 200 очков: ELF-бинарник, 14 КБ, stripped, без внешних зависимостей. Три человека бились час — один в radare2, второй в IDA Free, третий пытался трейсить в GDB. Ghidra решила задачу за восемь минут: строка «Access Granted» нашлась через Defined Strings, xref привёл к функции-обработчику, декомпилятор показал strcmp с захардкоженной строкой. Весь секрет не в инструменте, а в методологии: куда смотреть первым делом и как читать то, что выплёвывает декомпилятор. Этот ghidra туториал — пошаговый разбор crackme, от импорта бинарника до извлечения флага, с объяснением каждого действия и типичных ловушек, в которые новички влетают раз за разом.
Прежде чем открывать Ghidra — подготовьте рабочее место. Без нормального окружения анализ исполняемого файла в Ghidra рискует упереться в технические проблемы на первом же шаге. Подробнее — в нашем обзоре бинарный анализ уязвимостей.
Требования к окружению:
apt install openjdk-17-jdk на Ubuntu).ghidraRun (Linux) или ghidraRun.bat (Windows).Для практики возьмите любой crackme начального уровня с crackmes.one (difficulty 1-2) или из репозитория hackaday-u — там подготовлены упражнения специально для обучения reverse engineering crackme: нужно найти пароль или обойти проверку, сложность нарастает от сессии к сессии.
Частая ошибка новичка — сразу тащить бинарник в дизассемблер. Пять минут разведки экономят час мучений внутри Ghidra.
Шаг 1. Команда file. Запустите file crackme_binary в терминале. Команда покажет архитектуру (x86, x86-64, ARM), формат (ELF, PE), тип линковки (static, dynamic), наличие stripped-символов. Эта информация определяет, чего ожидать внутри Ghidra: stripped-бинарник потребует дополнительных шагов для поиска main.
Шаг 2. Команда strings. Прогоните strings crackme_binary | grep -i "pass\|flag\|correct\|wrong" — иногда флаг или подсказка лежат в открытом виде. На CTF это встречается чаще, чем хочется признавать. Ищите характерные паттерны: flag{, Correct!, Wrong password, Try Again. Если strings не выдаёт ничего читаемого — бинарник запакован или строки зашифрованы.
Шаг 3. Hex-редактор. Откройте файл в hex-редакторе и посмотрите первые байты. Сигнатуры пакеров видны сразу: UPX! для UPX, ASPack для ASPack. Нашли пакер — значит прямой разбор crackme в Ghidra покажет только распаковщик, а не реальную логику.
Предусловия и ограничения: описанный подход к разведке работает для нативных ELF и PE бинарников. Для .NET-сборок, Java JAR, Python-байткода нужны специализированные инструменты: dotPeek, JD-GUI, pycdc/decompyle3 соответственно (uncompyle6 не поддерживает Python 3.9+). Ghidra умеет анализировать Java и Dalvik-байткод, но методология поиска флага там другая.
Шаг 1. Запустите Ghidra, создайте проект: File → New Project → Non-Shared Project. Дайте проекту осмысленное имя — «CTF_TaskName» удобнее, чем «New Project». Укажите каталог для хранения файлов проекта.
Шаг 2. Перетащите бинарник в окно проекта. Ghidra автоматически определит формат и архитектуру — для типовых ELF и PE этого достаточно. В окне импорта увидите: формат файла, процессорную архитектуру, размер, точку входа. Нажмите OK. Ожидаемый результат: файл появится в списке проекта с иконкой, соответствующей его типу.
Шаг 3. Двойной клик по файлу — откроется Code Browser. Всплывающее окно предложит авто-анализ. Соглашайтесь с настройками по умолчанию. Для Windows PE-файлов рекомендую дополнительно включить опцию «WindowsPE x86 Propagate External Parameters» — по данным руководства Varonis, параметры импортированных функций отобразятся прямо в листинге, что заметно упрощает чтение кода.
Шаг 4. Дождитесь окончания авто-анализа — прогресс виден в правом нижнем углу Code Browser. На бинарнике 50 КБ это секунды, на мегабайтных файлах — минуты. Не переходите к анализу, пока полоса прогресса не исчезнет: иначе функции и перекрёстные ссылки будут определены не полностью.
Для решения crackme CTF задач в Ghidra нужны три окна. Если какое-то из них не видно — откройте через меню Window.
Symbol Tree (левая панель) — дерево символов: импорты, экспорты, обнаруженные функции. Разверните Imports: если видите scanf, fgets, GetDlgItemTextA — бинарник читает пользовательский ввод. Видите strcmp, memcmp — где-то есть сравнение строк. Разверните Functions — здесь Ghidra покажет все обнаруженные функции. Часть будет именована (main, entry), часть получит автоматические имена вида FUN_00401234 — FUN означает function, число после — адрес в памяти.
Listing (центральная панель) — дизассемблированный код с адресами, байтами, операндами. Двойной клик на адресе в инструкции CALL переносит внутрь вызываемой функции, Alt+← возвращает назад. Для анализа ассемблерного кода это основной рабочий инструмент: здесь видно каждую инструкцию без искажений декомпилятора.
Decompiler (правая панель) — псевдокод на C, результат декомпиляции кода. Для тех, кто только начинает reverse engineering в Ghidra, это спасение. Подсветка строки в декомпиляторе автоматически подсвечивает соответствующий ассемблер в листинге. Видите if (iVar1 == 0) справа — смотрите CMP и JNZ слева. После десятка таких сопоставлений связь между C и ассемблером становится осязаемой. Нажатие F1 при наведении на любой элемент интерфейса вызывает контекстную справку — пользуйтесь, пока осваиваетесь.
Window → Function Graph открывает граф потока управления функции — аналог Graph View в IDA. Блоки кода связаны стрелками, визуально видно ветвления и циклы. По умолчанию граф открывается в уменьшенном масштабе — правый клик → Properties → Start Fully Zoomed In исправит это.
Ключевой этап всего разбора crackme. Не начинайте с main или entry — начинайте со строк. Поиск условия флага в бинарнике через строки — самый быстрый путь к результату.
Шаг 1. Откройте Defined Strings: Window → Defined Strings. Ghidra покажет все текстовые строки, обнаруженные в бинарнике. Ищите характерные маркеры: Wrong password, Try Again, Correct, Access Granted, Good work, Bad password.
Шаг 2. Нашли строку? Кликните — Listing перейдёт к её адресу в секции данных. Теперь нужно найти функцию, которая её использует. Правый клик → References → Show References to Address (или клавиша X). Появится список всех мест, где строка упоминается — перекрёстные ссылки (xref). Обычно для строки вроде «Try Again!» будет один-два xref: это и есть функция проверки.
Шаг 3. Перейдите по xref. Decompiler автоматически покажет псевдокод целевой функции. Именно здесь находится условие проверки флага — логика, которая решает, выдать «Correct» или «Wrong».
Когда техника НЕ работает: если Defined Strings пуст или содержит только мусор — строки зашифрованы, сгенерированы в рантайме или бинарник запакован. Это признак обфускации (Obfuscated Files or Information, T1027, Defense Evasion). В таком случае ищите вызовы strcmp, memcmp в Symbol Tree → Imports и идите по их xref. Если и импортов нет — бинарник использует собственные реализации сравнения, и тут уже придётся копать глубже.
Допустим, xref привёл к функции FUN_080485a5. Ghidra показывает в декомпиляторе C-подобный псевдокод. Вот типичная картина для простого crackme (адаптировано из разбора задачи ELF - CrackPass на root-me, описанного в материале FreeCodeCamp):
void CheckPassword(char *param_1) {
char generated_pwd[128], input_copy[4096];
FUN_080484f4(input_copy, param_1); // копирование ввода без проверки длины (CWE-120)
FUN_0804851c(DAT_08049960, generated_pwd); // генерация эталона
int result = strcmp(generated_pwd, input_copy);
if (result == 0)
printf("Good work: %s\n", generated_pwd);
else
puts("Wrong password!");
}
Что здесь видно: функция принимает пользовательский ввод (param_1), копирует его в буфер (FUN_080484f4 — кастомная реализация memcpy), затем генерирует эталонный пароль из захардкоженной строки DAT_08049960 и сравнивает через strcmp. Результат равен нулю — пароль верный.
Псевдокод Ghidra по умолчанию нечитаем: переменные называются iVar1, uVar2, функции — FUN_00401234. Это нормально — не пугайтесь. Вот приёмы, которые ускоряют дизассемблирование бинарного файла:
Переименование переменных: правый клик на переменной → Rename Variable. param_1 → user_input, local_108c → generated_password. Ghidra синхронизирует имена между декомпилятором и листингом автоматически. Код мгновенно становится осмысленным — я обычно трачу на это минуту-две, и дальше работать в разы приятнее.
Правка сигнатур: правый клик на функции → Edit Function Signature. Если Ghidra определила параметр как undefined4, а в него передаётся строка — измените тип на char *. Декомпилированный код станет заметно чище.
Исследование вложенных функций: двойной клик на FUN_080484f4 — внутри цикл, копирующий байты из одного буфера в другой. Кастомный memcpy, ничего интересного. Нажмите L на имени и переименуйте в custom_memcpy. Вернитесь назад Alt+← — вызов читается как custom_memcpy(input_copy, user_input). Код из окна декомпилятора копируется напрямую — удобно для CTF-отчётов и writeup'ов.
В решении crackme CTF задач логика проверки сводится к нескольким повторяющимся паттернам:
Прямое сравнение — strcmp(user_input, "secret_flag"). Пароль в открытом виде. Достаточно прочитать строку. Встречается в crackme с difficulty 1 — и да, такие задачи реально попадаются на CTF.
Трансформация и сравнение — ввод проходит через XOR, битовый сдвиг, кастомную функцию, затем сравнивается с эталоном. По данным разбора FatMike's CrackMe#1 на fewstreet.com, одна из проверок выглядела так: XOR каждого символа ввода с константным массивом, затем CRC32 от результата и сравнение с 0x5a6aa47d. Результат XOR сохранялся в отдельный буфер, а 0xedb88320 — полином CRC32. Такие задачи решаются восстановлением алгоритма и подбором (z3-солвер) или патчингом.
Многослойная проверка — несколько условий одновременно. В том же FatMike's CrackMe#1 проверялись два условия: iVar1 == 0x5a6aa47d (контрольная сумма) и DAT_0040b4ec == 0x16 (длина ввода = 22 символа). Обе проверки должны пройти, иначе бинарник показывает «Try Again!».
Предусловия и ограничения: описанные паттерны характерны для crackme уровня сложности 1-3. В более сложных задачах используются виртуальные машины для обфускации кода, многопоточные проверки, самомодифицирующийся код — там уже совсем другая история.
Когда вычислить правильный ввод сложно или долго — проще обойти проверку. На CTF оба подхода засчитываются: чистое решение (вычислить пароль) и грязное (пропатчить). Патчинг бинарных файлов в Ghidra занимает минуту.
Работает если: бинарник не запакован, нет проверки собственной целостности, нет антиотладочных проверок до целевой функции. Не работает если: присутствует Software Packing (T1027.002, Defense Evasion) или бинарник проверяет хеш собственного кода перед исполнением.
Шаг 1. Найдите условный переход в листинге. В декомпиляторе видите if (result == 0) — кликните на эту строку, в Listing подсветится инструкция JNZ (jump if not zero) или JNE (jump if not equal). Это переход к ветке «Wrong password».
Шаг 2. Правый клик на инструкции → Patch Instruction. Замените JNZ на JZ (jump if zero). Логика инвертирована: любой неправильный пароль пройдёт проверку, а правильный — нет. Альтернатива — заменить JNZ на JMP (безусловный переход), тогда ветка «Correct» выполняется всегда.
Шаг 3. Экспортируйте пропатченный бинарник: File → Export Program → выберите формат (ELF или PE Original). Запустите — программа должна принять любой ввод.
По данным разбора crackme с root-me в материале FreeCodeCamp, автор применил три патча: инвертировал проверку ptrace (JNS → JMP), обошёл валидацию символов ввода (JZ → JMP) и инвертировал финальное сравнение (JNZ → JZ). Три изменённых байта — и бинарник выдал пароль при произвольном вводе. Красиво, хоть и грязно.
Если Defined Strings пуст, а вместо функций видна только entry и блоки данных — бинарник защищён. Три основных препятствия при решении crackme CTF задач повышенной сложности.
Software Packing (T1027.002, Defense Evasion) — сжатие или шифрование исполняемого кода. В crackme чаще всего встречается UPX. Признаки в Ghidra: секции UPX0 и UPX1 в Program Tree, минимум функций (только entry), отсутствие читаемых строк. По данным разбора FatMike's CrackMe#1 на fewstreet.com, Windows Defender срабатывает на бинарники с UPX — косвенный признак пакинга.
Распаковка: утилита upx -d packed_binary.exe снимает UPX-упаковку. CFF Explorer даёт графический интерфейс: UPX Utility → Unpack → File → Save As. После распаковки заново импортируйте файл в Ghidra и повторите авто-анализ — теперь Defined Strings покажет реальные строки, в Symbol Tree появятся функции.
Debugger Evasion (T1622, Defense Evasion / Discovery) — crackme проверяет наличие отладчика. На Linux распространён вызов ptrace(PTRACE_TRACEME, 0, 1, 0) — если процесс уже трейсится, ptrace вернёт ошибку и программа завершится через abort(). (Примечание: MITRE ATT&CK описывает T1622 преимущественно в контексте Windows; ptrace концептуально подпадает под эту технику, но публичные тесты Atomic Red Team для Linux-варианта отсутствуют.) На Windows: IsDebuggerPresent() или чтение флага BeingDebugged из PEB.
При чисто статическом анализе в Ghidra это не мешает — код не запускается. Но при попытке отладки пропатченного бинарника в x64dbg или GDB антиотладочные проверки сработают. Решение: найти ptrace или IsDebuggerPresent в Symbol Tree → Imports, перейти по xref, пропатчить условный переход после вызова — точно так же, как основную проверку.
Stripped Payloads (T1027.008, Defense Evasion) — из бинарника удалены отладочные символы. В Symbol Tree нет main, все функции именуются FUN_XXXXXXXX. На CTF это стандартная ситуация, так что привыкайте.
Как найти main в stripped ELF: функция __libc_start_main сохраняется даже в stripped-бинарниках, поскольку она из динамической библиотеки libc. По данным из материала vickieli.dev, первый аргумент __libc_start_main — адрес main. В Ghidra: найдите __libc_start_main через Symbol Tree → Imports, перейдите по xref к entry. В декомпиляторе увидите вызов вида __libc_start_main(FUN_00401136, ...) — FUN_00401136 и есть main. Нажмите L и переименуйте. Дальше — стандартный путь.
Для закрепления навыков reverse engineering в Ghidra — минимальный воспроизводимый сценарий:
file, strings, hex-редактор.Репозиторий hackaday-u содержит четыре уровня упражнений с Docker-контейнером для запуска — каждое упражнение представляет собой задачу типа «найти пароль», сложность нарастает от сессии к сессии. Первый crackme этой серии проходится за 15 минут по описанной методологии. На root-me.org раздел Cracking предлагает задачи ELF разного уровня для более длительной практики.
Первый разбор займёт 30-60 минут. Третий — не больше десяти. Дело не в сложности Ghidra, а в количестве повторений.
Половина русскоязычных материалов по reverse engineering останавливается на скриншотах интерфейса Ghidra: вот Symbol Tree, вот Listing, вот кнопка Decompile. Это обзор софта, а не реверс-инжиниринг. Реверс начинается в момент, когда видишь FUN_0804851c и принимаешь решение: нырять внутрь или пропатчить переход над ней. Навык растёт от количества разобранных бинарников, не от количества прочитанных гайдов. После десятого crackme паттерны начинают повторяться — XOR с константой, CRC32 от промежуточного буфера, кастомный memcpy вместо стандартного — и псевдокод декомпилятора перестаёт выглядеть стеной из iVar.
Но есть нюанс, о котором мало кто предупреждает: декомпилятор Ghidra ошибается. Регулярно. Типы переменных определяются криво, структуры разваливаются в набор отдельных полей, иногда целые ветки кода склеиваются или исчезают. Привычка сверять псевдокод с ассемблерным листингом — не перфекционизм, а необходимость. Если декомпилятор показывает линейный код без ветвлений, а в листинге два условных перехода — верьте листингу. Декомпилятор — подсказка, не истина. Те, кто привыкает работать только с Decompiler-окном, рано или поздно упираются в задачу, где C-подобный вывод врёт, а три инструкции ассемблера содержат ответ. На WAPT эту связку «декомпилятор соврал → листинг спас» проходят с лабами в модуле по реверсу.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...