Главная / Блог / Реверс-инжиниринг в Ghidra: разбираем crackme

10 мин.00

Реверс-инжиниринг в Ghidra: разбираем crackme

Реверс-инжиниринг в Ghidra: разбираем crackme

Исследование CrackMeBench (arxiv, 2025) проверило, как LLM-агенты справляются с классическими crackme-задачами. Лучшая модель решила 3 из 8 публичных калибровочных задач при пятиминутном лимите и трёх попытках. Причины провала зафиксированы: чрезмерное доверие декомпилятору, путаница между закодированными данными и финальными строками, неверная интерпретация формата ввода-вывода. Те же ошибки совершают и новички на CTF — только у человека есть шанс выработать методологию, после чего типовой crackme решается за 10-15 минут. Разберём пошаговый реверс-инжиниринг в Ghidra на примерах трёх паттернов проверки пароля: от открытого strcmp до XOR-шифрования и кастомных хеш-функций.

Разведка до дизассемблера: file, strings, ltrace

Перед запуском Ghidra стоит потратить 30 секунд на командную строку. Иногда задача решается вообще без дизассемблера — и это не шутка, а суровая реальность crackmes.one. Подробнее — в нашем руководстве по бинарный анализ уязвимостей.

Команда file crackme покажет формат (ELF/PE), архитектуру (x86/x64/ARM), тип линковки (static/dynamic). Для CTF reverse engineering задач это определяет дальнейший набор инструментов: динамическая линковка означает, что ltrace перехватит библиотечные вызовы.

strings crackme | grep -iE "flag|pass|wrong|correct" — грубый приём, но работает. По опыту решения задач на crackmes.one, удивительное количество crackme начального уровня хранят пароль открытым текстом в секции данных. Видите в выводе строку формата flag{...} или CTF{...} — проверяйте сразу, задача может закрыться за секунды.

Третий приём — ltrace ./crackme. Утилита перехватывает вызовы библиотечных функций в реальном времени. Если программа сравнивает ваш ввод с эталоном через strcmp, ltrace покажет оба аргумента: strcmp("your_input", "real_password"). Компилятор иногда инлайнит strcmp, и ltrace его не увидит — но проверка занимает две секунды, пропускать её глупо.

Эти три команды работают как фильтр. Ни одна не дала результата — пароль зашифрован или проверка нестандартная, переходим к статическому анализу бинарных файлов в Ghidra.

Навигация в Ghidra CodeBrowser: четыре окна для анализа бинарников

Создание проекта и импорт: File → New Project → Non-Shared Project, затем File → Import File. Ghidra сама определит формат и архитектуру. На вопрос "Analyze now?" соглашайтесь с дефолтными настройками — для типовых ELF и PE хватит за глаза. Установку опустим: нужен JDK 21+ и ZIP-архив с github.com/NationalSecurityAgency/ghidra/releases, установщика нет — распаковка и запуск ghidraRun.

После автоанализа интерфейс CodeBrowser выглядит перегруженным. Для разбора crackme в Ghidra нужны четыре окна, остальные смело закрывайте.

Symbol Tree (левая панель) — дерево символов: функции, импорты, экспорты. Тут ищем main. Раскрываете ветку Functions, двойной клик по main — оба центральных окна прыгают к этой функции. Если в импортах мелькнул WriteProcessMemory или VirtualAlloc — бинарник может модифицировать собственный код в рантайме, и чисто статический анализ будет неполным.

Listing (центральная панель) — дизассемблированный код. Адреса слева, мнемоники справа: MOV, CMP, JNE, CALL. Двойной клик на адресе CALL переносит внутрь вызываемой функции, Alt+← возвращает назад. Правый клик → Patch Instruction позволяет менять инструкции — пригодится в разделе про патчинг.

Decompile (правая панель) — декомпиляция в Ghidra, псевдокод на C. Главное оружие при анализе алгоритма проверки пароля. Подсветка строки в декомпиляторе подсвечивает соответствующий ассемблер в Listing — и наоборот. Видите if (iVar1 == 0) справа — смотрите CMP и JNZ слева. Кстати, нажатие F1 при наведении на любой элемент интерфейса вызывает контекстную справку (совет из курса Introduction to Reverse Engineering with Ghidra на Hackaday.io) — пользуйтесь, пока осваиваетесь.

Поиск main в stripped-бинарниках

Если main в Symbol Tree нет — бинарник stripped. В терминах MITRE ATT&CK это техника Stripped Payloads (T1027.008) из тактики Defense Evasion: символы удалены, чтобы затруднить анализ. Решение: ищите вызов __libc_start_main в секции Imports или Functions. Даже в stripped-бинарниках динамические библиотеки вроде libc сохраняют свои символы. Один из аргументов __libc_start_main — адрес main. Переходите по нему двойным кликом — вы в основной функции.

Defined Strings и перекрёстные ссылки

Четвёртое окно — Defined Strings (Window → Defined Strings). Все текстовые строки из бинарника как на ладони. В каждом crackme есть "Wrong password", "Correct!", "Access Denied" — это маяки для поиска проверки пароля в бинарнике.

Нашли "Wrong password"? Правый клик → References → Show References to Address. Ghidra покажет все функции, ссылающиеся на эту строку. Переходите к первой — вы внутри функции-обработчика. Не нужно препарировать весь бинарник: scope сужен до одной функции и одного условного перехода.

Переименование переменных — навык, без которого в Ghidra делать нечего. Декомпилятор именует переменные как local_28, iVar1, DAT_00104020. Правый клик → Rename Variable (или клавиша L): увидели, что local_28 передаётся в strcmp — переименуйте в user_input. Строка DAT_00104020 содержит "Enter password:" — переименуйте в prompt_msg. После 5-10 переименований нечитаемый псевдокод превращается в нормальный C-код. Ghidra синхронизирует имена между декомпилятором и листингом автоматически.

Тип undefined8 — 8 байт данных, тип которых Ghidra не определила. Для возвращаемого значения main это почти всегда int. Правый клик → Retype Variable → задаёте тип вручную. Для решения задачи необязательно, но код станет читабельнее.

Паттерны проверки пароля в crackme-задачах

Реверс простых crackme сводится к распознаванию одного из нескольких типичных паттернов. Каждый следующий — модификация предыдущего.

Открытый strcmp — решение за секунды

Простейший паттерн: программа берёт ввод и напрямую сравнивает с захардкоженной строкой через strcmp. В декомпиляторе Ghidra для начинающих это выглядит почти как исходник:

void main(void) {
    char user_input[104];
    puts("Enter password:");
    gets(user_input);
    if (strcmp(user_input, s_S3cr3tP@ss_00104020) == 0) {
        puts("Access Granted!");
    } else {
        puts("Access Denied!");
    }
}

Вместо литеральной строки "S3cr3tP@ss" Ghidra покажет s_S3cr3tP@ss_00104020 — та же строка по адресу 0x00104020. Двойной клик на имени перенесёт в Listing, где она лежит в открытом виде. Такие задачи решаются ещё до Ghidra — через strings или ltrace. Но узнавать этот паттерн в декомпиляторе полезно: следующие уровни строятся на его модификациях.

XOR-цикл с захардкоженным ключом

Автор crackme прячет эталонный пароль: каждый байт строки XOR-ится с ключом перед компиляцией. При проверке программа либо расшифровывает эталон и сравнивает с вводом, либо шифрует ввод и сравнивает с зашифрованным эталоном. В декомпиляторе — цикл с побитовой операцией:

for (i = 0; i < len; i++) {
    if ((user_input[i] ^ 0x42) != encrypted_pass[i]) {
        puts("Wrong!");
        return;
    }
}
puts("Correct!");

Ключ 0x42 — однобайтовый пример. В реальных crackme ключ бывает многобайтовым (циклический XOR: key[i % key_len]), или каждый следующий байт ключа вычисляется от предыдущего. Распознать этот паттерн можно по характерным признакам в Decompile View: цикл с переменной-счётчиком, оператор ^ внутри тела, обращение к массиву по индексу, условный переход к "Wrong" или "Correct".

Решение: вытаскиваете массив encrypted_pass из бинарника (Ghidra покажет его содержимое в Listing по адресу), прогоняете XOR с тем же ключом — и получаете пароль. Три строки на Python: прочитать байты, XOR, вывести результат. Ключ виден прямо в декомпилированном коде — это immediate-значение в операции ^.

Кастомные хеш-функции и посимвольные проверки

Третий уровень: программа не хранит эталон вообще. Вместо этого вычисляет хеш или контрольную сумму введённого пароля и сравнивает с числовой константой. В декомпиляторе — цикл, аккумулирующий значение через умножение, сложение, битовые сдвиги. Результат сравнивается с магическим числом: if (hash_result == 0xdeadbeef).

Дальше зависит от сложности хеша. Если он простой — сумма ASCII-кодов или hash = hash * 31 + char — обращается аналитически или подбирается перебором. Если хеш криптографический (SHA-256, MD5) — crackme явно не для начинающих, нужен другой подход: rainbow tables или z3 solver.

Ещё вариант — посимвольная проверка: каждый символ пароля проверяется отдельным условием. В декомпиляторе это серия if: if (input[0] == 'p') { if (input[1] == '@') { ... } }. Пароль читается прямо из кода, символ за символом. Встречается на crackmes.one в задачах уровня easy.

Когда декомпилятор Ghidra искажает логику проверки

Декомпиляция в Ghidra — штука мощная, но не безошибочная. По данным CrackMeBench, одна из главных причин провала при решении crackme — over-trusting decompilers. Вот конкретные ситуации, когда стоит перепроверять Decompile View через Listing.

Неверные типы данных. Ghidra не знает оригинальных типов. Если функция возвращает char *, а декомпилятор показывает undefined8, псевдокод может выглядеть как арифметика с числами, хотя на деле это работа со строками. Правый клик → Retype Variable, задаёте char * — декомпилированный код мгновенно перестроится.

Оптимизированные циклы. Компилятор с -O2 может развернуть цикл сравнения строк в серию отдельных CMP и JNE для каждого символа. Декомпилятор иногда показывает это как цепочку вложенных if, хотя в оригинале был один strcmp. Сверяйтесь с Listing: видите последовательные CMP byte ptr [RAX+N], immediate_value — это развёрнутый цикл, и пароль читается по immediate-значениям каждого CMP. Они и есть ASCII-коды символов пароля.

Строки, собранные на стеке. Бывает, строки формируются не из статической секции данных, а прямо на стеке — серией MOV DWORD PTR [RSP+offset], immediate_value. Декомпилятор покажет присваивание числовых значений локальным переменным. А в Listing те же immediate-значения — ASCII-коды. Четырёхбайтовое значение 0x73736150 в little-endian — строка "Pass". Увидели загадочные числовые константы перед сравнением — переключайтесь на Listing и интерпретируйте как ASCII.

Путаница с закодированными данными. CrackMeBench фиксирует эту ошибку как "confusing encoded data with final strings": агент (или человек) видит в бинарнике строку и принимает её за пароль, хотя это промежуточный этап — зашифрованный эталон или seed для генерации настоящего пароля. В разборе crackme ELF CrackPass с root-me (описан на freeCodeCamp) строка "THEPASSWORDISEASYTOCRACK" — не пароль, а seed, из которого генерируется настоящий пароль через отдельную функцию. Я на это попадался не раз — и каждый раз обидно.

Правило: если декомпилятор показывает что-то странное в районе сравнения — переключайтесь на Listing. Кликните на подозрительную переменную в Decompile — подсветится соответствующий ассемблер. Связка двух окон работает в обе стороны и помогает поймать моменты, когда декомпилятор врёт.

Патчинг условного перехода: инверсия вместо реверса

Если вытащить пароль из логики не получается — хеш слишком сложный, обфускация запутанная — есть альтернативный подход: пропатчить условный переход так, чтобы программа принимала любой ввод. На CTF это засчитывается. Дизассемблирование exe файлов и патчинг — два навыка, которые работают в связке.

Находите в Listing инструкцию условного перехода после сравнения. Обычно JNZ (jump if not zero) или JNE (jump if not equal) — переход к ветке "Wrong password". Правый клик → Patch Instruction → меняете мнемонику JNZ на JZ (jump if zero). Логика инвертирована: программа считает правильным любой ввод, кроме настоящего пароля. Вводите произвольную строку — получаете "Access Granted" или вывод флага.

В разборе crackme ELF CrackPass на freeCodeCamp автор применил три патча последовательно: обход ptrace-проверки (JNS → JMP), обход валидации символов (JZ → JMP), инверсию финального сравнения (JNZ → JZ). Результат: программа сама вывела сгенерированный пароль при вводе произвольной строки.

Для экспорта пропатченного бинарника: File → Export Program → формат Binary или Original File. Запускаете модифицированный файл — он отработает с новой логикой.

Оговорка: патчинг не работает на задачах, где флаг вычисляется из правильного ввода (flag = f(password)). Если программа при корректном пароле выводит статическую строку — патчинг решает задачу. Если флаг генерируется на основе ввода — придётся восстановить алгоритм полностью.

Большинство новичков в CTF reverse тратят время не на то. Изучают ассемблер x86 по справочникам Intel, читают про calling conventions и устройство стека — а потом не могут найти строку "Wrong password" через Defined Strings. Проблема не в незнании архитектуры, а в отсутствии навигационного навыка: умения быстро сузить scope от целого бинарника до одной функции и одного условного перехода.

Ассемблер нужен, но позже — когда декомпилятор перестанет справляться. Для 90% crackme начального и среднего уровня хватает Decompile View и умения переименовывать переменные. Через год регулярной практики ассемблер начинает читаться сам — не потому что зубрили мнемоники, а потому что видели тысячу раз одни и те же CMP/JNZ связки в паре с псевдокодом.

Я убеждён, что вход в реверс через декомпилятор, а не через учебник по ассемблеру — единственно разумный путь для 2025 года. Спорить с этим будут те, кто начинал в 2005-м с OllyDbg и пустого notepad. Они правы для своего времени, но порог входа с тех пор должен был снизиться — и Ghidra его снизила. Если хочешь не только crackme, а пройти полную подготовку к OSCP с лабами на каждый вектор — WAPT покрывает и реверс, и веб, и бинарную эксплуатацию в одном треке с ментором.

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

Поделиться

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

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

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

Читайте также

Write-up CTF методология: гайд с шаблоном

12 мин.

7

Write-up CTF методология: гайд с шаблоном

Готовый Markdown-шаблон для CTF write-up, разбор хороших и плохих примеров, чеклист перед публикацией. Как тратить 10 минут на документацию вместо двух часов.

8 ОКТЯБРЬ, 2026

Pwntools buffer overflow эксплойт с нуля для CTF

13 мин.

5

Pwntools buffer overflow эксплойт с нуля для CTF

Пошаговый туториал: установка pwntools, checksec, cyclic, ret2win — пишем рабочий эксплойт для CTF buffer overflow с разбором каждой строки кода

7 ОКТЯБРЬ, 2026

Format string уязвимости в CTF: от %p до shell

13 мин.

9

Format string уязвимости в CTF: от %p до shell

Полный разбор эксплуатации format string в CTF: поиск offset, утечка libc через %p, побайтовая запись через %hhn, GOT overwrite в pwntools. С GDB и примерами.

7 ОКТЯБРЬ, 2026