Главная / Блог / Reverse engineering для начинающих: находим пароль в бинарнике через Ghidra — пошаговый разбор

13 мин.00

Reverse engineering для начинающих: находим пароль в бинарнике через Ghidra — пошаговый разбор

Reverse engineering для начинающих: находим пароль в бинарнике через Ghidra — пошаговый разбор

Reverse engineering для начинающих: находим пароль в бинарнике через Ghidra — пошаговый разбор

Первый crackme в Ghidra — это четыре часа разглядывания local_18 и uVar2, бесконечное переключение между ассемблером и декомпилятором, и момент, когда наконец замечаешь if (local_8 == 0x149a) и до тебя доходит: пароль — просто число в десятичной записи. Через это прошёл каждый, кто начинал реверсить бинарники. Здесь — весь процесс от открытия файла до момента «Password OK»: без пропусков, без «это очевидно», без магии.

Требования к окружению для статического анализа бинарника

Перед первым crackme проверь, что рабочая станция не подведёт. Подробнее — в нашем статье о бинарный анализ уязвимостей.

Минимальная конфигурация:

  • ОС: Windows 10/11, Linux (Ubuntu 20.04+), macOS 10.13+
  • RAM: 4 ГБ минимум, 8 ГБ рекомендуется — Ghidra загружает бинарник в память и строит графы вызовов, на 2 ГБ будет тесно
  • JDK: версия 17 или выше. Ghidra написана на Java и требует именно JDK, не JRE. OpenJDK подходит
  • Ghidra: качаем с официального GitHub NSA (github.com/NationalSecurityAgency/ghidra), проект живой — релизы выходят регулярно
  • Диск: 2 ГБ для самой Ghidra + место под проекты (каждый проект занимает от 10 до 500 МБ)
  • Интернет: после скачивания Ghidra и бинарников не нужен — весь анализ локальный

Материал для практики: набор IOLI CrackMe — классическая серия из 10 бинарников с постепенно растущей сложностью, от простого сравнения с константой до хеширования. Файлы валяются в публичных репозиториях на GitHub. Альтернатива — crackmes.one с фильтрацией по сложности и платформе.

Изоляция: для учебных crackme виртуалка не нужна — IOLI и аналогичные CTF-задачи для новичков безопасны. Но если анализируешь бинарники сомнительного происхождения — работай в изолированной VM с сетевым адаптером в режиме Host-Only или полностью отключённым.

Создаём проект и запускаем автоанализ в Ghidra

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: как читать псевдокод после декомпиляции

Декомпилятор — главная причина, по которой 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 — и наоборот. Когда анализ псевдокода заходит в тупик, переключайся на ассемблер: он показывает ровно то, что делает процессор, без интерпретаций декомпилятора.

Находим проверку пароля: решение crackme шаг за шагом

Переходим к практике. Берём 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) — учётные данные хранятся в файлах и извлекаются через реверс-инжиниринг.

Что мы сделали:

  1. Импортировали бинарник, запустили автоанализ
  2. Нашли main в Symbol Tree
  3. В Decompile нашли оператор сравнения if
  4. Сконвертировали hex в decimal
  5. Ввели десятичное значение — пароль принят

После первого разбора решение crackme 0x01 занимает пять минут. Суть — найти функцию, обрабатывающую пользовательский ввод, и проследить путь данных до оператора сравнения.

CTF reverse engineering: усложнённая проверка с циклами и арифметикой

На следующих уровнях авторы crackme прячут пароль за арифметическими операциями, циклами и вызовами вспомогательных функций. В crackme0x04 из набора IOLI прямого сравнения в main нет — логика проверки целиком спрятана в отдельной функции.

После автоанализа открываем main и видим: программа читает ввод и передаёт его в функцию с автоматическим именем вроде FUN_080484b4 или _check. Никакого сравнения с паролем в main нет.

Навигация через Function Call Trees

Чтобы быстро понять, чем занимается неизвестная функция, используем 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 и содержит цикл — скорее всего, обрабатывает ввод посимвольно.

Упакованные бинарники и антиотладка: когда Ghidra декомпиляция не работает сразу

Не каждый бинарник отдаёт секреты после автоанализа. Две самые частые преграды для начинающих: упаковка (packing) и антиотладочные проверки.

Распознаём и распаковываем UPX

Симптомы: после импорта окно 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 хватает за глаза.

Антиотладочные проверки: ptrace и обход

Некоторые 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_00401160generate_password, FUN_080484f4custom_memcopy. Результат обновляется во всех местах проекта.

Переименование переменной: правый клик в Decompile → Rename Variable. local_8user_input, local_7cpassword_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 — отличный инструмент статического анализа, но у подхода есть границы, и их лучше знать заранее.

Динамически генерируемые значения. Если пароль вычисляется в рантайме на основе даты, имени пользователя или серийного номера диска, 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 комментариев

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

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