Изогнутый монитор в тёмной лаборатории отображает декомпилированный байткод Python и граф сетевых соединений. Единственный источник света — экран с янтарными и голубыми схемами.


Infostealers - чемпионы 2024 года: 32% обнаруженных семейств по IBM X-Force Threat Intelligence Index 2025, ransomware позади. В даркнет ежедневно сливается больше 6000 свежих наборов учётных данных. Растущая доля этого потока - Python-based стилеры, упакованные через PyInstaller: BlankGrabber, XillenStealer и десятки их форков расходятся через Discord-каналы, поддельные GitHub-репозитории и «крякнутый» софт. Русскоязычные гайды по анализу малвари заканчиваются на strings и VirusTotal - без разбора PyInstaller, без восстановления байткода, без извлечения C2 из обфусцированных строк. Здесь - полный цикл анализа вредоносного ПО на Python, от бинаря до IoC. Вскрываем, препарируем, достаём адреса.
Pyinstaller - это популярный инструмент для упаковки Python-приложений в standalone-исполняемые файлы.

Бизнес-логика: зачем атакующему Python-стилер и где он в kill chain​

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

Типичная цепочка доставки: жертва скачивает «кряк» или утилиту с GitHub/Discord (initial access). Внутри - PyInstaller-бинарь, при запуске распаковывается Python-скрипт стилера (execution). Скрипт тянет пароли из браузеров Chromium и Firefox, токены Discord, кошельки криптовалют, cookie-файлы и скриншоты (collection). Собранное архивируется и улетает на C2 - обычно через Telegram Bot API или HTTP POST (exfiltration). Украденные credential-пакеты продаются на маркетплейсах Initial Access Broker или идут в ход для дальнейшего проникновения в инфраструктуру жертвы.

Почему Python? Порог входа - на уровне плинтуса. XillenStealer, по данным CYFIRMA, создан разработчиком, который представляется 15-летним подростком. Модульная архитектура: каждая функция (сбор из браузеров, кошельки, скриншоты) живёт в отдельном скрипте. PyInstaller превращает набор .py-файлов в standalone-exe без зависимости от установленного Python на машине жертвы.

С точки зрения MITRE ATT&CK, Python-стилеры реализуют характерный набор техник:
  • Obfuscated Files or Information (T1027, Stealth) - многослойная обфускация кода и шифрование payload
  • System Checks (T1497.001, Stealth / Discovery) - проверки на виртуальную машину и sandbox перед выполнением основного кода
  • Gather Victim Identity Information (T1589, Reconnaissance) - сбор IP, геолокации и сетевой информации через внешние сервисы
Задача аналитика - пройти цепочку в обратном направлении: от бинаря к исходному коду, от обфусцированных строк к C2-адресу и Telegram-бот-токену.

Идентификация PyInstaller-бинаря​

1781066778117.webp

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

Все дальнейшие шаги - исключительно в изолированной среде. Запускать малварь на рабочей машине - идея уровня «а давайте проверим, работает ли огнетушитель, подпалив серверную».
  • ОС: Windows 10/11 или GNU/Linux (Ubuntu 22.04+)
  • RAM: минимум 4 ГБ для статического triage, 8 ГБ для sandbox с динамическим анализом
  • Python: 3.8–3.12, версия должна совпадать с версией анализируемого образца
  • Triage: Detect It Easy (DiE), утилита strings (GNU или Sysinternals)
  • Распаковка: pyinstxtractor (GitHub: extremecoders-re/pyinstxtractor, активно поддерживается)
  • Декомпиляция: uncompyle6 (Python 3.8 и ниже), pycdc / Decompyle++ (Python 3.9+)
  • Sandbox: изолированная VM с FakeNet-NG (Mandiant FLARE) и Wireshark
  • RE нативных компонентов: radare2 6.1.7+ или Ghidra (NSA, активно поддерживается)

Маркеры PyInstaller в образце​

Вопреки распространённому совету искать строку PYZ-00.pyz - имя PYZ-архива гуляет между билдами и конфигурациями PyInstaller. Есть маркеры понадёжнее:
  • MEIPASS - переменная окружения, через которую PyInstaller указывает путь к распакованным ресурсам при runtime
  • Префикс pyi- в именах временных файлов и процессов
  • Модули pyimod02_importers и pyimod03_ctypes - служебные компоненты PyInstaller runtime, сидят в каждом бинаре
  • Паттерн _MEIXXXXXX в именах временных директорий
Проверка одной командой: strings -a suspect.exe | grep -iE "MEIPASS|pyimod|pyi-|_MEI". Есть совпадения - PyInstaller-упаковка подтверждена.

Если строковый поиск ничего не дал (маркеры замаскированы агрессивным пакером поверх PyInstaller), смотрим энтропию. PyInstaller-бинари содержат сжатый архив в overlay-секции PE-файла с энтропией выше 7.5 из 8.0. В Detect It Easy это видно как характерный блок высокой плотности в конце файла на графике Entropy.

[Применимо: triage любых PE-образцов при первичном анализе]

Ограничение: маркеры специфичны для PyInstaller. Бинари Nuitka (компиляция Python в C) и cx_Freeze - другие индикаторы, другие инструменты распаковки. Если strings не нашёл PyInstaller-маркеров при высокой энтропии - проверяйте альтернативные упаковщики.

Распаковка и восстановление .pyc-байткода​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме


Скрипт восстановления с ветками для обоих форматов:
Python:
import struct

MAGIC = {'3.8': 3413, '3.9': 3425, '3.10': 3439, '3.11': 3495, '3.12': 3531}

def restore_pyc(infile, outfile, ver='3.10'):
    magic = struct.pack('<H', MAGIC[ver]) + b'\r\n'  # <H (2 байта LE) + \r\n = стандартный 4-байтовый magic CPython
    # Python 3.3-3.6: magic + 8 нулей (итого 12 байт заголовка)
    # Python 3.7+:    magic + 12 нулей (итого 16 байт заголовка)
    pad = b'\x00' * (8 if ver in ('3.3','3.4','3.5','3.6') else 12)
    with open(infile, 'rb') as f:
        data = f.read()
    with open(outfile, 'wb') as f:
        f.write(magic + pad + data)
Версию Python определяйте по имени DLL в извлечённом каталоге: python310.dll - значит ver='3.10' и magic number 3439.

Декомпиляция и её ограничения​

После восстановления заголовка - декомпиляция:
  • Python ≤3.8: uncompyle6 restored.pyc > output.py. Работает стабильно для этих версий, но проект архивирован и обновлений не получает
  • Python 3.9+: pycdc restored.pyc > output.py. Decompyle++ (C++-декомпилятор), частично поддерживает 3.9–3.12
Где pycdc спотыкается: сложные конструкции (walrus operator :=, match/case из 3.10+, вложенные comprehensions) могут быть восстановлены криво или пропущены. Для критичных фрагментов - работа с сырым байткодом через модуль dis:

python -c "import dis, marshal; f=open('restored.pyc','rb'); f.read(16); dis.dis(marshal.loads(f.read()))"

Команда применима только к restored.pyc после восстановления заголовка скриптом restore_pyc выше. Для сырого .pyc из pyinstxtractor (без полного заголовка) marshal.loads упадёт с ошибкой.

Вызов f.read(16) пропускает 16-байтовый заголовок - парная операция к скрипту восстановления для Python 3.7+. Для образцов Python 3.3–3.6 замените на f.read(12).

Деобфускация: статический анализ стилера

1781066852894.webp

Многослойная обфускация (T1027)​

Python-стилеры применяют технику Obfuscated Files or Information (T1027) последовательными слоями - матрёшка, где каждый слой прячет следующий. Разбор BlankGrabber от Splunk Threat Research Team показывает типичную архитектуру:

Stage 1 - зашифрованный payload внутри PyInstaller: среди извлечённых файлов лежит blob с высокой энтропией (в BlankGrabber - файл blank.aes). Расшифровка при runtime через AES-GCM с захардкоженным ключом и IV, которые сидят в байткоде первого .pyc-модуля. Нюанс: BlankGrabber использует кастомизированную реализацию AES-GCM, подменяя стандартную pyaes внутри PyInstaller-пакета. Стандартная расшифровка не сработает - нужно воспроизвести модифицированный алгоритм из декомпилированного кода.

Stage 2 - обфусцированный Python-скрипт: расшифрованный архив содержит ещё один .pyc (stub-o.pyc в BlankGrabber). После декомпиляции - zlib-сжатый byte-массив, который при распаковке оказывается Python-кодом, закодированным Base64 + ROT13 + реверс строки. Каждый слой снимается тривиально, но их комбинация замедляет ручной разбор.

Stage 3 - итоговый payload: конфигурация стилера с C2-адресами, Telegram-токенами, списком целевых приложений и набором anti-sandbox проверок.

XOR-шифрование строк​

Самый распространённый метод сокрытия C2-адресов, bot-токенов и API-ключей. Встречается с однобайтовым и многобайтовым ключом:
Python:
def xor_decrypt(data, key):
    if isinstance(key, int):  # однобайтовый ключ
        return bytes(b ^ key for b in data)
    # многобайтовый ключ - XOR с циклическим повторением
    return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))

encrypted = b'\x82\x85\x8c\x8c\x8f'
print(xor_decrypt(encrypted, 0xED))  # b'Hello'
[Применимо: Python-стилеры всех семейств с XOR-обфускацией; не работает для AES/ChaCha20, где ключ вычисляется при runtime]

Как найти XOR-ключ, если он неочевиден. Частотный анализ: если зашифрованный blob содержит много нулевых байт в оригинале (типично для padding в конфигурационных структурах), самый частый байт в шифротексте и есть ключ. Для многобайтовых ключей - анализ позиционных частот. Метод старый как мир, но на этих стилерах работает безотказно.

В XillenStealer, по данным CYFIRMA, C2-адрес не хранится как единая строка - собирается через конкатенацию фрагментов. strings тут бесполезен, но декомпиляция решает: в восстановленном коде логика сборки видна целиком.

AST-подход к массовой деобфускации​

Когда обфускатор заменяет строковые литералы на вызовы функций расшифровки - паттерн d('\x48\x65\x6c\x6c\x6f') вместо 'Hello' - руками не масштабируется. Модуль ast стандартной библиотеки Python позволяет написать трансформер: обойти синтаксическое дерево, найти все вызовы дешифратора, выполнить их и подставить результат. Тысячи обфусцированных строк - за секунды.

Когда AST не спасает: обфускатор использует exec() / eval() для динамического исполнения - статический AST-разбор бессилен, код генерируется только при runtime. Тут единственный путь - динамический анализ.

Динамический анализ вредоносного ПО: от sandbox до C2-адреса

Подготовка sandbox и обход anti-sandbox​

Динамический анализ требует изолированной среды с несколькими настройками, без которых стилер просто не заведётся.

Минимальный стек: VM (VirtualBox или VMware Workstation), FakeNet-NG для перехвата DNS/HTTP без реального интернета, Wireshark на хосте или внутри VM, Process Monitor для файловых операций. RAM VM - минимум 8 ГБ. ОС - Windows 10 с «реалистичным» набором софта (браузеры, офис). Голая система без приложений выдаёт sandbox с первого взгляда.

Перед запуском образца стилеры проводят проверки окружения - техника System Checks (T1497.001, тактики: Stealth, Discovery). Что проверяют BlankGrabber и XillenStealer:

Аппаратные маркеры. MAC-адреса с OUI виртуальных сред: 00:0C:29 (VMware), 08:00:27 (VirtualBox). Модель оборудования через WMI-запросы к Win32_ComputerSystem - строки vmware, virtual, qemu. BlankGrabber, по данным Splunk, содержит расширенный чёрный список UUID, имён компьютеров и аккаунтов, характерных для sandbox-ферм.

Драйверы и процессы. Наличие vboxguest.sys, VBoxService.exe, драйверов VMware. XillenStealer проверяет это в функции check_vm_sandbox() вместе с вызовом IsDebuggerPresent.

Сетевая проверка. Соединение со случайно сгенерированным URL: если запрос не возвращает ошибку - сеть считается симулированной. FakeNet-NG по умолчанию отвечает на любой запрос, так что настройте выборочный DNS, чтобы рандомные домены оставались без ответа.

Реестр адаптеров. Запрос имён сетевых адаптеров - VMware и VirtualBox используют характерные названия драйверов.

Обход для аналитика: сменить MAC-адрес VM, имя компьютера и пользователя на реалистичные, снести Guest Additions / VMware Tools, в FakeNet-NG настроить выборочные DNS-ответы. Несложно, но без этого - тишина в логах.

Для контекста: LummaC2 v4.0, по данным Outpost24, применяет тригонометрический анализ траектории мыши для определения, управляет ли системой человек. Этот уровень anti-sandbox характерен для нативных C-стилеров; в Python-based семействах такого пока не встречал.

Перехват C2-коммуникации​

При запуске образца в sandbox с FakeNet-NG все сетевые обращения перехватываются. Ключевые паттерны эксфильтрации:

Telegram Bot API - самый ходовой канал эксфильтрации в Python-стилерах (BlankGrabber, XillenStealer). Перехватываются POST-запросы к api.telegram.org/bot<TOKEN>/sendDocument или sendMessage. Из запроса достаём: bot token (в URL после /bot), chat ID (параметр chat_id в теле POST), содержимое эксфильтрации (вложенные файлы). XillenStealer формирует одновременно report.txt и report.html, а архивы свыше 45 МБ нарезает функцией split_large_file(), удаляя каждую часть с диска после успешной отправки.

HTTP POST на выделенный сервер - у стилеров с собственной серверной инфраструктурой. В Wireshark: фильтр http.request.method == POST. Тело запроса - JSON или multipart form-data с собранными данными.

IP-lookup сервисы - техника Gather Victim Identity Information (T1589, Reconnaissance). Перед эксфильтрацией стилеры дёргают ip-api.com или ipinfo.io для получения IP, геолокации и определения типа хостинга. BlankGrabber использует поле hosting в ответе ip-api.com для фильтрации sandbox-хостов в облачных средах. DNS-запросы к этим сервисам - дополнительные IoC.

Извлечение C2 из памяти процесса

Если статический анализ не дал C2 - строка собирается динамически, ключ шифрования вычисляется при runtime, или использован нестандартный AES - последний рубеж: дамп памяти.

Последовательность: запустить образец в VM, дождаться сетевой активности (в FakeNet-NG появятся DNS-запросы или HTTP-соединения), снять дамп через procdump -ma <PID> dump.dmp, затем strings -el dump.dmp | grep -iE "http|api.telegram|bot[0-9]".

К моменту обращения к C2 все строки расшифрованы в памяти - метод работает даже против многослойного AES с модифицированной реализацией. Одна команда - и токен у вас.

Генерация IOC и правила обнаружения​

1781066893891.webp

Индикаторы компрометации​

Результат полного цикла анализа - структурированный набор IoC:
  • Хеши: SHA256 бинаря, извлечённых .pyc-файлов, итогового payload
  • Сетевые индикаторы: C2-домены и IP-адреса, Telegram bot token, chat ID, домены IP-lookup сервисов
  • Файловые артефакты: имена создаваемых файлов (report.txt, report.html), пути persistence - Task Scheduler entry WindowsSystemMaintenance в XillenStealer, Registry Run Keys в BlankGrabber
  • Поведенческие маркеры: обращения к Local State и Login Data профилей Chromium-браузеров, доступ к директориям крипто-кошельков (exodus.wallet, директории AtomicWallet, Coinomi, Electrum), чтение tdata Telegram Desktop

Sigma-правила и D3FEND-контрмеры​

В SigmaHQ по тегу T1027 доступно 140 правил, включая proc_creation_win_powershell_xor_encoded_command.yml для обнаружения XOR-кодированных команд. По T1497.001 - правило posh_ps_detect_vm_env.yml фиксирует скрипты, проверяющие виртуальное окружение.

Контрмеры MITRE D3FEND для PyInstaller-стилеров:

D3FEND IDКонтрмераПрименение
D3-DADynamic AnalysisSandbox-детонация обфусцированных payload
D3-EFAEmulated File AnalysisЭмуляция исполнения файла для детекции вредоносного поведения, в т.ч. обхода sandbox-проверок
D3-FAFile AnalysisЭнтропийный анализ PE, проверка overlay-секций
D3-SCASystem Call AnalysisМониторинг syscalls для обнаружения VM-проверок

Полный workflow: от бинаря до IoC​

  1. Triage: SHA256 в VirusTotal / MalwareBazaar, DiE для энтропии и маркеров PyInstaller
  2. Распаковка: pyinstxtractor.py, восстановление .pyc-заголовка, декомпиляция через uncompyle6 или pycdc
  3. Статический анализ: снятие слоёв обфускации, XOR-расшифровка строк, поиск C2-адресов и токенов
  4. Динамический анализ: sandbox с FakeNet-NG, перехват DNS/HTTP, дамп памяти при необходимости
  5. Документирование: хеши, сетевые IoC, файловые артефакты, MITRE ATT&CK mapping, Sigma rules
Каждый этап может завершить анализ: если XOR-ключ найден на шаге 3, динамический анализ становится верификацией, а не необходимостью.

Каждый второй Python-стилер в публичных репозиториях образцов собран из open-source конструктора за вечер. BlankGrabber, XillenStealer, безымянные форки - не APT-инструменты, а конвейер с нулевым порогом входа для атакующего. Именно это и делает их опасными: не сложность отдельного образца, а масштаб. IBM оценивает поток в 6000 наборов credentials в день, и заметная часть - результат «детских» стилеров, которые при этом успешно обходят базовые антивирусные проверки.

Технически ломаются они быстро: XOR-шифрование снимается за минуты, AES с захардкоженным ключом - за час, Telegram-токен из дампа памяти - за одну команду. Но аналитиков, способных пройти цепочку от PyInstaller-бинаря до полного набора IoC, кратно меньше, чем новых образцов каждый день.

Инструментарий доступен и бесплатен: pyinstxtractor, pycdc, FakeNet-NG. Порог входа в анализ вредоносного ПО на Python ниже, чем принято считать. Проблема не в инструментах, а в отсутствии практики на реальных образцах - теория без разбора живого семпла остаётся мёртвым знанием. Собрать навык можно только на задачах - на HackerLab.pro (https://hackerlab.pro) есть reverse-инженерные таски, где цепочка от бинаря до C2 проходится end-to-end.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab