
На crackmes.one лежат сотни задач уровня «1 из 6» — начальная сложность. Тот, кто знает Ghidra, щёлкает такие за пять-десять минут. Тот, кто открывает дизассемблер впервые — застревает на два часа, а потом закрывает со словами «вернусь когда-нибудь». Разница не в знании ассемблера наизусть. Разница в трёх конкретных навыках: найти строку-подсказку, перейти по перекрёстной ссылке к функции проверки и прочитать псевдокод декомпилятора. Этот Ghidra туториал — пошаговый разбор типового crackme с контрольными точками на каждом шаге, чтобы вы могли сверить свой прогресс с моим.
Прежде чем открывать дизассемблер Ghidra, убедитесь, что окружение готово. Ничего экзотического: Подробнее — в нашем материале про бинарный анализ уязвимостей.
sudo apt install openjdk-21-jdk. Проверка: java -version должна показать 21.x.ghidraRun (Linux/macOS) или ghidraRun.bat (Windows). Всё.file, strings, ltrace (последняя — только Linux). На Windows аналог strings есть в Sysinternals.Про IDA Free: как второй инструмент полезна и чуть быстрее при первичном анализе небольших бинарников. Но декомпилятор в бесплатной версии отсутствует — F5 (Hex-Rays) доступен только в платной IDA Pro. Для реверс-инжиниринга для новичков это критично: читать чистый ассемблер на старте — боль. Ghidra с бесплатным встроенным декомпилятором — оптимальный выбор для первого crackme. Я сам начинал с IDA Free, потом переключился на Ghidra и обратно не тянет.
Тратить время на импорт в Ghidra, не проверив бинарник в терминале — ошибка, которую совершают все. Тридцать секунд в командной строке иногда закрывают задачу целиком.
file crackme — покажет архитектуру (x86-64, ARM), формат (ELF, PE), тип линковки. Контрольная точка: видите ELF 64-bit LSB executable, x86-64, dynamically linked — стандартный Linux-бинарник, Ghidra обработает его без танцев с бубном.
strings crackme — выведет все читаемые строки. По опыту решения задач на crackmes.one и picoCTF — удивительное количество crackme начального уровня решается одной этой командой. Пароль или флаг валяется в бинарнике открытым текстом. Ищите строки формата flag{...}, CTF{...} или подозрительные последовательности символов. Если в выводе strings мелькают «Wrong password», «Access Denied», «Correct!» — это маркеры функции проверки. Запомните их: пригодятся в Ghidra.
ltrace ./crackme (только Linux) — перехватывает вызовы библиотечных функций в реальном времени. Если программа сравнивает ввод с захардкоженным паролем через strcmp, ltrace покажет оба аргумента: strcmp("ваш_ввод", "реальный_пароль"). Иногда компилятор инлайнит strcmp, и ltrace его не увидит. Но проверка занимает две секунды — пропускать нет причин.
Ещё один быстрый приём: проверьте бинарник на сигнатуры пакеров. Откройте файл в hex-редакторе — строки UPX! или ASPack в первых байтах означают, что бинарник упакован. Для UPX: upx -d crackme. Без распаковки декомпилятор покажет не логику программы, а рутину распаковщика — а это совсем другая история.
Эти команды — не ритуал. Они формируют карту бинарника до статического анализа исполняемых файлов в Ghidra.
Запускаете Ghidra. Первый экран — менеджер проектов. File → New Project → Non-Shared Project → выбираете папку и имя. Один проект — одна задача.
Импорт: File → Import File или перетащите файл в окно проекта. Ghidra автоматически определит формат и архитектуру. Появится окно с метаданными — архитектура процессора, разрядность, предполагаемый компилятор. Менять ничего не нужно, жмите OK.
Двойной клик по файлу в проекте открывает Code Browser — основное рабочее окно. Ghidra спросит: «Analyze now?» — соглашайтесь и оставляйте настройки по умолчанию. Автоанализ занимает от секунд до пары минут. За это время Ghidra определяет границы функций, строит перекрёстные ссылки, вытаскивает текстовые строки и идентифицирует библиотечные вызовы.
Контрольная точка: после завершения автоанализа в нижней строке статуса — «Idle». В Symbol Tree → Functions появится список обнаруженных функций, включая main (если бинарник не stripped). Если main в списке есть — полдела сделано.
Интерфейс Code Browser выглядит перегруженным — десятки панелей, кнопок, деревьев. Для решения crackme нужны четыре окна. Остальные закрывайте без сожалений.
Symbol Tree (левая панель) — дерево всех символов: функции, импорты, экспорты. Здесь ищете main. Двойной клик по имени — оба центральных окна переместятся к ней. Раздел Imports покажет API-вызовы: strcmp, puts, scanf — моментальный снимок того, что бинарник умеет делать. Если видите WriteProcessMemory или VirtualAlloc — бинарник может модифицировать код в рантайме, и это уже другой уровень.
Listing (центральная панель) — ассемблерный код. Адреса слева, мнемоники справа: MOV, CMP, JNE, CALL. В 90% CTF-задач начального уровня читать этот код напрямую не придётся — декомпилятор справится лучше. Но полезно знать: подсветка строки в листинге одновременно подсвечивает соответствующую строку в декомпиляторе. И наоборот. Эта связка — одна из сильных сторон Ghidra.
Decompiler (правая панель) — псевдокод на C, сгенерированный встроенным декомпилятором. Главное оружие для обратной разработки Ghidra. Кстати, нажатие F1 при наведении на любой элемент интерфейса вызывает контекстную справку — полезно, пока осваиваетесь (подсмотрел в курсе Introduction to Reverse Engineering with Ghidra на Hackaday.io).
Defined Strings (Window → Defined Strings) — все текстовые строки из бинарника: сообщения пользователю, пароли, пути к файлам. Иногда задача решается одним взглядом на этот список. Чаще, чем хочется признавать.
Декомпилятор не знает, как автор назвал переменные. Вместо password вы увидите local_28, вместо char input[100] — что-то вроде char local_78[104], а вместо осмысленного типа — undefined8. Глобальные переменные получают имена по адресу: DAT_00104020 — просто ячейка памяти по адресу 0x00104020. Выглядит как абракадабра, но это лечится.
Переименование — ключевой навык. Правый клик на переменной → Rename Variable (или клавиша L). Увидели, что local_28 передаётся вторым аргументом в strcmp? Переименуйте в user_input. Строка по адресу DAT_00104020 содержит текст "Enter password:"? Переименуйте в prompt_msg.
Контрольная точка: после 5–10 переименований псевдокод из нечитаемого становится понятным. Конструкция undefined8 FUN_00401234(undefined8 param_1) превращается в int check_password(char *user_input). Два приёма, ускоряющих работу: правый клик на функции → Edit Function Signature позволяет исправить типы параметров (тип undefined8 для возвращаемого значения main — почти всегда int); код из окна декомпилятора копируется напрямую — удобно для CTF-отчётов и writeup'ов.
Функции с именами FUN_00401234 — Ghidra не смогла сопоставить их с библиотечными вызовами. Двойной клик откроет декомпилированный код. Часто это кастомные обёртки над стандартными функциями вроде memcpy или strlen. Ничего страшного — залезаете внутрь и разбираетесь.
Типичный crackme начального уровня работает по одной из двух схем: password checker (принимает ввод, сравнивает с эталоном) или flag checker (проверяет введённый флаг на корректность). Подход к каждому отличается, но начало одинаковое.
Открываете Defined Strings (Window → Defined Strings). Ищите строки «Wrong», «Denied», «Correct», «Access Granted» — маркеры функции проверки. Двойной клик на строке переведёт листинг к месту, где она объявлена. Теперь нужно найти, кто на неё ссылается: правый клик → References → Show References to Address. Появится список перекрёстных ссылок (xrefs). Переходите по ссылке — вы внутри функции проверки.
Контрольная точка: в окне декомпилятора должен отобразиться псевдокод с ветвлением if/else, где в одной ветке puts("Access Granted!"), в другой puts("Access Denied!").
Простейший случай — прямое сравнение через strcmp:
// Декомпилированный код после переименования переменных
void main(void) {
char user_input[104];
puts("Enter password:");
gets(user_input); // gets() удалена в C11/glibc 2.32+; в реальных crackme встречается часто, но не компилируется современным тулчейном
if (strcmp(user_input, "S3cr3tP@ss") == 0) {
puts("Access Granted!");
} else {
puts("Access Denied!");
}
}
Пароль виден прямо в псевдокоде: S3cr3tP@ss. Задача закрыта. Если strcmp сравнивает ввод с глобальной переменной DAT_00104020 — двойной клик на ней покажет содержимое строки в окне листинга.
Если пароль не лежит в открытом виде, автор скорее всего применил XOR — самый частый приём в crackme начального уровня. Декомпилятор покажет цикл, где каждый байт массива XOR-ится с ключом:
// XOR-декодирование пароля перед сравнением
for (i = 0; i < 8; i++) {
decoded[i] = encoded_data[i] ^ 0x42;
}
if (strcmp(user_input, decoded) == 0) {
puts("Correct!");
}
Здесь encoded_data — массив зашифрованных байтов, 0x42 — ключ XOR. Чтобы получить пароль: перейдите к адресу encoded_data в листинге, выпишите байты, выполните XOR с ключом. Быстрее всего — однострочник Python: ''.join(chr(b ^ 0x42) for b in encoded_bytes).
Контрольная точка: значение каждого байта decoded[i] после XOR должно давать печатный ASCII-символ (диапазон 0x20–0x7E). Если получаете мусор — ключ или массив определены неправильно, перепроверьте адрес в Ghidra.
На практике XOR-ключ может быть не одним байтом, а массивом. Декомпилятор покажет это как encoded[i] ^ key[i % key_len]. Логика не меняется — оба массива видны в Ghidra, остаётся повторить операцию. Тот же принцип обфускации используется вредоносным ПО — MITRE ATT&CK классифицирует это как Obfuscated Files or Information (T1027).
Иногда восстанавливать пароль нет смысла — быстрее пропатчить условный переход. На CTF оба подхода засчитываются. Суть: находите инструкцию JNZ (jump if not zero) после сравнения и меняете на JZ (jump if zero) — или наоборот. Инвертируете условие: программа выводит «Correct!» при любом неправильном пароле.
В Ghidra: правый клик на инструкции в листинге → Patch Instruction. Меняете мнемонику. Изменение мгновенно отражается в декомпиляторе — ветки if/else поменяются местами. Тот же приём работает для обхода антиотладочных проверок: условный переход (JS/JNS/JL/JGE — зависит от компилятора) после вызова ptrace(PTRACE_TRACEME, ...) патчат в безусловный JMP в ветку «debugger not detected», убирая проверку целиком.
Сохранение пропатченного файла: File → Export Program → формат ELF Binary для Linux, PE для Windows. Оригинал не модифицируется — Ghidra работает с копией.
Патчинг — не позорный приём. Позорный — когда четыре часа ковыряете алгоритм вместо того, чтобы инвертировать один байт. На реальных CTF время ограничено, и грязное решение за минуту стоит столько же очков, сколько чистое за час. Я этому научился на собственном горьком опыте.
Если бинарник скомпилирован с флагом strip, таблица символов удалена. В Symbol Tree → Functions вместо main — список из FUN_00401XXX. В терминах MITRE ATT&CK это Stripped Payloads (T1027.008).
Как найти main без символов: ищите функцию __libc_start_main в Imports. Даже в stripped-бинарнике динамически слинкованные библиотечные вызовы сохраняют имена. Первый аргумент __libc_start_main — адрес main:
// entry point stripped-бинарника
void entry(void) {
__libc_start_main(FUN_00401196, ...);
}
FUN_00401196 — ваш main. Двойной клик, затем правый клик → Rename Function → вводите main. Дальше всё по стандартной схеме — строки и перекрёстные ссылки.
Контрольная точка: после перехода в переименованную функцию декомпилятор должен показать знакомую структуру — вызовы ввода-вывода (puts, scanf) и условное ветвление.
Если бинарник слинкован статически (file покажет statically linked) — __libc_start_main не будет в Imports. Тут задача усложняется: Navigation → Go To → entry, затем ищите паттерн вызова с тремя аргументами — конвенция инициализации glibc. Не самый приятный квест, но решаемый.
Три промаха, которые я вижу у людей на каждом CTF-тренинге:
Не переименовывают переменные. Пытаются читать if (iVar1 == 0) с пятью local_XX вокруг. Потеря времени. Пять минут на переименование экономят час на анализ.
Начинают с entry вместо строк. Функция entry — инициализация рантайма C, не бизнес-логика. Строки «Wrong password» ведут к целевой функции напрямую, минуя весь инициализационный код.
Игнорируют разведку в терминале. Запускают Ghidra сразу, тратят минуты на импорт и автоанализ. А strings crackme | grep -i flag могла решить задачу за секунду.
Про radare2 (текущая версия 6.2.5): консольный фреймворк, который часто называют альтернативой. Зверь мощный, но кривая обучения крутая. Для первого знакомства с реверсом CLI без графического декомпилятора — лишний барьер. Ghidra для начинающих подходит лучше именно благодаря визуальной связке «ассемблер слева — псевдокод справа». Radare2 — это как Vim: когда освоишь — летаешь, но до этого момента хочется всё бросить.
Решили crackme с strcmp — берите следующий с XOR. Решили с XOR — ищите задачу с кастомным хешированием. На crackmes.one задачи отсортированы по сложности от 1 до 6 — проходите последовательно. На picoCTF категория reverse начинается с задач, решаемых одной командой strings, и доходит до кастомных виртуальных машин с собственным набором инструкций.
Каждый новый crackme тренирует один и тот же цикл: строки → xref → декомпилятор → логика → ответ. Различается только сложность трансформации. После десятка задач этот цикл становится автоматическим, а ассемблерный листинг перестаёт выглядеть случайным набором мнемоник.
Девять из десяти реверсеров, которых я знаю, начинали с убеждения: сначала выучить ассемблер x86 наизусть — мнемоники, соглашения о вызовах, форматы адресации — и только потом открывать дизассемблер. Подход выглядит логичным и абсолютно неработоспособен. Работает ровно наоборот: открываете Ghidra, загружаете бинарник, читаете декомпилятор. Когда псевдокод непонятен — смотрите на ассемблер в соседней панели. Через десяток crackme мнемоники MOV, CMP, JNZ, XOR запоминаются сами, без зубрёжки 4000-страничного мануала Intel.
Декомпилятор — костыль? Вероятно. Но костыль, который позволяет решать задачи с первого дня, а не после месяца теоретической подготовки. Вы же не учите весь словарь испанского перед поездкой — учите фразы, нужные прямо сейчас. С ассемблером тот же принцип: CMP/JNZ — потому что только что видели в crackme, PUSH/CALL — потому что нужно понять передачу аргументов. Курсы часто строят программу «сначала теория, потом практика», и студент три недели читает про регистры, а потом открывает Ghidra и всё равно не знает, куда нажимать. Если хотите не просто один writeup, а систему с лабами на каждый вектор — WAPT на codeby.school покрывает бинарный анализ в формате, заточенном под OSCP.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...