Главная / Блог / Ghidra reverse engineering для начинающих: декомпилируем бинарник и находим пароль в CTF crackme

15 мин.00

Ghidra reverse engineering для начинающих: декомпилируем бинарник и находим пароль в CTF crackme

Ghidra reverse engineering для начинающих: декомпилируем бинарник и находим пароль в CTF crackme

Ghidra reverse engineering для начинающих: декомпилируем бинарник и находим пароль в CTF crackme

На последнем воркшопе по реверсу три из десяти участников застряли на первой же задаче: ELF-бинарник просил пароль, Ghidra показывала декомпилированный код, но вместо привычного C — стена из local_38, param_1, DAT_001020a0. Через пятнадцать минут разбора каждый решил задачу. Проблема была не в сложности бинарника — просто никто до этого не объяснил, как читать то, что выплёвывает декомпилятор. Эта статья — тот самый разбор, от запуска Ghidra до найденного пароля, без пропущенных шагов.

Требования к окружению и установка Ghidra

Что понадобится для работы

Прежде чем импортировать первый бинарник, убедитесь, что машина потянет анализ. Ghidra написана на Java и при активной декомпиляции жрёт ресурсов ощутимо больше, чем кажется по размеру архива: Подробнее — в нашем обзоре бинарный анализ уязвимостей.

  • ОС: Windows 10/11, Linux (Ubuntu 20.04+, Kali, Fedora), macOS 12+
  • RAM: минимум 4 ГБ, рекомендуется 8 ГБ — при декомпиляции бинарников крупнее 1 МБ расход памяти растёт кратно
  • Диск: около 1 ГБ на Ghidra плюс пространство для проектов (каждый проект — десятки мегабайт)
  • JDK: версия 17+, 64-бит. Рекомендуется Adoptium Temurin — по данным Securelist, он стабильно работает с Ghidra без сюрпризов
  • Виртуальная машина: обязательна для запуска анализируемых бинарников. Даже задачи с CTF-площадок вроде crackmes.one не запускайте на основной системе — привычка, которая однажды спасёт
  • Интернет: нужен только для скачивания, весь статический анализ исполняемых файлов работает полностью offline

Установка за пять минут

Скачайте ZIP-архив последнего релиза с официального GitHub-репозитория Ghidra. Никакого инсталлятора — распакуйте архив и запустите ghidraRun (Linux/macOS) или ghidraRun.bat (Windows). При первом запуске укажите путь к JDK. Ghidra активно поддерживается: коммиты в репозитории появляются еженедельно, релизы выходят каждые несколько месяцев.

Для сборки из исходников (свежая master-ветка с последними исправлениями) потребуется Gradle версии, указанной в DevGuide.md репозитория. Команды: gradle -I gradle/support/fetchDependencies.gradle init (загрузка зависимостей), затем gradle buildGhidra — дистрибутив окажется в build/dist. На Linux с русской локалью GCC может споткнуться о переведённые сообщения — лечится префиксом LANG=C gradle .... Но для Ghidra обучения и первых CTF задач готового релиза хватит за глаза — сборка из исходников оправдана, когда нужны свежие фиксы декомпилятора.

Первый проект в Ghidra: импорт и автоанализ бинарника

Запустили Ghidra — перед вами окно Project Manager. Создайте проект: File → New Project → Non-Shared Project. Укажите папку и имя (например, ctf_practice). Проект в Ghidra — контейнер для всех результатов анализа: дизассемблированный код, переименованные переменные, комментарии. Работает как IDB-файл в IDA Pro, но хранит данные для нескольких бинарников сразу, поддерживает совместную работу через удалённую базу и позволяет переиспользовать библиотеки типов между файлами.

Импорт файла и автоанализ

Перетащите файл crackme в окно проекта или выберите File → Import File. Ghidra сама определит формат (ELF, PE, Mach-O) и покажет диалог с метаданными: архитектура (x86-64, ARM, MIPS), компилятор (GCC, MSVC), тип линковки (static/dynamic). Для типичного CTF-бинарника увидите ELF / x86:LE:64:default / GCC. Жмём OK.

Ghidra предложит запустить автоматический анализ — соглашайтесь. Набор анализаторов сделает основную грязную работу: найдёт функции, построит перекрёстные ссылки (cross-references, они же xrefs), вытащит строки, восстановит сигнатуры стандартных библиотечных вызовов. На crackme размером 10–50 КБ анализ занимает секунды. Дефолтные настройки подходят для CTF reverse engineering задач в подавляющем большинстве случаев — не трогайте ничего, пока не разберётесь, за что отвечает каждый анализатор.

Навигация по CodeBrowser

Двойной клик на импортированном файле открывает CodeBrowser — основное рабочее окно для анализа бинарных файлов. Ключевые панели:

Listing (центр) — дизассемблированный код. Инструкции mov, cmp, call, jz — машинный язык процессора в читаемом виде.

Decompiler (справа) — псевдокод C, восстановленный декомпилятором. Если окно не открыто: Window → Decompile. Это главный инструмент для разбора логики — именно здесь вы будете проводить 80% времени.

Symbol Tree (слева) — дерево функций, импортов, экспортов. Ищите main здесь — это точка входа программы. Если бинарник «not stripped», имена функций будут человекочитаемыми.

Defined Strings — список строк, найденных в бинарнике. Открывается через Window → Defined Strings. Именно с этого окна начинается 90% разборов CTF задач — строки типа «Enter password» или «Wrong!» ведут прямо к интересующей функции.

Все окна синхронизированы: клик на строку в декомпиляторе подсвечивает соответствующий блок ассемблера, и наоборот. Удобно переключаться между высокоуровневым псевдокодом и низкоуровневым листингом, когда что-то выглядит подозрительно.

Декомпиляция бинарника: как читать псевдокод C в Ghidra

Это ключевой навык для решения CTF reverse engineering задач. Псевдокод C декомпилятора — не исходный код: компилятор необратимо уничтожает имена переменных, комментарии, часть типовой информации. Но для понимания логики crackme этого уровня абстракции хватает с запасом. Декомпилятор Ghidra восстанавливает циклы, условия, вызовы функций — и позволяет найти проверку пароля, не читая ни строки ассемблера.

Расшифровка автоматических имён переменных

Когда декомпилятор показывает local_38, iVar1, param_2 — это не шифр. Это автоименование по фиксированным правилам:

param_1, param_2 — параметры текущей функции. В main(int param_1, char **param_2) первый — argc (число аргументов командной строки), второй — argv (массив строк-аргументов). Если функция принимает один аргумент типа указатель — param_1 и будет этим указателем.

local_XX — локальные переменные на стеке. Число XX — смещение от начала стекового кадра в hex. local_38 означает: переменная лежит на расстоянии 0x38 (56) байт от базового указателя rbp. Размер намекает на тип: local_10 на x86-64 (8 байт) — скорее всего указатель или long; local_1 (1 байт) — char или bool; local_38 с объявлением char[40] — строковый буфер.

iVar1, uVar2, cVar3 — временные переменные. Префикс указывает тип: i = int, u = unsigned int, l = long, c = char, b = bool. Ghidra выводит тип из контекста использования.

DAT_001020a0 — глобальная переменная или константа по фиксированному адресу в памяти. Двойной клик перенесёт к этому адресу в листинге, где часто обнаруживается строковая константа с паролем или сообщением. Вот тут-то и начинается самое интересное.

FUN_00101234 — функция, для которой имя не удалось определить (символьные таблицы ELF отсутствуют или stripped). Число — адрес точки входа.

Переименование — основной метод анализа

Правый клик на переменной → Rename Variable (или клавиша L) — и local_38 превращается в user_input. Это не косметика: переименование делает логику программы читаемой для человека. Ghidra сохраняет все переименования в проекте и обновляет каждое вхождение по всему коду. Аналогично работает с функциями: FUN_00101234check_password — и каждый вызов этой функции в проекте станет осмысленным.

Начинайте с того, что уже понимаете. Видите printf("Enter password: ") — значит, следующая переменная, куда пишет fgets или scanf, это буфер пользовательского ввода. Переименуйте. Видите strcmp() — переименуйте его аргументы. Через десять переименований загадочная стена превращается в понятную программу.

Четыре паттерна проверки пароля в crackme

В задачах по обратной разработке проверка реализуется одним из характерных способов:

  1. Прямое сравнение строк: вызов strcmp() или strncmp() с захардкоженной строкой. Простейший вариант — пароль лежит в открытом тексте прямо в бинарнике.
  2. Посимвольное сравнение: цикл, где каждый символ ввода проверяется через == с эталонным значением. Пароль разбросан по коду, но каждый символ виден.
  3. XOR или арифметическая трансформация: ввод пропускается через XOR, сложение, побитовый сдвиг и сравнивается с зашифрованным эталоном. Нужно обратить операцию.
  4. Хеширование: от ввода вычисляется хеш (CRC32, MD5, SHA) и сравнивается с константой. В разборе FatMike's CrackMe (fewstreet.com) автор обнаружил именно CRC32-проверку — по характерной константе-полиному 0xedb88320 в коде. Увидите такое число — знайте, это CRC32.

Crackme разбор Ghidra: пошаговый поиск проверки пароля в бинарнике

Разберём типовой crackme — ELF-бинарник x86-64, который при запуске просит ввести пароль и выводит результат проверки. Такие задачи составляют основу категории reverse на PicoCTF, HackTheBox и crackmes.one.

Шаг 1. Разведка до Ghidra

Перед загрузкой в дизассемблер выполните две команды в терминале. Первая — file crackme — покажет тип: ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped. Фраза «not stripped» — хорошая новость: символьные таблицы ELF сохранены, имена функций (main, check, verify) будут видны в Symbol Tree. Stripped-бинарник — дополнительная головная боль. Кстати, та же техника (намеренное удаление символов) используется атакующими в малвари — MITRE ATT&CK описывает её как Stripped Payloads (T1027.008).

Вторая команда — strings crackme | grep -i "pass\|flag\|correct\|wrong" — иногда сразу показывает строки-маркеры. Но полагаться только на strings не стоит: в задачах посложнее строки зашифрованы или генерируются в рантайме.

Шаг 2. Defined Strings — отправная точка анализа

В CodeBrowser откройте Window → Defined Strings. Ищите строки, связанные с результатом: «Access granted», «Wrong password», «Correct», «Try again». Нашли «Wrong password!»? Это якорь — теперь нужно определить, какая функция ссылается на эту строку.

Шаг 3. Перекрёстные ссылки

Кликните на найденную строку правой кнопкой → References → Show References to Address. Ghidra покажет список мест в коде, где строка упоминается — обычно одно или два. Двойной клик перенесёт в функцию с логикой проверки. Метод поиска через строки и xrefs — фундаментальный приём статического анализа, работающий в любом дизассемблере: Ghidra, IDA, Radare2.

Шаг 4. Чтение логики в декомпиляторе

В сыром виде перед переименованием Ghidra покажет примерно следующее:

undefined8 FUN_00101169(void) {
    char local_38[40];
    printf("Enter password: ");
    fgets(local_38, 0x28, _stdin);
    if (strcmp(local_38, &DAT_00102010) == 0) {
        puts(&DAT_00102025);
    } else {
        puts(&DAT_00102035);
    }
    return 0;
}

Вот она — та самая «стена», на которой застревают новички. Ключевое действие: двойной клик на DAT_00102010. Ghidra перенесёт к адресу, где хранятся данные — и вы увидите строку-пароль в виде ASCII-текста. После переименования переменных и функций (клавиша L на каждом элементе) тот же код превращается в понятный псевдокод:

void main(void) {
    char user_input[40];
    printf("Enter password: ");
    fgets(user_input, 0x28, stdin);
    if (strcmp(user_input, "CTF{r3v3rs3_101}") == 0) {
        puts("Access granted!");
    } else {
        puts("Wrong password!");
    }
}

Логика прозрачна: fgets читает ввод в буфер, strcmp сравнивает со строкой "CTF{r3v3rs3_101}". Совпадение (возвращается 0) — доступ открыт. Пароль найден без чтения ассемблера.

Шаг 5. Верификация

Запустите бинарник в виртуальной машине и введите найденный пароль. Если не срабатывает — вероятно, fgets читает ввод вместе с символом \n, а strcmp сравнивает строго. Передайте пароль через echo -n "CTF{r3v3rs3_101}" | ./crackme — флаг -n убирает перенос строки.

Обфусцированные строки и упакованные бинарники в CTF

Не каждый crackme отдаёт пароль через Defined Strings. Если список строк пуст или содержит мусор — бинарник, вероятно, обфусцирован или упакован. Тут начинается следующий уровень.

Упаковщики: UPX и аналоги

Откройте Program Tree в левой панели CodeBrowser. Если вместо стандартных секций .text, .data, .rodata видны секции UPX0, UPX1 — бинарник сжат упаковщиком UPX. В классификации MITRE ATT&CK это Software Packing (T1027.002): реальный код сжат и распаковывается в память при запуске. Автор CTF writeup по FatMike's CrackMe (fewstreet.com) столкнулся именно с этим: Ghidra не нашла ни одной функции кроме _entry, потому что весь значимый код был запакован.

Решение для UPX тривиально: ставите его (apt install upx на Kali/Ubuntu) и выполняете upx -d crackme -o crackme_unpacked. Загружаете распакованный файл в Ghidra — строки и функции появятся. Для нестандартных упаковщиков без публичного распаковщика потребуется ручная распаковка или динамический анализ с дампом памяти — но это уже тема для отдельного разбора.

XOR-шифрование строк

В задачах среднего уровня пароль или флаг хранится в зашифрованном виде и расшифровывается перед сравнением. Типичный признак в декомпиляторе — цикл, где каждый байт массива XOR'ится с ключом: конструкция вида result[i] = encrypted[i] ^ key[i % key_len]. MITRE ATT&CK описывает это как Deobfuscate/Decode Files or Information (T1140). В разборе FatMike's CrackMe автор нашёл именно такой XOR-цикл с 24-символьным ключом. Видите подобный паттерн — извлеките массив encrypted и значение key из бинарника и напишите три строки на Python для расшифровки. Серьёзно, три строки.

Антиотладка

Некоторые crackme проверяют наличие отладчика: вызов ptrace(PTRACE_TRACEME) в Linux, IsDebuggerPresent() в Windows, чтение /proc/self/status на предмет TracerPid. MITRE ATT&CK классифицирует это как Debugger Evasion (T1622). (Кстати, в Atomic Red Team тесты для T1622 реализованы только под Windows, хотя сама техника применима и на Linux.) При статическом анализе в Ghidra эти проверки видны в псевдокоде, но не срабатывают — Ghidra не запускает код. Антиотладочные трюки на статический анализ не действуют. Это важное преимущество подхода: вы препарируете бинарник в безопасной среде, без риска выполнения чего-то неприятного.

Reverse engineering инструменты: Ghidra, IDA Free, Radare2

Ghidra — не единственный выбор для обратной разработки. Trade-off таблица для осознанного выбора инструмента:

Критерий Ghidra IDA Free Radare2
Стоимость Бесплатно, open source Бесплатно (урезанная версия) Бесплатно, open source
Декомпилятор Встроенный, полноценный Нет (IDA Pro от $500+) Через плагины (r2ghidra, r2dec)
Интерфейс GUI на Java Нативный GUI Командная строка
Порог входа Средний Низкий Высокий (сотни команд)
Встроенный отладчик Экспериментальный Нет (в бесплатной версии) Полноценный
Скриптинг Java, Python IDAPython доступен Python, множество языков
Поддержка архитектур Десятки (x86, ARM, MIPS, PPC) Ограничен в Free Десятки
Статус поддержки Активный, еженедельные коммиты Коммерческий продукт Активная разработка
Когда использовать CTF, обучение, анализ прошивок, малвари Быстрый визуальный анализ графов Автоматизация, скриптинг, CLI-workflow
Когда НЕ использовать Нужен надёжный встроенный отладчик Нужна декомпиляция бесплатно Первое знакомство с RE

Для CTF reverse engineering задач Ghidra — оптимальный выбор. Бесплатный декомпилятор — решающее преимущество. Лицензия IDA Pro стоит от $500 (по данным blog.ry4n.org), что делает её недоступной для студентов и тех, кто только входит в реверс. IDA Free даёт отличный graph view для визуализации потока управления, но без декомпилятора решение задач требует чтения чистого ассемблера — а это совсем другой уровень. Radare2 мощен для автоматизации через скрипты, но его CLI-интерфейс с сотнями однобуквенных команд — слишком крутой порог для новичка. Если в будущем понадобится динамический анализ — добавьте GDB (Linux) или x64dbg (Windows) как отдельный инструмент.

Ограничения декомпилятора Ghidra

Знать, где инструмент врёт, важнее, чем знать его возможности. Вот что регулярно ломается при декомпиляции бинарника в Ghidra:

Неверные типы данных

Декомпилятор путает типы указателей: массив char[] отображается как int*, структура — как набор отдельных переменных. В разборе CrackMe от FatMike (fewstreet.com) автор обнаружил, что Ghidra трактовала массив из 24 символов как 24 отдельных глобальных переменных (DAT_xxxxxx, DAT_xxxxxx+1, ...). Решение: выделите адрес в листинге, нажмите C (Clear), затем T (Retype) и задайте char[24]. Псевдокод C мгновенно станет читаемым.

Пропуск функций

Автоанализ иногда не распознаёт блок байтов как функцию — особенно при косвенных вызовах или обфускации. В том же FatMike's CrackMe Ghidra не нашла перекрёстных ссылок на ключевую функцию check_input, потому что вызов был замаскирован внутри switch-конструкции. Решение: перейдите к неразобранным байтам, нажмите D (Disassemble), затем F (Define Function). После переанализа (Analysis → Auto Analyze) связи восстановятся.

Оптимизации компилятора

Компилятор с флагом -O2 или -O3 инлайнит функции, разворачивает циклы, переставляет инструкции для конвейера процессора. Декомпилятор пытается восстановить логику, но результат может быть далёк от оригинала. Если конструкция в псевдокоде выглядит абсурдно — переключитесь на листинг ассемблера. Ассемблер отражает реальный машинный код и никогда не врёт.

Статический анализ — не панацея

Ghidra не запускает код. Если пароль генерируется динамически — на основе текущего времени, MAC-адреса, идентификатора системы — статический анализ покажет алгоритм генерации, но не конечное значение. Для таких случаев нужен динамический анализ: отладчик GDB или x64dbg, точка останова на момент сравнения, чтение значения из регистра. Статический и динамический подходы дополняют друг друга — большинство реальных задач требует обоих.

Чеклист: от бинарника до пароля за десять действий

Минимальный алгоритм решения типового crackme:

  1. Определить формат: file crackme
  2. Быстрый поиск строк: strings crackme | grep -i "pass\|flag"
  3. Создать проект в Ghidra, импортировать бинарник
  4. Запустить автоанализ с дефолтными настройками
  5. Открыть Defined Strings — найти строки-маркеры результата проверки
  6. Через References → Show References to Address перейти к функции
  7. Прочитать псевдокод: искать strcmp, strncmp, циклы сравнения
  8. Переименовать переменные и функции для ясности (клавиша L)
  9. Если строки не найдены — проверить Program Tree на UPX, распаковать
  10. Верифицировать: запустить бинарник в VM и ввести найденный пароль

Навыки чтения псевдокода и работы с перекрёстными ссылками из этого разбора — не только CTF. Те же приёмы работают при анализе реальной малвари: вредоносы хранят адреса C2-серверов в захардкоженных строках (MITRE ATT&CK: Credentials In Files, T1552.001), прячут конфигурацию за XOR-обфускацией (T1027, Obfuscated Files or Information), пакуют код UPX'ом или кастомным паковщиком. Разница между CTF-задачей и реальным сэмплом — в масштабе и уровне защиты, не в методе.

По моему опыту, основной барьер для входа в обратную разработку — не инструменты и не ассемблер. Барьер — непонимание C. Большинство людей, которые «не могут» решить reverse-задачу, не знают, что strcmp возвращает 0 при совпадении строк. Не понимают, чем fgets отличается от scanf. Не видели, как for-цикл превращается в пару cmp/jl инструкций. Мой совет неизменен уже второй год: прежде чем учиться реверсу, потратьте неделю на C. Напишите программу, которая принимает пароль через argv, проверяет его и печатает результат. Скомпилируйте с -O0 и с -O2. Откройте оба варианта в Ghidra. Посмотрите, во что превратился ваш код. Когда увидите собственный printf("Hello") в виде puts("Hello") после оптимизации — декомпилятор перестанет быть чёрным ящиком. Обратная разработка начинается с прямой: кто не писал код, тот не прочтёт его после компилятора. Если хочешь пройти этот путь системно, а не собирать по кускам — на IB Basics закрывают базу за два месяца, от нуля до первых реальных задач.

🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».

Поделиться

0 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...