Главная / Блог / Реверс-инжиниринг в Ghidra для начинающих: шаг за шагом

15 мин.00

Реверс-инжиниринг в Ghidra для начинающих: шаг за шагом

Реверс-инжиниринг в Ghidra для начинающих: шаг за шагом

Ghidra — бесплатный фреймворк от NSA, вышедший в открытый доступ в 2019 году. За шесть лет стал стандартом CTF-категории reverse. Поддержка x86, ARM, MIPS, PowerPC, AVR, SPARC и других архитектур — это хорошо, но главное оружие — встроенный декомпилятор, которого нет в бесплатной IDA Free. Вместо чистого ассемблера можно читать C-подобный псевдокод, и это снимает тот самый барьер, о который разбиваются новички.

Но первая встреча с интерфейсом выглядит у всех одинаково: десятки панелей, переменные iVar1 и uVar2, глобальные данные с именами DAT_00104020 и ноль подсказок, куда вообще тыкать. Этот Ghidra туториал — путь от установки до решённой CTF-задачи: куда кликнуть, что искать в псевдокоде и как превратить стену нечитаемого кода в понятную логику.

Подготовка к reverse engineering с нуля

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

Системные требования:

  • ОС: Windows 10/11 или Linux (Ubuntu 20.04+). Ghidra написана на Java — работает кроссплатформенно.
  • RAM: минимум 4 ГБ, но лучше 8. JVM при автоанализе крупных файлов жрёт память заметно — с 4 ГБ будет тесно.
  • JDK 17 (минимум) для Ghidra 11.x; сборки 11.3+ могут требовать JDK 21 — смотрите changelog конкретного релиза. На Ubuntu — sudo apt install openjdk-21-jdk, на Windows — установщик с adoptium.net.
  • Ghidra — ZIP-архив с github.com/NationalSecurityAgency/ghidra/releases. Никакого инсталлятора: распаковка, запуск ghidraRun (Linux) или ghidraRun.bat (Windows).

Виртуальная машина обязательна при анализе незнакомых бинарников. На CTF-задачах начального уровня это перестраховка, но привычка правильная: запуск неизвестного бинарника на хостовой ОС — прямой путь к переустановке системы. VirtualBox или VMware Workstation Player, сеть внутри ВМ отключена.

Вопрос «Ghidra или IDA?» задаёт каждый, кто начинает обратную разработку программ. Для обучения реверс-инжинирингу Ghidra — оптимальный старт. Причина одна: декомпилятор. IDA Free его не даёт (Hex-Rays доступен только в платной версии за ощутимые деньги). Без декомпилятора придётся читать чистый ассемблер — навык полезный, но на старте это как учить язык по словарю без разговорной практики. Держать оба инструмента — нормальная практика: каждый подсвечивает то, что второй пропускает. Вот сравнение по критериям, которые реально влияют на работу:

Критерий Ghidra IDA Free radare2 (6.2.3)
Декомпилятор Встроенный Нет Через плагин r2ghidra
Интерфейс GUI (Java) GUI (нативный) CLI
Скриптинг Java, Python (Jython) Ограничен в бесплатной версии r2pipe, Python
Главный плюс Декомпилятор + мультиархитектурность Скорость работы с PE-файлами Автоматизация, встраивание в pipeline
Когда НЕ подходит Бинарники больше 100 МБ — JVM тормозит Нужен декомпилятор или расширенный скриптинг Требует привыкания к CLI — не для первого знакомства с реверсом

На CTF держите оба: Ghidra — основной инструмент, IDA Free — для экспресс-проверки гипотез. radare2 отложите до момента, когда захочется автоматизировать рутину скриптами.

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

Каждую CTF-задачу по reverse стоит начинать не с Ghidra, а с командной строки. Три команды экономят от минут до часов.

file crackme покажет формат (ELF/PE/Mach-O), архитектуру (x86-64, ARM, MIPS), тип линковки (static/dynamic), stripped или нет. Если file показывает stripped — готовьтесь к тому, что main в Symbol Tree не будет (об этом ниже). Если statically linked — бинарник содержит библиотечные функции внутри себя, и список символов в Ghidra окажется заметно длиннее обычного.

strings crackme выводит все читаемые строки из бинарника: сообщения пользователю, пути к файлам, иногда — пароли открытым текстом. По опыту решения задач на picoCTF и crackmes.one, удивительное количество задач начального уровня закрывается этой командой. Видите строку формата flag{...} или CTF{...} — проверяйте сразу, задача может решиться за секунды. Даже когда флаг не лежит в открытом виде, строки "Wrong password", "Try again", "Correct!" подскажут, какие функции потом искать внутри Ghidra через перекрёстные ссылки.

ltrace ./crackme перехватывает вызовы библиотечных функций при запуске. Если бинарник сравнивает ввод с эталоном через strcmp, ltrace покажет оба аргумента: strcmp("ваш_ввод", "настоящий_пароль"). Тут есть нюанс: ltrace работает только с динамически слинкованными бинарниками — он перехватывает вызовы через PLT. Для статически слинкованных (которые file покажет как statically linked) ltrace не покажет ничего — библиотечные функции встроены в код напрямую. В этом случае сразу переходите к Ghidra или используйте strace для анализа системных вызовов. Ещё один подводный камень: компилятор иногда инлайнит strcmp, и ltrace этого не увидит. Дополнительно попробуйте strace ./crackme — перехватывает системные вызовы, полезно, если программа читает пароль из файла. Обе проверки занимают секунды — пропускать их нет смысла.

Эти шаги — не ритуал. На CTF-блицах разница между первым и десятым местом измеряется секундами. Кто начал с strings — решает задачу за 10 секунд. Кто сразу открыл Ghidra — за 10 минут.

Как пользоваться Ghidra: импорт бинарника и автоанализ

Запустите Ghidra. При первом старте появится окно менеджера проектов. Всплывающее «Tip of the Day» закрывайте — ничего полезного там нет.

Создание проекта: File → New Project → Non-Shared Project → выберите директорию и имя проекта. Один проект — одна CTF-задача. Все комментарии, переименования переменных и аннотации сохраняются между сессиями. Можно вернуться к задаче через неделю и увидеть ровно тот контекст, который наработали — не сравнить с одноразовым анализом в терминале.

Импорт файла: File → Import File → указываете бинарник (или перетаскиваете прямо в окно проекта). Ghidra автоматически определит формат, архитектуру, разрядность, предполагаемый компилятор. Для типовых ELF и PE менять настройки не нужно — жмите OK.

Code Browser: двойной клик по файлу в проекте открывает основное рабочее окно. Ghidra спрашивает «Analyze now?» — соглашайтесь с настройками по умолчанию. Автоанализ займёт от секунд до пары минут. Инструмент определит границы функций, построит перекрёстные ссылки (xrefs), вытащит текстовые строки, идентифицирует библиотечные вызовы.

Для Windows PE-файлов стоит проверить в окне анализа опции пропагации параметров внешних функций (название может отличаться в зависимости от версии Ghidra). Ghidra подставит параметры импортированных API прямо в декомпилированный код — читать станет заметно проще.

Статический анализ бинарных файлов в Code Browser

После автоанализа на экране — интерфейс с десятком панелей. Для решения CTF-задач нужны четыре окна. Остальные смело закрывайте — только мешают.

Symbol Tree — карта бинарника

Левая панель. Дерево всех символов программы: функции, импорты, экспорты, метки. Разверните ветку Functions — здесь ищите main. Двойной клик перенесёт Listing и Decompiler к этой функции.

Разверните Imports — увидите API-вызовы. Это моментальный снимок того, что бинарник умеет. WriteProcessMemory, VirtualAlloc — код может модифицировать себя в рантайме. socket, connect — сетевая активность. CreateFile, ReadFile — работа с файловой системой. На CTF начального уровня импорты обычно минимальны (stdio, string), но привычка проверять их формируется здесь. В реальном анализе исполняемых файлов и вредоносного ПО этот взгляд — первое, что делаешь.

Listing и Decompile — два взгляда на один код

Listing (центральная панель) — ассемблерный код: адреса слева, мнемоники справа (MOV, CMP, JNE, CALL). Decompile (правая панель) — C-подобный псевдокод от декомпилятора. Вот это — главное оружие новичка при работе с Ghidra.

Ключевая связка: подсветка строки в декомпиляторе подсвечивает соответствующий ассемблер в Listing, и наоборот. Видите if (iVar1 == 0) справа — смотрите CMP и JNZ слева. Эта синхронизация бесценна для обучения: после десятка таких сопоставлений ассемблер перестаёт казаться случайным набором символов. Связь между if в декомпиляторе и парой CMP + условный переход в листинге становится осязаемой — это тот момент, когда реверс начинает «щёлкать».

Навигация в Listing: двойной клик на адресе CALL переносит внутрь вызываемой функции. Alt+← возвращает назад. Правый клик → Patch Instruction позволяет менять инструкции прямо в листинге — пригодится для патчинга условных переходов.

Function Graph (Window → Function Graph) — граф потока управления, аналог Graph View в IDA. По умолчанию открывается в уменьшенном масштабе — ничего не разобрать. Правый клик → Properties → Start Fully Zoomed In исправляет это. Нажатие F1 при наведении на любой элемент интерфейса вызывает контекстную справку — пользуйтесь, пока осваиваетесь.

Defined Strings — быстрый путь к целевой функции

Меню Window → Defined Strings. Все текстовые строки из бинарника в одном месте. Двойной клик на строке переносит к ней в Listing. Правый клик → References → Show References to Address — список всех мест, где строка используется. Перекрёстная ссылка (xref) приведёт к функции-обработчику. Это ваша целевая функция — та, в которой происходит проверка пароля или флага. Часто задача решается одним взглядом на Defined Strings. Не преувеличиваю — на CTF начального уровня это работает раз за разом.

Декомпиляция кода в Ghidra: разбираем псевдокод

Декомпилятор понятия не имеет, как автор назвал переменные. Вместо password вы увидите local_28. Вместо char input[100]char local_78[104]. Глобальные данные именуются по адресу: DAT_00104020 — ячейка памяти по адресу 0x00104020. Тип undefined8 — 8 байт, тип которых Ghidra не определила автоматически. Для возвращаемого значения main это почти всегда int.

Функции вида FUN_00401234 — те, которые Ghidra не смогла сопоставить с известными библиотечными вызовами. Двойной клик покажет их декомпилированный код. Часто назначение очевидно по контексту: какие API вызываются внутри, какие строки используются.

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

Retype Variable (правый клик → Retype Variable или Ctrl+L) — исправление типа данных. undefined4int, undefined8long или char *. Не обязательно для решения задачи, но делает код заметно понятнее.

Edit Function Signature (правый клик на имени функции) — исправление прототипа. Если Ghidra ошиблась с количеством или типами параметров, правильная сигнатура мгновенно улучшит весь декомпилированный код функции. Декомпилятор бинарного кода в Ghidra не идеален — но при систематических переименованиях и правках типов он выдаёт результат, достаточный для восстановления логики подавляющего большинства CTF-задач.

Разбор бинарника в Ghidra: от файла до флага

Допустим, strings и ltrace не дали результата — пароль зашифрован или логика нестандартная. Переходим к полному статическому анализу.

Импортируем файл, запускаем автоанализ. В Symbol Tree → Functions находим main. Двойной клик — декомпилятор показывает псевдокод. Типичный password checker после автоанализа (пример для демонстрации — так выглядит реальный вывод декомпилятора Ghidra):

void main(void) {
  char local_78[104];
  int iVar1;
  puts("Enter the key:");
  fgets(local_78, 100, stdin);
  iVar1 = strcmp(local_78, "r3v3rs3_m3");
  if (iVar1 == 0) {
    puts("Flag: CTF{well_done}");
  } else { puts("Try again."); }
}

Пароль — строковый литерал "r3v3rs3_m3", видимый прямо в декомпиляторе. Задача закрыта за секунды. Но на CTF среднего уровня эталон не лежит открытым текстом — ввод трансформируется перед сравнением. Классический паттерн — XOR:

void main(void) {
  char local_48[32];
  fgets(local_48, 32, stdin);
  for (int i = 0; i < 16; i++) {
    local_48[i] = local_48[i] ^ 0x37;
  }
  if (memcmp(local_48, &DAT_00402010, 16) == 0)
    puts("Correct!");
  else puts("Wrong!");
}

Логика: программа XOR-ит каждый символ ввода с ключом 0x37, сравнивает результат с массивом байт по адресу DAT_00402010. Пример упрощён — в реальном CTF-бинарнике обычно есть проверка длины ввода перед XOR, чтобы не обрабатывать мусор. XOR — обратимая операция: encrypted ^ key = original. Чтобы получить пароль, берём байты эталона и XOR-им каждый с 0x37.

Как добраться до байтов эталона: двойной клик на DAT_00402010 в декомпиляторе перенесёт Listing к этому адресу. Увидите сырые байты в hex. Выделите 16 байт, правый клик → Copy Special → Byte String — скопируете последовательность. Дальше — скрипт на Python из пяти строк: итерация по массиву с XOR 0x37 на каждый элемент, вывод результата как строки. Операция настолько частая на CTF, что шаблон стоит держать под рукой.

Два подхода к решению:

Чистый — восстановить логику, написать обратный алгоритм, вычислить правильный ввод. Даёт полное понимание и работает на любой сложности.

Патчинг — найти в Listing инструкцию JNZ (jump if not zero) после memcmp и заменить на JZ (jump if zero) через правый клик → Patch Instruction. Программа примет любой ввод. На CTF оба подхода засчитываются.

Грязный путь — не позор. Позор — четыре часа ковырять алгоритм, когда задача закрывалась инверсией одного байта.

Stripped-бинарники в CTF reverse заданиях

В задачах среднего уровня и выше бинарники часто собраны без отладочных символов — stripped. В реальных атаках удаление символов из payload'а классифицируется как T1027.008 (Stripped Payloads, тактика Defense Evasion по MITRE ATT&CK). В CTF stripped-бинарник — просто результат сборки с флагом strip, но эффект тот же: в Symbol Tree не будет main — только набор функций вида FUN_00401xxx. Команда file заранее покажет stripped в выводе — и вы будете готовы.

Три способа найти главную функцию:

Через entry и __libc_start_main. В Symbol Tree всегда есть entry — точка входа ELF-файла. Двойной клик покажет код, вызывающий __libc_start_main. Штука в том, что динамически слинкованные stripped-бинарники сохраняют символы импортов — libc нуждается в них для корректной работы. Для статически слинкованных stripped-бинарников символ __libc_start_main тоже может отсутствовать — тогда потребуется поиск по паттерну кода или FunctionID. Один из аргументов __libc_start_main — адрес main. Ghidra покажет его как первый параметр вызова. Двойной клик на этом адресе — и вы в целевой функции. Переименуйте через L в main.

Через строки. Откройте Defined Strings. Найдите строки, связанные с логикой задачи: "Enter password", "Correct", "Wrong". Правый клик → References → перейдите к функции, которая их использует. Даже если она называется FUN_00401234 — это ваша цель. Переименуйте, все xrefs обновятся автоматически.

Через Function Call Graph. Window → Function Call Graph строит граф вызовов. Функция, которая обращается к puts, scanf/fgets, strcmp/memcmp — почти наверняка содержит основную логику задачи. Ищите узлы с наибольшим количеством исходящих рёбер к библиотечным вызовам.

Ghidra туториал: паттерны и типичные ошибки при дизассемблировании

Частые конструкции в CTF reverse

Password checker — ввод сравнивается с эталоном. Эталон хранится открытым текстом, зашифрован XOR, захеширован или собран на стеке побайтово.

Flag checker — программа проверяет формат флага (flag{...}) и пропускает его через серию проверок: длина, допустимые символы, математические соотношения между символами. Чистое решение требует восстановления всех условий.

Обфускация строк. Распространённый приём — сборка строк на стеке побайтово (в реальных атаках классифицируется как T1027, Obfuscated Files or Information по MITRE ATT&CK, в CTF — просто способ спрятать строку от strings): local_20[0] = 0x66; local_20[1] = 0x6c; ... — это ASCII-коды символов. Утилита strings такие строки не находит, потому что в секции данных их нет — они существуют только в рантайме. В декомпиляторе Ghidra это выглядит как цепочка присваиваний. Выделите hex-значение → правый клик → Convert → Char покажет символ.

Многошаговые трансформации. На уровне medium ввод проходит через цепочку: base64 → XOR → ROT13 → сравнение. Восстанавливайте каждый шаг последовательно, начиная с конца — от точки сравнения к вводу. Декомпилятор покажет всю цепочку в одной функции (или в нескольких — переходите по CALL).

Ошибки, которые крадут время

Ошибка Что видите Как исправить
Не запустили автоанализ Пустой Symbol Tree, нет функций Analysis → Auto Analyze
Не переименовали переменные Стена из local_28 и iVar1 Систематически именуйте через L
Начали с entry вместо main Утонули в коде инициализации libc Ищите main в Functions или через строки
Игнорируете Defined Strings Часы анализа вместо секунд Window → Defined Strings — первое действие
Не проверили file/strings Открыли Ghidra для задачи, решаемой без неё Всегда начинайте с терминала
Function Graph нечитаем Мелкий масштаб, блоки не видно ПКМ → Properties → Start Fully Zoomed In

Основы реверс-инжиниринга: следующие шаги после Ghidra

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

Следующий уровень — комбинация Ghidra с динамическим анализом. На Linux — gdb (с расширениями pwndbg или GEF, без них gdb — боль), на Windows — x64dbg. Рабочая схема: находите интересную функцию в Ghidra, ставите breakpoint по её адресу в отладчике, запускаете бинарник и смотрите реальные значения регистров и переменных в момент исполнения. Два инструмента вместе работают кратно эффективнее каждого по отдельности.

Для автоматизации рутины Ghidra поддерживает скрипты на Java и Python (через Jython). Window → Script Manager открывает библиотеку готовых скриптов. Плагин FindCrypt автоматически находит криптографические константы в бинарнике: AES S-Box, SHA-256 init values и подобное. Для оценки энтропии секций (выявление упакованного или зашифрованного кода) используйте binwalk -E снаружи Ghidra.

Основы реверс-инжиниринга — не знание мнемоник MOV и CMP. Это выработанный рефлекс: stringsltrace → Defined Strings → xref к целевой функции → переименование переменных → восстановление логики. Конвейер одинаков для задачи на 50 очков и для задачи на 500. Разница — в количестве слоёв обфускации, но стартовая точка всегда та же.

За последние пару лет CTF-задачи reverse всё чаще совмещают статический анализ с элементами pwn и crypto. Чистые password checkers с strcmp уходят в warmup. Средний уровень уже требует понимания heap layout, custom allocators, нестандартных шифров. Ghidra с декомпилятором закрывает первый барьер — чтение кода. Но без привычки сверять псевдокод с ассемблерным листингом выше среднего уровня не подняться.

Декомпилятор ошибается — и ошибается регулярно. Путает знаковые и беззнаковые типы, неправильно восстанавливает оптимизированные циклы, пропускает блоки кода при нестандартных calling conventions. Навык чтения ассемблера — не атавизм и не бонус для перфекционистов, а то, что отделяет решающего easy от решающего hard. Полагаться исключительно на окно Decompile — значит добровольно поставить себе потолок. И он станет ощутим уже через десяток-другой решённых задач.

Попробуйте взять любой crackme с crackmes.one уровня 2-3 и пройти весь конвейер: filestringsltrace → Ghidra → переименование → восстановление логики. Если на WAPT интересует связка реверса с веб-эксплуатацией — там эту цепочку разбирают в нескольких модулях с лабами.

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

Поделиться

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

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

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

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

Buffer overflow с нуля: перезапись адреса возврата

8 мин.

2

Buffer overflow с нуля: перезапись адреса возврата

Разбираем переполнение стека на конкретном бинарнике: компилируем без защит, находим смещение до адреса возврата в GDB и перезаписываем EIP. Эксплойт на Python.

10 СЕНТЯБРЬ, 2026

Path Traversal и LFI уязвимости: гайд для CTF

14 мин.

4

Path Traversal и LFI уязвимости: гайд для CTF

Пошаговый разбор path traversal и LFI: обход фильтров, PHP wrappers, цепочки LFI to RCE, log poisoning — с пейлоадами и командами для CTF

9 СЕНТЯБРЬ, 2026

Bash скрипты для CTF: автоматизация разведки

14 мин.

11

Bash скрипты для CTF: автоматизация разведки

Три bash-скрипта для CTF с построчным разбором: автоматизация разведки, брутфорс HTTP-форм, парсинг вывода. Коллекция one-liners и 5 ошибок новичков.

9 СЕНТЯБРЬ, 2026