Каждый инженер, хоть раз открывавший дизассемблер, помнит это гнетущее чувство интеллектуального тупика. Перед глазами бесконечные листинги инструкций mov, lea и вызовы функций вроде func_004018A0. Инструменты декомпиляции переводят опкоды в Си-подобный псевдокод, но мешанина из нетипизированных указателей void*, переменных uVar1 и раздутых условий далека от живого исходного кода.
Десятилетиями реверс-инжиниринг оставался медитативным, филигранным и дорогим ремеслом. На восстановление исходников небольшой утилиты или консольной игры уходили месяцы ручного труда. Однако появление специализированных моделей (вроде LLM4Decompile) и рассуждающих агентов меняет эту сферу. Объединение анализатора NSA Ghidra с языковыми моделями сформировало замкнутый контур, автоматически превращающий бинарные дампы в чистый, типобезопасный код на C или Rust.
Туман декомпиляции: почему классический статический анализ застревает на полпути
Чтобы оценить масштаб прорыва, важно понять, что компилятор с жесткими оптимизациями (-O2, -O3) безжалостно стирает метаинформацию:
- Стирание типов и структур: объекты превращаются в плоские смещения относительно регистров (например,
*(int*)(ESI + 0x18)). Декомпилятор видит доступ к байтам памяти, но не знает, что это: поле структуры профиля или ширина графического буфера. - Инлайнинг функций и разворачивание циклов: лаконичные вызовы растворяются внутри вызывающего кода, превращаясь в длинные монолитные последовательности инструкций.
- Размытие управляющих конструкций: операторы
switchиif-elseпревращаются в таблицы переходов и хаос безусловных прыжковjmp.
Классический декомпилятор восстанавливает лишь поток управления и приблизительную арифметику. Смысловую логику до последнего времени мог извлечь только человек.
Двухлетний марафон за 84 дня: практический прорыв на реальных бинарниках
Показательный пример произошел в сообществе ретро-инженерии. Специалист по реверсу Крис Льюис потратил почти два года кропотливой работы на декомпиляцию бинарного кода игры Snowboard Kids для приставки Nintendo 64 — тысячи часов за ассемблерными листингами и ручным сопоставлением байтов.
Когда тот же процесс воспроизвели с использованием агентных LLM-циклов, срок реконструкции сократился до 84 дней — почти в девять раз быстрее. По оценкам экспертов по безопасности, с развитием моделей время восстановления типовых бинарных модулей сжимается до считанных дней.
Модель в этой связке работает как эксперт по распознаванию алгоритмических паттернов: видя разрозненные битовые сдвиги и операции XOR, она узнает реализацию шифрования AES или расчет контрольной суммы CRC32 и заменяет сотню строк псевдокода на одну чистую функцию.
Архитектура верифицируемого контура: компилятор как беспристрастный судья
Главная опасность слепого доверия нейросетям в системном программировании — скрытые галлюцинации. Решением стал подход Verifiable Feedback Loop (Верифицируемый цикл с обратной связью), где модель работает в постоянном спарринге с компилятором:
- Шаг 1: Декомпилятор Ghidra извлекает сырой псевдокод из целевого бинарника;
- Шаг 2: LLM анализирует псевдокод, восстанавливает типы данных, дает осмысленные имена переменным и генерирует код на Rust;
- Шаг 3: Компилятор (
rustc) пытается собрать код. При ошибках синтаксиса или заимствования ссылок диагностический лог возвращается в контекст модели; - Шаг 4: Тестовый стенд запускает оригинальный и скомпилированный бинарники на одинаковых входных данных, сверяя дампы памяти и поток STDOUT;
- Шаг 5: Цикл повторяется до полной функциональной идентичности.

Извлечение псевдокода без интерфейса: автоматизация Ghidra в режиме Headless
Для построения конвейера ручные клики в интерфейсе исключаются. Используется консольная утилита analyzeHeadless и Java-скрипт декомпиляции (DecompileExport.java):
import ghidra.app.script.GhidraScript;
import ghidra.app.decompiler.*;
import ghidra.program.model.listing.*;
import java.io.PrintWriter;
public class DecompileExport extends GhidraScript {
@Override
public void run() throws Exception {
DecompInterface decompiler = new DecompInterface();
decompiler.openProgram(currentProgram);
PrintWriter writer = new PrintWriter(getScriptArgs()[0]);
FunctionManager funcManager = currentProgram.getFunctionManager();
for (Function func : funcManager.getFunctions(true)) {
DecompileResults results = decompiler.decompileFunction(func, 60, monitor);
if (results.decompileCompleted()) {
writer.println("// Function: " + func.getName());
writer.println(results.getDecompiledFunction().getC());
}
}
writer.close();
}
}
Запуск декомпилятора выполняется из консоли без подъема графического сервера:
# Извлечение псевдокода из исполняемого файла target_binary
/opt/ghidra/support/analyzeHeadless /tmp/ghidra_workspace ProjectTemp \
-import ./target_binary \
-postScript DecompileExport.java ./raw_decompiled.c \
-deleteProject
Трансформация псевдокода в безопасный Rust: связка Python и компилятора
Связующий скрипт на Python передает фрагмент псевдокода в языковую модель, запрашивает переписывание на Rust и немедленно проверяет результат через rustc:
import os
import subprocess
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
def reconstruct_to_rust(raw_c_code: str) -> str:
system_prompt = (
"Ты — эксперт по системному реверс-инжинирингу. Преобразуй псевдокод Ghidra "
"в идиоматичный, типобезопасный Rust. Восстанови осмысленные имена и структуры данных. "
"Верни только чистый блок кода без пояснений."
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Псевдокод:\n```c\n{raw_c_code}\n```"}
],
temperature=0.1
)
text = response.choices[0].message.content.strip()
return text.removeprefix("```rust").removeprefix("```").removesuffix("```").strip()
def verify_compilation(rust_code: str, output_path: str = "./reconstructed.rs") -> bool:
with open(output_path, "w", encoding="utf-8") as f:
f.write(rust_code)
proc = subprocess.run(["rustc", "--crate-type=lib", output_path], capture_output=True, text=True)
if proc.returncode == 0:
print("[УСПЕХ] Rust-код валиден и скомпилирован.")
return True
print(f"[ОШИБКА]: {proc.stderr}")
return False
if __name__ == "__main__":
with open("./raw_decompiled.c", "r", encoding="utf-8") as f:
pseudocode = f.read()
rust_code = reconstruct_to_rust(pseudocode)
verify_compilation(rust_code)
Компилятор здесь выступает строгим арбитром: ошибки заимствования ссылок или несовпадения типов локализуются мгновенно.
Методология чистой комнаты: как легально воссоздавать закрытый софт
Понимание логики бинарников открывает дорогу легальному воссозданию проприетарных систем по принципу «чистой комнаты» (clean-room design). Закон защищает конкретную форму выражения кода, но не функциональный протокол. Методология разделяет две группы:
- Группа анализа (Dirty Room): исследует закрытый бинарник, декомпилирует функции и составляет спецификацию протоколов и структур;
- Группа разработки (Clean Room): не видит закрытого кода и пишет независимую реализацию с нуля по составленному описанию.
В проектах вроде PhotoCraft и FilmCraft разработчики используют кодовую базу на Rust для воссоздания закрытых медиаредакторов без нарушения копирайта.
Подводные течения оптимизации: ограничения инлайнинга и архитектурный вердикт
При всех успехах метод имеет инженерные ограничения:
- Агрессивный инлайнинг и векторизация: код с флагами SIMD/AVX дробится на параллельные регистровые операции, восстановить смысл которых сложно даже передовым LLM;
- Протекторы и обфускаторы: коммерческие упаковщики (VMProtect, Denuvo) требуют предварительного ручного снятия виртуализации;
- Изоляция сред: декомпиляция недоверенных файлов должна проходить в одноразовых песочницах без доступа к сети.
Связка Ghidra и кодовых моделей лишает закрытое ПО статуса «вечного черного ящика»: старые библиотеки получают вторую жизнь в безопасном Rust, а команды безопасности — инструмент мгновенного разбора инцидентов.
