Перейти к содержимому

Реверс стилера в Ghidra: снимаем антиотладку и восстанавливаем шифрование конфига

Реверс стилера в Ghidra: снимаем антиотладку и восстанавливаем шифрование конфига
Время чтения: 13 мин.

Получил из песочницы ANY.RUN сэмпл инфостилера — PE-файл на 380 КБ, детект больше 40 движков на VirusTotal. Подцепил его в x64dbg — процесс умер за доли секунды, не дойдя даже до первого сетевого вызова. Знакомо? Антиотладочные проверки убивают процесс раньше, чем ты успеваешь поставить второй брейкпоинт. Остался реверс стилера в Ghidra: статический разбор без запуска бинарника. За вечер из сэмпла вытащил три C2-адреса, полный список целевых браузерных профилей и алгоритм шифрования конфига. Ниже — пошаговый разбор, от первой проверки IsDebuggerPresent до финального XOR-ключа.

Зачем стилеру антиотладка и зашифрованный конфиг

Стилер — вредоносная программа, которая крадёт учётные данные: пароли из браузеров, cookies сессий, файлы криптокошельков, токены мессенджеров. Семейства вроде RedLine, Vidar, Raccoon работают по одной схеме: оказались на машине жертвы, собрали данные, отправили на C2-сервер (Command & Control — удалённый сервер, куда стилер отгружает украденное).

Чтобы понять, зачем стилеру антиотладка и шифрование, полезно увидеть его место в цепочке атаки (kill chain — последовательность шагов от первого контакта с жертвой до конечной цели злоумышленника):

  1. Initial Access — жертва скачивает «кряк», «активатор» или открывает вложение из фишингового письма
  2. Execution — лоадер запускает стилер: иногда прямым запуском .exe, иногда инъекцией в легитимный процесс
  3. Defense Evasion — стилер проверяет, не работает ли он под отладчиком или в песочнице. В классификации MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1622 — её идентификаторы, по которым аналитики маркируют поведение малвари) это Debugger Evasion (T1622). Конфиг зашифрован, чтобы антивирус не вытащил C2-адрес сигнатурным поиском — Obfuscated Files or Information (T1027)
  4. Credential Access — стилер расшифровывает конфиг, узнаёт, какие данные красть, и обходит целевые пути: профили Chrome, Firefox, Telegram, криптокошельки (Credentials In Files, T1552.001)
  5. Exfiltration — собранный архив уходит HTTP-запросом на C2

Зачем автору малвари шифровать конфиг? Две причины. Первая — C2-адрес в открытом виде мгновенно детектируется антивирусом по сигнатуре, и сэмпл блокируется ещё до запуска. Вторая — конкурирующие группировки парсят чужие стилеры, вытаскивают адреса C2-панелей и перехватывают логи. Шифрование конфига усложняет оба сценария.

Антиотладка решает смежную задачу: стилер должен отработать быстро — собрать данные за секунды и самоуничтожиться. Если аналитик подцепит отладчик, он увидит расшифрованный конфиг в памяти, извлечёт C2 и отправит abuse-запрос хостеру. Антиотладочные проверки дают стилеру фору: бинарник молча завершается, если чувствует дебаггер, и аналитику приходится переходить к статическому анализу вредоносного ПО, чтобы добраться до конфига.

Что нужно для работы

Анализировать вредоносный сэмпл на хостовой ОС нельзя: случайный двойной клик — и стилер отработает по назначению.

Требования к окружению:

  • Виртуальная машина: Windows 10 или 11 в VirtualBox / VMware. Сетевой адаптер внутри ВМ отключён. RAM для ВМ: минимум 4 ГБ, рекомендуется 8 ГБ — Ghidra на JVM активно ест память при авто-анализе
  • Ghidra 11.x — скачивается с GitHub (NationalSecurityAgency/ghidra). Установки нет: распаковка архива, запуск ghidraRun.bat. Требует JDK 17+, скачать с adoptium.net
  • Python 3.10+ — для скриптов дешифровки, установлен внутри ВМ
  • Сэмпл — из MalwareBazaar, ANY.RUN или Triage. Работаем с выгруженным PE-файлом. Не запускаем его вручную
  • Опционально: x64dbg для динамической верификации гипотез после снятия антиотладки; CFF Explorer для быстрого просмотра PE-заголовков

Первый взгляд: импорт сэмпла и разведка в Ghidra

Импортируйте сэмпл в Ghidra: File → New Project → Non-Shared Project, перетащите файл в окно проекта, откройте двойным кликом. На предложение запустить авто-анализ — соглашайтесь с настройками по умолчанию. Ghidra разберёт импорты, восстановит функции и найдёт строки. Ожидаемое время: от 10 секунд до пары минут в зависимости от размера бинарника. Когда прогресс-бар внизу справа пропадёт — анализ завершён.

Первое, что стоит проверить — упакован ли сэмпл (Software Packing, T1027.002). Признаки упаковки: откройте Window → Defined Strings. Если строк меньше 20–30 и среди них нет ничего осмысленного (ни имён WinAPI, ни путей к файлам) — бинарник почти наверняка упакован. Второй признак: в Program Trees одна-две секции с энтропией выше 7.0 (проверяется через Window → Entropy). UPX-пакер оставляет характерную сигнатуру UPX! в первых байтах — видно в листинге по адресу 0x0. Если сэмпл упакован — перед анализом нужна распаковка стилера: для UPX достаточно upx -d sample.exe, для кастомных пакеров придётся дампить память после распаковки в рантайме через x64dbg.

Если строк много (несколько сотен), видны пути к файлам, HTTP-заголовки, имена функций — бинарник не упакован, двигаемся дальше.

Откройте Symbol Tree → Imports (левая панель). Здесь перечислены DLL и вызываемые из них функции. Ищите характерные имена: IsDebuggerPresent, CheckRemoteDebuggerPresent, NtQueryInformationProcess, GetTickCount64, QueryPerformanceCounter. Каждое из этих имён — маркер антиотладочного приёма. Двойной клик на импорте → правый клик → References → Show References To Address покажет все места в коде, где вызывается эта функция.

Антиотладочные приёмы малвари в декомпиляторе Ghidra

IsDebuggerPresent и PEB-проверки

IsDebuggerPresent — самая элементарная проверка. Функция из kernel32.dll возвращает 1, если к процессу подключён отладчик, и 0, если нет. В декомпиляторе Ghidra (правая панель Code Browser) это выглядит так:

if (IsDebuggerPresent() != 0) {
    ExitProcess(0);
}

Некоторые сэмплы не вызывают WinAPI напрямую, а читают флаг BeingDebugged из PEB (Process Environment Block — структура в памяти процесса, где Windows хранит отладочные флаги и метаданные). Реализуется через обращение к fs:[0x30] (в 32-битном PE) или gs:[0x60] (в 64-битном) с последующим чтением байта по смещению +0x2. Результат тот же: ненулевой байт — процесс завершается.

Более хитрый вариант — NtQueryInformationProcess с аргументом ProcessDebugPort (0x07). Эта функция из ntdll.dll возвращает ненулевое значение, если процесс отлаживается. В Ghidra импорт может быть не разрешён по имени — ищите вызов через GetProcAddress(hNtdll, "NtQueryInformationProcess") и последующий indirect call через указатель.

[Применимо: PE-бинарники x86/x64, стандартные стилеры без виртуализации кода. Не работает, если проверка скрыта внутри виртуализированного обфускатора (VMProtect, Themida) — Ghidra покажет вызовы в виртуальную машину обфускатора, а не реальную логику]

Timing-checks: GetTickCount64 и rdtsc

Замер времени выполнения — вторая по частоте техника. Логика простая: нормальное выполнение блока кода занимает микросекунды, но если аналитик шагает по инструкциям в дебаггере — секунды. Стилер замеряет время до и после контрольного участка, и если разница превышает порог (обычно 1000–5000 мс) — завершает процесс.

В Ghidra ищите пару вызовов GetTickCount64() или QueryPerformanceCounter() с последующим сравнением разницы. Паттерн в декомпиляторе: одна переменная получает значение до блока кода, вторая — после, затем if (end - start > threshold) ExitProcess(0). Переименуйте переменные (правый клик → Rename Variable) в start_time и end_time — паттерн сразу станет очевидным. Я всегда так делаю при первом проходе: переименование переменных экономит часы на втором.

Отдельная разновидность — инструкция rdtsc (Read Time-Stamp Counter), которая читает аппаратный счётчик тактов процессора. Она не отображается как импорт WinAPI, поэтому в Symbol Tree её нет. Ищите мнемонику RDTSC в ассемблерном листинге через Search → Program Text.

TLS Callbacks — код до main

TLS Callback (Thread Local Storage Callback) — функция, которую Windows вызывает до точки входа программы. Антиотладочные проверки внутри TLS Callback срабатывают раньше, чем отладчик успеет остановиться на main. Если вы поставили брейкпоинт только на entry point и процесс всё равно умирает — проверьте TLS.

Ghidra обнаруживает TLS Callbacks автоматически при импорте PE-файла. Ищите их в Symbol Tree → Functions — Ghidra может пометить их как tls_callback_0, tls_callback_1. Если такие функции содержат вызовы IsDebuggerPresent или сравнения с ProcessDebugPort — антиотладка спрятана именно здесь.

Обход антиотладки малвари: патчим проверки в Ghidra

Задача — нейтрализовать проверки, чтобы бинарник можно было запустить под отладчиком для верификации гипотез. Ghidra позволяет сделать это статическим патчингом: меняем инструкции прямо в листинге и экспортируем модифицированный бинарник.

Шаг 1. Найдите условный переход после проверки. В листинге (центральная панель) кликните на строку с вызовом IsDebuggerPresent. Декомпилятор подсветит соответствующий C-подобный код справа. Ниже в листинге будет инструкция TEST EAX, EAX (проверка возвращаемого значения), а за ней — условный переход JNZ или JNE (Jump if Not Zero / Jump if Not Equal), который направляет поток на блок с ExitProcess.

Шаг 2. Инвертируйте или занопте переход. Правый клик на инструкции JNZ → Patch Instruction. Два варианта: — Замените JNZ на JZ (Jump if Zero) — теперь условие инвертировано, процесс продолжит работу при наличии отладчика — Замените JNZ на два NOP (No Operation): байты 0x75 0xXX0x90 0x90. Проверка перестанет влиять на поток выполнения

Я обычно предпочитаю NOP — проще и нет риска перепутать логику, особенно когда проверок в бинарнике штук пять.

Шаг 3. Обработайте timing-check. Для проверок по времени — найдите инструкцию CMP (сравнение разницы с порогом) и следующий за ней JA (Jump if Above). Замените JA на NOP NOP либо измените пороговое значение через Patch Data на 0xFFFFFFFF — любая реальная задержка будет меньше.

Шаг 4. Проверьте TLS Callbacks. Если антиотладка размещена в TLS Callback — тот же подход: найдите условный переход внутри callback-функции и занопте его.

Шаг 5. Экспортируйте патченый бинарник. File → Export Program → Format: Binary. Сохраните как новый .exe.

Как понять, что получилось: запустите патченый бинарник в x64dbg внутри ВМ. Если раньше процесс умирал за доли секунды, а теперь доходит до сетевых вызовов (видно по вкладке Handles или логу системных вызовов) — антиотладка снята. Стилер может начать резолвить DNS и пытаться установить HTTP-соединение с C2 — это нормально, сеть в ВМ отключена, запрос никуда не уйдёт.

Восстановление алгоритма шифрования конфига стилера

После обхода антиотладки переходим к основной задаче — деобфускация конфига стилера. Конфиг содержит C2-адреса, список целевых приложений, иногда build ID и версию панели. Всё это хранится в зашифрованном виде внутри секции данных бинарника (Deobfuscate/Decode Files or Information, T1140).

Находим зашифрованный блоб

Первый способ: Window → Defined Strings в Ghidra. В типичном неупакованном стилере видны десятки читаемых строк — пути к профилям браузеров, HTTP-заголовки, имена WinAPI-функций. Среди них попадаются нечитаемые последовательности байтов фиксированной длины — кандидаты на зашифрованный конфиг.

Второй способ надёжнее. Ищите функцию, которая принимает указатель на массив байтов и длину, а возвращает указатель на расшифрованный буфер. Переходите по xref (перекрёстная ссылка — показывает, откуда вызывается функция или откуда читаются данные) от строк, которые стилер использует после расшифровки: "Content-Type", "Mozilla/5.0", "POST", "gate.php". Если строка нашлась в открытом виде — конфиг не зашифрован (редкость). Если нет — проследите вызовы функций, которые генерируют эти строки: одна из них будет дешифратором.

Третий маркер — функция, которая вызывается в самом начале main или WinMain, до любой полезной активности, и заполняет глобальный массив или структуру. Часто это инициализация конфига: расшифровка блоба в глобальную переменную, к которой потом обращаются десятки других функций. Если видите функцию с одним xref из main и кучей xref на глобальный массив — начинайте с неё.

Определяем схему: XOR, multi-byte XOR или RC4

Три самых распространённых алгоритма шифрования конфигов в commodity-стилерах (массовых стилерах, которые продаются как сервис):

Однобайтовый XOR. Каждый байт конфига XOR’ится с одним и тем же ключом (например, 0x55). В декомпиляторе Ghidra это цикл с операцией buf[i] = buf[i] ^ key, где key — одна переменная без индексации. Самый простой вариант: если ключ — единственный аргумент функции помимо указателя и длины.

Multi-byte XOR. Массив XOR’ится с ключом длиной N байт по формуле buf[i] = buf[i] ^ key[i % key_len]. В декомпиляторе появляется операция % (остаток от деления) или два вложенных счётчика. Ключ обычно лежит рядом с зашифрованными данными в секции .data или .rdata. Длина ключа — от 4 до 32 байт.

RC4. Потоковый шифр с двумя фазами: KSA (Key Scheduling Algorithm — инициализация массива S-box из 256 элементов) и PRGA (Pseudo-Random Generation Algorithm — генерация потока для XOR). В Ghidra RC4 распознаётся по характерному паттерну: два цикла до 256 в первой части функции и swap-операции с XOR во второй. Если в декомпиляторе видите локальный массив на 256 элементов типа byte, два цикла с i от 0 до 255, и операции вида temp = s[i]; s[i] = s[j]; s[j] = temp — перед вами RC4. Этот паттерн настолько узнаваемый, что со временем начинаешь видеть его за секунду — как знакомое лицо в толпе.

Ключ к RC4 часто хардкодится строкой или массивом байтов. Ищите первый аргумент, передаваемый в функцию с 256-элементным массивом — это и есть ключ.

Пишем дешифратор и вытаскиваем C2

Когда алгоритм определён и ключ найден — дело за скриптом. Для multi-byte XOR:

encrypted = bytes.fromhex("4a1f3c...")  # скопировать из Ghidra: Copy Special → Byte String
key = b"\x55\xAA\x0F\x31"              # ключ из секции .rdata
decrypted = bytes([b ^ key[i % len(key)] for i, b in enumerate(encrypted)])
print(decrypted.decode("utf-8", errors="replace"))

Чтобы скопировать зашифрованный блоб: выделите диапазон байтов в листинге Ghidra → правый клик → Copy Special → Byte String (No Spaces). Ключ берётся аналогично — по адресу, который передаётся вторым аргументом в функцию-дешифратор.

Ожидаемый результат: в консоли появятся читаемые строки — URL вида http://185.X.X.X/gate.php, пути типа \Google\Chrome\User Data\Default\Login Data, иногда build ID. Если вместо читаемого текста — мусор, проверьте три вещи: правильно ли скопирован блоб (длина совпадает?), верный ли ключ (адрес?), верный ли алгоритм (может быть RC4, а не XOR).

Для RC4 — реализуйте KSA и PRGA на Python вручную (20 строк) или возьмите библиотеку arc4 (pip install arc4). Принцип тот же: передаёте ключ и зашифрованный буфер, получаете расшифрованный конфиг.

Каждый извлечённый C2-адрес — actionable IOC (Indicator of Compromise, индикатор компрометации — конкретный технический артефакт, по которому можно обнаружить заражение или заблокировать угрозу). Его можно передать в CERT, добавить в блоклист фаервола, использовать для threat intelligence. Список целевых приложений из конфига показывает, что именно стилер собирает — это помогает оценить масштаб ущерба при реальном инциденте.

Ограничения: когда анализ вредоносного сэмпла этим путём не пройдёт

Описанный подход работает для массовых стилеров с типовой антиотладкой и стандартным шифрованием. Вот ситуации, когда его недостаточно:

Сценарий Почему не работает Альтернатива
Виртуализация кода (VMProtect, Themida) Ghidra декомпилирует вызовы к обфускатору, не к реальной логике Динамический анализ с дампом после девиртуализации
Ключ шифрования приходит с C2 В бинарнике нет ключа — конфиг расшифровывается только после запроса к серверу Перехват трафика через FakeNet-NG, эмуляция C2-ответа
Кастомный криптоалгоритм Не XOR, не RC4 — авторская схема без узнаваемых паттернов Полный ручной reverse engineering шифрования конфига
Многослойная упаковка Несколько уровней: UPX → custom loader → in-memory payload Послойная распаковка в x64dbg с промежуточными дампами
Сэмпл на .NET / Python Ghidra слабее специализированных инструментов для managed-кода dnSpy для .NET, uncompyle6 для Python

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

Для практики рекомендую брать сэмплы с MalwareBazaar (bazaar.abuse.ch) — там есть фильтр по тегам семейств (redline, vidar, raccoon), и каждый сэмпл доступен с хэшем и метаданными. Начинайте с тех, что помечены тегом exe без тега packed — это упростит первый разбор. На HackerLab.pro есть задачи на реверс, которые помогают отточить навык работы с дизассемблером до перехода к реальной малвари — нужна регистрация, после неё доступны таски разных уровней.

Антиотладка в commodity-стилерах — это копипаста. Из нескольких десятков разобранных сэмплов RedLine и Vidar больше половины использовали один и тот же набор: IsDebuggerPresent, иногда GetTickCount64, редко TLS Callback. Авторы стилеров не тратят время на защиту от реверса — они вкладываются в скорость распространения и смену C2-инфраструктуры. Новый билд с новым C2-адресом появляется каждые несколько часов, и к моменту, когда аналитик закончит разбор, адрес уже может быть мёртв.

Отсюда неочевидный вывод: главный навык малвар-аналитика — не реверс сам по себе, а автоматизация реверса. Один раз разобрали алгоритм шифрования конкретного семейства — написали скрипт на Python или Ghidra API, который вытаскивает конфиг из любого нового билда за секунды, без ручного ковыряния каждого сэмпла. Здесь проходит граница между junior-аналитиком, который каждый раз открывает Ghidra заново, и senior, у которого конвейер скриптов покрывает десятки семейств. Понимание XOR и RC4 на уровне байтов — точка входа. А точка роста — превратить одноразовый разбор в воспроизводимый pipeline.

Эту тему и смежные навыки разбирают на практике в курсе «Профессия Реверс-инженер» Codeby Academy.