
Первый crackme в Ghidra — это четыре часа разглядывания local_18 и uVar2, бесконечное переключение между ассемблером и декомпилятором, и момент, когда наконец замечаешь if (local_8 == 0x149a) и до тебя доходит: пароль — просто число в десятичной записи. Через это прошёл каждый, кто начинал реверсить бинарники. Здесь — весь процесс от открытия файла до момента «Password OK»: без пропусков, без «это очевидно», без магии.
Перед первым crackme проверь, что рабочая станция не подведёт. Подробнее — в нашем статье о бинарный анализ уязвимостей.
Минимальная конфигурация:
github.com/NationalSecurityAgency/ghidra), проект живой — релизы выходят регулярноМатериал для практики: набор IOLI CrackMe — классическая серия из 10 бинарников с постепенно растущей сложностью, от простого сравнения с константой до хеширования. Файлы валяются в публичных репозиториях на GitHub. Альтернатива — crackmes.one с фильтрацией по сложности и платформе.
Изоляция: для учебных crackme виртуалка не нужна — IOLI и аналогичные CTF-задачи для новичков безопасны. Но если анализируешь бинарники сомнительного происхождения — работай в изолированной VM с сетевым адаптером в режиме Host-Only или полностью отключённым.
Ghidra организована вокруг проектов — контейнеров, которые хранят импортированные бинарники вместе со всеми переименованиями, комментариями и пометками.
Запускаешь ghidraRun (Linux/macOS) или ghidraRun.bat (Windows). В окне Project Manager создаёшь новый проект: File → New Project → Non-Shared Project, указываешь имя и директорию. Для импорта: File → Import File, выбираешь файл crackme. Ghidra автоматически определяет формат (ELF, PE, Mach-O), архитектуру (x86, x64, ARM) и предлагает параметры импорта. Для учебных crackme дефолтные настройки подходят — жмёшь OK.
Двойной клик на импортированный файл — открывается CodeBrowser, основное рабочее пространство. Ghidra предлагает запустить автоанализ (Auto Analysis) — соглашайся и оставляй галочки по умолчанию. Анализ занимает от пары секунд для маленького crackme до нескольких минут для крупного бинарника. За это время Ghidra: дизассемблирует машинный код в ассемблерные инструкции, строит граф вызовов между функциями, определяет типы данных и строковые литералы, запускает декомпилятор для восстановления подобия исходного кода на C.
После анализа перед тобой четыре ключевых окна. Listing слева — дизассемблированный код: адреса памяти, байты, ассемблерные мнемоники. Decompile справа — псевдокод, восстановленный декомпилятором. Symbol Tree в крайней левой панели — дерево всех обнаруженных функций, меток и данных. Console внизу — лог анализа и вывод скриптов.
Окно Decompile — твой основной инструмент на старте. Именно здесь ты будешь читать логику программы, не разбирая каждую ассемблерную инструкцию вручную.
Декомпилятор — главная причина, по которой Ghidra рекомендуют для reverse engineering начинающим. До 2022 года бесплатная IDA не имела декомпилятора вовсе. С IDA Free 8.0 (2022) добавлен локальный декомпилятор Hex-Rays для x86-64, но без поддержки других архитектур и без ряда функций коммерческой IDA Pro. Ghidra даёт декомпилятор для x86, x64, ARM, MIPS и других архитектур — бесплатно и без ограничений.
Декомпилятор берёт ассемблерные инструкции и восстанавливает подобие C-кода. Не точную копию исходника — имена переменных, комментарии и некоторые структурные конструкции теряются при компиляции безвозвратно. Но результат достаточен, чтобы восстановить логику.
Переменные local_XX. Ghidra не знает оригинальных имён и генерирует автоматические: local_8, local_1c, local_7c. Число — смещение переменной относительно указателя кадра стека (EBP/RBP). На практике: local_8 лежит ближе к вершине стека (скорее всего int или указатель), local_7c — дальше и, вероятно, это массив или буфер.
Параметры param_1, param_2. Аргументы функции. Для main обычно param_1 = argc (количество аргументов), param_2 = argv (массив строк-аргументов).
Типы undefined, undefined4. Ghidra не всегда определяет тип. undefined4 означает «4 байта неизвестного типа» — в большинстве случаев это int. Тип меняешь вручную: правый клик на переменную → Retype Variable.
Функции FUN_XXXXXXXX. Автоматическое имя по адресу начала функции. Переименовывается клавишей L.
Связь между окнами. Выделение строки в Decompile подсвечивает соответствующие инструкции в Listing — и наоборот. Когда анализ псевдокода заходит в тупик, переключайся на ассемблер: он показывает ровно то, что делает процессор, без интерпретаций декомпилятора.
Переходим к практике. Берём crackme0x01 из набора IOLI CrackMe — бинарник запрашивает пароль и сообщает, правильный он или нет.
После импорта и автоанализа находим функцию main. В окне Symbol Tree раскрываем папку Functions, ищем _main или main (имя зависит от компилятора и платформы). Если функций много — используем поле фильтра вверху панели. Кликаем на main, и в Decompile появляется псевдокод:
printf("Password: ");
scanf("%d", &local_8);
if (local_8 == 0x149a) {
printf("Password OK :)\n");
} else {
printf("Invalid Password!\n");
}
Разбираем построчно. Программа выводит приглашение, через scanf с форматом %d читает целое число и записывает его в local_8. Затем сравнивает введённое значение с 0x149a. Совпало — пароль принят.
Типичная ловушка для начинающих: попытка ввести 0x149a как есть. Программа ждёт десятичное число (формат %d), а 0x149a — шестнадцатеричная запись. Ghidra конвертирует прямо в интерфейсе: правый клик на 0x149a в окне Listing → Convert → Unsigned Decimal. Значение превращается в 5274. Вводишь 5274 при запуске бинарника — получаешь «Password OK».
Этот паттерн — захардкоженная константа для сравнения — не ограничен учебными CTF-задачами. В реальных бинарниках вредоносное ПО хранит ключи шифрования, URL командных серверов или пароли прямо в коде. В MITRE ATT&CK это Credentials In Files (T1552.001, Credential Access) — учётные данные хранятся в файлах и извлекаются через реверс-инжиниринг.
Что мы сделали:
main в Symbol TreeifПосле первого разбора решение crackme 0x01 занимает пять минут. Суть — найти функцию, обрабатывающую пользовательский ввод, и проследить путь данных до оператора сравнения.
На следующих уровнях авторы crackme прячут пароль за арифметическими операциями, циклами и вызовами вспомогательных функций. В crackme0x04 из набора IOLI прямого сравнения в main нет — логика проверки целиком спрятана в отдельной функции.
После автоанализа открываем main и видим: программа читает ввод и передаёт его в функцию с автоматическим именем вроде FUN_080484b4 или _check. Никакого сравнения с паролем в main нет.
Чтобы быстро понять, чем занимается неизвестная функция, используем Function Call Trees: Window → Function Call Trees. Инструмент показывает входящие и исходящие вызовы. Для _check видим: она вызывает _strlen, _sscanf, _exit и _printf. Наличие strlen и sscanf — подсказка: в проверке задействована длина строки и посимвольное чтение.
Дважды кликаем на _check в Decompile. Исходный псевдокод с автоименами — нечитаемая каша из local_c, local_10, iVar1. Но после переименования переменных логика становится прозрачной:
while (counter < strlen(password)) {
sscanf(&password[counter], "%d", &digit);
sum = sum + digit;
if (sum == 0xf) {
printf("Password OK!\n");
exit(0);
}
counter = counter + 1;
}
Цикл проходит по каждому символу пароля, интерпретирует его как цифру через sscanf и складывает в sum. Когда сумма достигает 0xf (15 в десятичной) — программа выводит успех и завершается.
Принципиальное отличие от предыдущего примера: здесь нет единственного правильного ответа. Любая комбинация цифр с суммой 15 проходит проверку — 555, 771, 12345, 5541, даже 96. Этот тип задач учит не просто искать константу, а восстанавливать алгоритм целиком.
Методика для подобных случаев: когда в main нет явной проверки, ищи вызовы функций, которым передаётся пользовательский ввод. Переходи внутрь каждой такой функции. Function Call Graph (Window → Function Call Graph) визуализирует структуру вызовов и ускоряет навигацию в незнакомом коде. Если функция вызывает strcmp — ищи второй аргумент, с которым идёт сравнение. Если вызывает strlen и содержит цикл — скорее всего, обрабатывает ввод посимвольно.
Не каждый бинарник отдаёт секреты после автоанализа. Две самые частые преграды для начинающих: упаковка (packing) и антиотладочные проверки.
Симптомы: после импорта окно Defined Strings (Window → Defined Strings) пустое, Ghidra нашла единственную функцию _entry, а в Program Tree видны секции с характерными именами UPX0 и UPX1. Бинарник сжат утилитой UPX — классический пример техники Software Packing (T1027.002, Defense Evasion по MITRE ATT&CK). Исполняемый файл сжимается, и при запуске код в _entry распаковывает оригинальную программу в память.
Ghidra не распаковывает UPX автоматически: декомпилятор покажет только код распаковщика, а не реальную логику. Решение — утилита UPX с официального сайта (upx.github.io): cp packed.exe unpacked.exe && upx -d unpacked.exe (UPX распаковывает на месте, поэтому сначала делаем копию). Импортируешь распакованный файл в Ghidra — появляются функции, строки и полноценный псевдокод.
Подтвердить упаковку перед распаковкой можно через CFF Explorer или PE-Bear: если File Info показывает «UPX 2.90» или аналогичную метку — диагноз подтверждён.
Ограничения: upx -d работает только со стандартным UPX. Коммерческие протекторы (VMProtect, Themida, ASProtect) требуют специализированных распаковщиков или ручной работы в отладчике. Модифицированные версии UPX с изменёнными сигнатурами стандартная утилита тоже не берёт — потребуется ручная правка заголовков секций. Для CTF начального и среднего уровня стандартного UPX хватает за глаза.
Некоторые crackme (и реальная малварь) проверяют, не запущены ли они под отладчиком. На Linux классический приём — вызов ptrace(PTRACE_TRACEME, 0, 1, 0) в начале программы. Если процесс уже трассируется (GDB, strace), ptrace возвращает ошибку, и программа завершается через abort(). В MITRE ATT&CK это Debugger Evasion (T1622, Defense Evasion / Discovery). Хотя ptrace — приём Linux, в публичных тестах Atomic Red Team для T1622 представлены только Windows-сценарии (например, IsDebuggerPresent); Linux-примеры вроде ptrace концептуально подпадают под ту же технику, но формально не покрыты этими тестами.
В Ghidra такой механизм выглядит как вызов ptrace с последующим условным переходом. Пример из root-me ELF CrackPass: после вызова ptrace обычно стоит инструкция, устанавливающая флаги — TEST EAX, EAX или CMP EAX, -1, — а затем условный переход JNS (Jump if Not Sign). JNS проверяет флаг SF: при неотрицательном результате (отладчик не обнаружен) прыгает на нормальный путь исполнения. Если ptrace вернул -1 (отладчик обнаружен), TEST EAX, EAX устанавливает SF=1, JNS не срабатывает, и выполнение проваливается в abort().
Обход через патчинг: выделяешь JNS в окне Listing, правый клик → Patch Instruction, заменяешь на JMP — безусловный переход, который всегда пропускает проверку. Аналогично: инвертирование JNZ на JZ в функции проверки пароля заставляет программу считать неверный пароль верным. Экспорт модифицированного бинарника: File → Export Program → Binary.
Когда патчинг не спасает: если бинарник проверяет собственную целостность (считает хеш своего кода и сравнивает с эталоном), изменение одного байта ломает проверку. Тогда нужно отключить и integrity check, или перейти к динамическому анализу — модификация значений в памяти через отладчик не затрагивает файл на диске. Множественные anti-debug проверки, разбросанные по всему коду, тоже плохо поддаются точечному патчингу.
Автоматические имена local_8, FUN_00401160, DAT_00402008 делают псевдокод нечитаемым. Привычка переименовывать объекты с первых минут — то, что превращает хаотичное блуждание по коду в системную работу.
Переименование функции: клик на имя в Listing или Decompile → клавиша L → осмысленное имя. FUN_00401160 → generate_password, FUN_080484f4 → custom_memcopy. Результат обновляется во всех местах проекта.
Переименование переменной: правый клик в Decompile → Rename Variable. local_8 → user_input, local_7c → password_buffer. Это не модифицирует бинарник — только твоё представление в Ghidra.
Применение типов данных. Когда Ghidra показывает последовательность байтов (E0h, A1h, B2h...) — правый клик → Data → string. В CTF-бинарнике Overlong.exe от Flare-On 6 применение типа string к данным по определённому адресу раскрыло флаг прямо в окне декомпилятора — без единого запуска.
Перекрёстные ссылки (XREF). Находишь строку «Password OK» или «Try Again!» в Defined Strings (Window → Defined Strings), правый клик → References → Show References to Address. Ghidra показывает все места, где строка используется — от «Try Again!» попадаешь прямо в функцию проверки. Один из самых быстрых способов навигации в незнакомом бинарнике.
Комментирование. Клавиша ; на строке в Listing добавляет комментарий. Пометки вроде «XOR input с константой из массива по адресу 0x409480» или «Сумма цифр сравнивается с 15» через неделю спасают от повторного разбора с нуля. (Я в своё время пренебрегал этим — потом жалел каждый раз.)
Работа с массивами. Если Ghidra показывает обращения к DAT_00409480, DAT_00409481, DAT_00409482 — это элементы одного массива, которые Ghidra интерпретирует как отдельные переменные. Выделяешь блок данных, очищаешь через правый клик → Clear Code Bytes (или клавишу C в окне Listing), затем жмёшь T (Choose Data Type) и вводишь char[24]. После этого Ghidra корректно отображает индексированный доступ, и код из нечитаемого месива превращается в понятный цикл по массиву. По данным разбора FatMike's CrackMe, именно эта операция превратила XOR-шифрование из набора разрозненных обращений к памяти в читаемый цикл.
Ghidra — отличный инструмент статического анализа, но у подхода есть границы, и их лучше знать заранее.
Динамически генерируемые значения. Если пароль вычисляется в рантайме на основе даты, имени пользователя или серийного номера диска, Ghidra покажет алгоритм вычисления, но не итоговое значение. Два варианта: реализовать алгоритм вручную (скрипт на Python) или запустить бинарник под отладчиком и посмотреть на значения регистров в момент сравнения.
Обфускация потока управления. Протекторы вроде VMProtect превращают код в виртуальную машину с собственным набором инструкций. Ghidra декомпилирует обработчик этой VM, но результат — гигантский switch с сотнями case — нечитаем. Для таких случаев нужны специализированные деобфускаторы или трассировка в отладчике. В MITRE ATT&CK это Obfuscated Files or Information (T1027, Defense Evasion).
Ошибки декомпилятора. Псевдокод — приближение, не истина. Ghidra может неправильно определить количество параметров функции, перепутать знаковые и беззнаковые типы, неверно восстановить границы циклов. Когда поведение программы не соответствует тому, что показывает Decompile — лезь в ассемблер в Listing. Там ровно то, что делает процессор.
Когда подключать отладчик. Практическое правило: если после 30 минут в Ghidra логика функции остаётся непонятной, переходи к динамическому анализу. GDB (Linux) или x64dbg (Windows): ставишь breakpoint перед подозрительным сравнением, запускаешь программу, смотришь реальные значения в регистрах. Связка Ghidra (статическая гипотеза) + отладчик (динамическая проверка) — стандартный рабочий процесс, а не признак того, что ты «не справился» со статикой.
Альтернативные инструменты: radare2 (активно поддерживается, текущая master-ветка 6.1.9, open source) лучше подходит для скриптового анализа из командной строки и автоматизации через r2pipe. Для визуальной работы с псевдокодом Ghidra остаётся лучшим бесплатным выбором. IDA Pro с Hex-Rays — индустриальный стандарт, но стоимость лицензии делает её недоступной для самостоятельного обучения.
У меня реверс начался с часового разглядывания local_1c и param_1 без понимания, что это за переменные. Переломный момент наступил не от чтения очередной теоретической статьи, а когда решил первый crackme: увидел 0x149a, сконвертировал, ввёл, получил «Password OK» — и физически ощутил, как работает обратная разработка. После этого каждый следующий бинарник давался проще. Не потому что появилось тайное знание, а потому что мозг начал распознавать паттерны: вот scanf читает ввод, вот strcmp сравнивает строки, вот цикл считает контрольную сумму.
Большинство руководств по reverse engineering для начинающих грешат одним: объясняют архитектуру процессора, систему команд x86, конвенции вызовов — к моменту, когда читатель добирается до практики, мотивация сгорела. Подход, который реально работает — противоположный: открываешь Ghidra, импортируешь бинарник, ищешь сравнение. Когда появляется вопрос «а что такое EBP и почему local_8 — это [EBP-0x8]», теория ложится на подготовленную почву. RE невозможно выучить по статьям — только через решение задач, одну за другой, с возрастающей сложностью. Если хочется пройти эту базу системно — IB Basics берёт с любого старта, без требований «вы должны знать ассемблер x86».
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...