Проведение многих последних исследований вредоносного программного обеспечения автоматизируется с использование больших языковых моделей (БЯМ, Large Language Model, LLM). Исследователь загружает декомпилированный псевдокод отдельного модуля вредоноса из IDA Pro / Ghidra и отправляет в БЯМ с просьбой разъяснить код.
У данного подхода есть ряд недостатков:
часть IOC (Indicator of Compromise) сливается в общую базу выбранной модели;
некоторые открытые модели могут цензурить описание некоторых модулей, из‑за чего исследователь пропустит описание функционала;
структура вредоносной программы может быть довольно запутанной, что приведет к переполнению контекста диалога БЯМ (привет Claude, с покупкой токенов за каждые 3 вопроса).
При количестве функций в районе 100–200 (типичный небольшой троян или шифровальщик) это часы рутинной работы.
В связи с развитием локальных LLM возникла идея автоматизировать первоначальный проход по псевдокоду вредоноса для автоматического выявления части функционала и подсвечивания наиболее значимых для исследования функций.
Для этого предлагается соединить следующие технологии:
PyGhidra (Python‑биндинг Ghidra) — для дизассемблирование и декомпиляция без графического интерфейса для удобства парсинга данных и загрузки в базу.
Neo4j — база данных, для удобного представления функций из дизассмблера в виде графа. Выбрана из‑за удобства последующего обхода последовательностей вызовов функций и последующего добавления описания из БЯМ.
Локальная LLM (Qwen3, запущенная через LM Studio с API‑ключом для доступа) — для анализа каждой функции.
Архитектура системы
Графически структура проекта может быть представлена следующим образом:

По данной структуре навайбкодился за пару вечеров проект, исходный код которого можно увидеть в репозитории: Dvoranchik/MLMalwareAnalyzer: Analyze Malwares by local LLM.
Процесс обработки вредоносной программы описывается следующими последовательными шагами, реализованными в модуле pipeline.py:
Извлечение фактов (
ghidra_extractor.py) — PyGhidra дизассемблирует и декомпилирует бинарник.
Для каждой функции извлекается:
— адрес, имя, размер, цикломатическая сложность (число базовых блоков);
— декомпилированный C‑псевдокод черезDecompInterface(с таймаутом 30 секунд на функцию и обрезкой до 6000 символов, чтобы не ломать контекст LLM);
— граф вызовов (getCalledFunctions);
— строковые ссылки (getReferencesFrom+hasStringValue);
— таблица импортов (ExternalManager).
Все это загружается в Neo4j.
Отдельно вычисляются метаданные образца (формат PE, архитектура/LanguageID из Ghidra, точки входа) и все стандартные хэши файла (MD5, SHA1, SHA256).LLM‑анализ функций (
worker.py+mcp.py) — каждая функция по очереди отправляется в локальную LLM с запросом на выполнение анализа: назначение, IOC, теги, сетевые индикаторы, команды запуска, техники уклонения. Все это возвращается в виде JSON.
Каждой функции в промпт передаётся не только декомпилированный код, но и её позиция в графе вызовов — какие функции она вызывает и кто вызывает её. Это даёт модели минимальный «контекст соседей».Агрегация возможностей (
aggregator.py) — теги и категории со всех проанализированных функций собираются в узлыCapabilityс весами.Построение цепочек поведения (
builder.py) — на основе комбинацийCapabilityи графа вызовов выполняется поиск поведенческих паттернов (C2-коммуникация, шифрование файлов, антидетект и так далее).Генерация отчёта (
generator.py) — основные накопленные данные снова выгружаются моделью из Neo4j, анализируются и собираются в отчет.
Модели данных в Neo4j
Если анализировать бинарник «в лоб», результат ложится в одну плоскую таблицу: строка на функцию, в ней колонки “summary”, “tags”, “importance” и так далее.
Проблема в том, что реальный вопрос, который интересует аналитика, почти никогда не звучит как «покажи мне строки, где tag = crypto». Ему необходимо: «функция шифрует файлы — вызывает ли она при этом что‑то из тех функций, что открывают сетевое соединение?», «эти пять функций помечены как anti‑debug — связаны ли они между собой вызовами, или это никак не взаимодействующие проверки?».
Граф решает это следующим образом: функция — это узел, вызов другой функции — это ребро между узлами. Вопрос «связаны ли эти пять функций вызовами» превращается в один Cypher‑запрос по графу, а не в рекурсивный JOIN. По этой причине в проекте есть отдельные уровни абстракции — не только «Функция» и «Анализ этой функции», но и «Возможность» (Capability) и «Поведение» (Behavior).
Получаем четыре смысловых уровня:
Sample — хэши, формат, архитектура.
Function — конкретная функция из файла: адрес, декомпилированный код, размер.
Analysis — результат разбора функции локальной LLM: что она делает, какие у неё IOC, какие теги.
Capability — Behavior — обобщения на основе множества функций: сначала отдельный функционал бинарника (умеет работать с файлами, умеет шифровать), а затем — уже интерпретация их в поведение («если есть работа с файлами И шифрование одновременно — скорее всего, шифровальщик»).
Каждый следующий уровень строится от предыдущего отдельным шагом, и на каждом уровне — в граф вносятся новые узлы.
Узлы имеют следующие типы:
Sample— один узел на весь анализируемый файл. Хранит все виды хэшей (MD5, SHA1, SHA256), формат (PE/ELF), архитектуру и точки входа.Function— один узел на каждую функцию, хранит: адрес как уникальный идентификатор, имя (из GhidraFUN_xxxxx), декомпилированный C‑псевдокод, метрика сложности (число базовых блоков), флаг «это библиотечная функция без своего тела» и флаг «уже проанализирована LLM».String— строковые константы, на которые ссылаются функции. Необходимо, чтобы увидеть все функции, обращающиеся к одной и той же подозрительной строке.Import— запись из таблицы импортов PE‑файла: какая DLL и какая функция из неё используется.Analysis— результат разбора одной функции локальной LLM.Capability— обобщённый функционал бинарника, собранный агрегацией тегов сразу с нескольких функций (например, «умеет работать с файлами» — это может быть подтверждено 40 разными функциями). Хранит вес (strength) — насколько часто эта способность подтверждается по всему бинарнику.Behavior— итоговая интерпретация: комбинация нескольких Capability, сложенная в конкретное поведение верхнего уровня («шифрование файлов», «связь с C2-сервером»).
Связи узлов могут быть выражены следующим образом:

Проверка работоспособности
Проверка программы проводилась на образцах WannaCry из открытых источников (MalwareBazaar). После полного прогона (~195 функций, анализ занял 126 минут, при моих мощностях в 60 ГБ оперативной памяти и Nvidia 5070 TI) отчёт показал три поведенческих паттерна с уверенностью 100%: File Encryption (40 функций‑доказательств), C2 Communication, Anti‑Analysis. YARA‑правило DebuggerCheck__API на сайте подтвердило найденный пайплайном anti-debug паттерн.
В будущем думаю еще доработать архитектуру вызовов и улучшить промтинг для поиска дополнительных индикаторов.
