
Параллельный бинарный анализ в IDA Pro с AI-управляемым именованием функций, графом знаний Neo4j и движком эмуляции/хукинга/фаддзинга phantomrt для автоматизированного реверс-инжиниринга форматов PE, ELF и NSO.
Призрак сквозь бинарники.
Локальный AI-ассистент для реверс-инжиниринга: параллельный анализ IDA Pro, AI-именование функций, терминал, который не бесит, граф знаний Neo4j всего, что он когда-либо выяснил, и MCP-сервер, чтобы Claude мог напрямую искать и прослеживать связи в этом графе.
А теперь — с phantomrt — он не просто читает стены. Он проходит сквозь них: эмулирует, перехватывает и фаззит функции, которые он назвал, и записывает то, что на самом деле происходит, обратно в граф.
✓ 00 ✓ 01 ✓ 02 ✓ 03 ▸ 04 · 05 · 06 · 07 ✓ 08 ✓ 09 ✓ 10 ✓ 11 ✓ 12 ✓ 13 ▸ 14 · 15
14/16 shards │ 141,203 functions found ████████████████████████████░░░░ 89% ~4s remaining
## Что это такое
Автоанализ IDA Pro выполняется в одном потоке. Для 34 МБ DLL-файла il2cpp это *минуты*. spectrIDA разбивает бинарник на N фрагментов, запускает их параллельно через idalib, объединяет в один `.i64`, а затем позволяет точно настроенной 8B модели **называть каждую функцию** — всё из одного терминального интерфейса с киберпанк-темой и ровно таким количеством сарказма, какое нужно.
Это Глава 1, и она самодостаточна: чистая скорость, никакого ИИ не требуется, если не хотите.
Глава 2 превращает результат в нечто, переживающее сессию, — граф Neo4j, в котором клиент MCP (Claude, [pi](https://pi.dev) или любой другой, понимающий MCP) может реально *жить*, вместо того чтобы копировать вывод декомпилятора в окно чата по одной функции за раз:```
Binary ─▶ Parallel IDA Analysis ─▶ Demangle ─▶ AI Naming ─▶ Neo4j Graph ─▶ MCP Server ─▶ Claude
(N idalib shards) (free, real) (stripped (persists, (search/chain/
leftovers forever, rename, live)
only) across sessions)
Глава 3 (phantomrt) добавляет ту половину, которой у статических инструментов никогда не было: она запускает код. Эмулирует функцию без ОС (работает с бинарниками, которые даже нельзя запустить, например, Switch .nso), или подключается к живому процессу, или фазит его — и наносит вердикт (crashes / needs live state / clean) на тот же узел графа, что и имя.
Полный обзор, включая то, что ещё осталось в списке задач, находится в Главе 2 и Главе 3.
Это не Ghidra. Он делает одну раздражающую вещь (медленный анализ + именование) быстро, и им действительно весело пользоваться. 199 загрузок говорят сами за себя.
Никакого облака. Никакой телеметрии. Работает полностью на вашем компьютере.
| задача | время |
|---|---|
| Among Us DLL — однопоточная IDA | ~4 часов |
| Among Us DLL — spectrIDA (16 рабочих процессов) | 67 секунд |
| Бинарник с 153 649 функциями — полный проход именования | за ночь |
| Обзор бинарника (что он делает?) | ~30 секунд |
Оборудование, на котором проводились измерения: AMD Ryzen 7 5800X3D (8C/16T), 32 ГБ ОЗУ, RTX 4070 12 ГБ. Другое оборудование изменяет показатели параллельного анализа (больше ядер, больше шардов, быстрее); показатели именования в основном зависят от GPU. Цифры 4 часа/67 секунд для Among Us относятся ко времени до Главы 2 и не перепроверяются независимо в каждом релизе — запустите spectrida analyze сами, если хотите получить цифру для своей машины и своего бинарника; результаты варьируются в зависимости от плотности шардов и размера бинарника.
Цифры, фактически перепроверенные во время разработки Главы 2, на том же оборудовании:
Эта строка NSO является реальным эквивалентом старой претензии Among Us «4 часа → 67 секунд», замеренной заново в этом релизе на бинарнике Switch с 74 790 функциями, без участия ИИ в именовании (только демангинг — populate=False). Параллельная фаза (16 ядер, ~55 с) выполняет начальное шардированное обнаружение; фаза слияния (~143 с) по замыслу однопоточная — одна база данных IDA, один писатель — так что если вы во время этой части смотрите в Диспетчер задач и видите, что 15 ядер дремлют, это не отчёт об ошибке, это физика.
Получение честной цифры здесь было своей собственной маленькой историей ужасов. Первая версия поддержки NSO работала чисто, завершилась с кодом 0 и гордо вернула 727 функций для бинарника, в котором их примерно 75 000 — не авария, просто эффектно, уверенно неправильно, что почему-то ещё хуже. Оказалось, что у IDA нет родного загрузчика NSO, поэтому файл молча загрузился как обычный x86 ("metapc"), хотя Switch с самого запуска был на ARM64. Каждый шард запускал сканер прологов x86 на чистые инструкции AArch64 и называл «функцией» то, что случайно совпало. Исправление архитектуры само по себе ничего не исправило, потому что бинарник также всё ещё был сжат LZ4 в памяти — так что половина того, что сканировалось, была, мягко говоря, шумом. И даже когда он был правильно распакован и помечен как AArch64, каждый шард искал цели вызовов только внутри своего крошечного фрагмента бинарника, пропуская все вызовы, пересекающие границу шарда — а в бинарнике такого размера это большинство из них. Три ошибки, одна цифра, и ни одна из них не имела приличия бросить исключение. (Мы также попытались сократить фазу слияния, пропуская анализ стековых фреймов IDA — получили прекрасное ускорение и базу данных, где Hex-Rays вежливо отказался декомпилировать половину из неё. Это было быстро отменено. Вместо этого оставили гораздо меньший и гораздо более безопасный пропуск FLIRT-сигнатур, который даёт ~3% (незначительно) и ничего не ломает, что к этому моменту уже казалось чертой характера, которую стоит сохранить.)
О точности именования: это не достоверность уровня Ghidra, это модель на 8B параметров, угадывающая по псевдокоду. Общие хелперы/геттеры часто попадают в цель; глубоко игровая логика — скорее подбрасывание монетки. Переименовывайте всё, что она называет неправильно — именно для этого rename_function сохраняется напрямую в граф.
.i64. Количество рабочих процессов настраивается через флаг, конфиг или переменную окружения..so/Linux) встроены; добавление нового — один файл, без изменений в ядре. spectrida formats показывает зарегистрированные форматы. См. Добавление нового формата бинарника.N. Наблюдайте, как она думает. Имя появляется.B для именования всех функций sub_* в списке. Отойдите. Вернитесь.O или выполните spectrida overview file.i64. Модель читает 120 выборочных имён функций и рассказывает, что делает бинарник, какие у него подсистемы и что-либо, связанное с безопасностью. Правильно распознала среду выполнения IL2CPP с 153 тыс. функций за 30 секунд.C показывает вызывающие и вызываемые функции. Модель использует их как контекст при именовании — функция, вызываемая , именуется лучше, чем изолированная.pip install spectrida
Требования: **IDA Pro 9.x** with idalib · **Python 3.10+** · **Ollama**```bash
# install Ollama (Windows)
winget install Ollama.Ollama
# pull the model (8.7 GB — go get coffee)
ollama pull hf.co/gdfhhjk/spectrida-re-gguf:latest
# first run — detects your IDA install and sets everything up
spectrida onboard
# or just try the demo right now
spectrida --demo
spectrida analyze GameAssembly.dll spectrida analyze GameAssembly.dll --workers 8 # custom worker count
spectrida open file.i64
spectrida overview file.i64 spectrida overview file.i64 --addr 0x10001000 --addr 0x10353fd0 # include specific functions
spectrida export file.i64 -f idc # IDA script — apply names to any install spectrida export file.i64 -f json # full dump with addresses + sizes spectrida export file.i64 -f csv # spreadsheet spectrida export file.i64 -f symbols # addr name pairs spectrida export file.i64 --named-only # skip sub_* functions
spectrida serve
spectrida onboard
---
## Клавиши TUI
| Key | Action |
|-----|--------|
| `N` | Назвать выбранную функцию — AI транслирует результат в реальном времени |
| `R` | Переименовать — предварительно заполнено предложением AI |
| `D` | Переключить декомпилированный псевдокод (Hex-Rays) |
| `C` | Цепочка вызовов — вызывающие и вызываемые функции |
| `B` | Пакетное именование всех функций `sub_*` в текущем списке |
| `O` | Обзор — AI сводка всего бинарного файла |
| `/` | Нечёткий поиск |
| `?` | Справка |
| `Q` | Выход |
---
## Программный API
TUI не требуется — управляйте spectrIDA из скриптов, Claude Code, блокнотов и т. д.:```python
import asyncio
from spectrida.api import open_i64
async def main():
async with open_i64("GameAssembly.i64") as db:
# list all 153k functions
funcs = await db.list_functions()
# name one function — returns name + reasoning + confidence
result = await db.name_function(0x10001000)
print(result["new_name"]) # init_atexit_handler
print(result["reasoning"]) # allocates array of 3 fn ptrs, calls _atexit...
# batch name everything (with live progress)
async def on_progress(done, total, r):
print(f" {done}/{total} {r['old_name']} -> {r['new_name']}")
await db.batch_name(limit=500, rename=True, progress_cb=on_progress)
# ask what the binary does
overview = await db.overview()
print(overview)
# export to IDA script
await db.export("names.idc", fmt="idc", named_only=True)
asyncio.run(main())
hf.co/gdfhhjk/spectrida-re-gguf — Qwen3-8B,
дообученная для реверс-инжиниринга.
Обучена на:
jtsylve/ida-mcp — headless IDA с idalibМетод обучения: нейронно-таргетированный SFT + GRPO. Настраиваются только нейроны, релевантные для RE — базовые знания Qwen3 остаются нетронутыми, вы просто добавляете очень специфический навык поверх.
Работает локально через Ollama. GGUF — работает на CPU, GPU или на обоих.
Вы занимаетесь реверсом. У вас есть бинарник со 150 000 функций. Возможно, 2000 из них имеют имена из
метаданных. Остальные 148 000 — это sub_XXXXXXXX. Вы хотите найти сетевой код.
Вы не можете использовать grep, потому что ни у чего ещё нет имени.
Человек-реверсер может назвать ~50–100 функций в час, если работает быстро. При таком темпе 150 тыс. функций = 3 года.
spectrIDA называет их за ночь. Не идеально — возможно, 70% точности на типовых функциях,
гораздо выше на паттернах, которые модель узнаёт. Но теперь вместо 148 тыс. функций sub_ у вас есть
network_send_packet, serialize_player_state, validate_checksum — и вы знаете, где искать.
Она не заменяет опытного реверс-инженера. Она выполняет скучные 80%, чтобы вы могли сосредоточиться на интересных 20%. Это слой ориентации.
Реальные сценарии использования:
sub_140001234 20 минут, думая: должен же быть способ лучше~/.spectrida/config.toml:```toml
[ida]
idalib = "C:/Program Files/IDA Professional 9.1"
output_dir = "~/.spectrida/output"
[ollama] base_url = "http://localhost:11434" model = "spectrida-re" # any ollama model name works
[pipeline] workers = 16
Переопределения переменных среды: `SPECTRIDA_IDALIB` · `SPECTRIDA_MODEL` · `SPECTRIDA_WORKERS` · `SPECTRIDA_OLLAMA_URL`
---
## Добавление нового бинарного формата
Поддержка форматов — это система плагинов, а не куча веток if/elif — PE, NSO и ELF — это просто файлы в каталоге `spectrida/analysis/formats/`, которые обнаруживаются автоматически. Выполните `spectrida formats`, чтобы увидеть, что сейчас зарегистрировано.```bash
$ spectrida formats
ELF spectrida.analysis.formats.elf.ELFHandler
NSO spectrida.analysis.formats.nso.NSOHandler
PE spectrida.analysis.formats.pe.PEHandler
generic spectrida.analysis.formats.generic.GenericHandler
Задача обработчика формата узка — посмотреть на файл и определить, принадлежит ли он вам, а затем описать его
структуру кода. Всё остальное (стратегия шардирования, сканирование GPU-пролога, объединение шардов в один
.i64) обрабатывается один раз, обобщённо, за пределами пакета форматов. NSO — это полный пример: его
LZ4-декомпрессия и логика mem2base/add_segm из idaapi уже существовали в nso_loader.py (исправление
ошибок с неправильной архитектурой/всё ещё сжатым/локально-слепым адресом входа из истории Главы 2) —
formats/nso.py — это тонкий адаптер, который предоставляет существующий, проверенный модуль через
контракт FormatHandler, а не переписывание.
Чтобы добавить формат, положите один новый файл в spectrida/analysis/formats/ и больше ничего. Никаких
изменений в parallel_analyze.py, shard_worker.py или реестре — он подхватывается сканированием
директории на наличие любого модуля, предоставляющего экземпляр HANDLER.```python
from spectrida.analysis.formats.base import FormatHandler, PreparedImage, Section
class MyFormatHandler(FormatHandler): name = "MYFMT"
@staticmethod
def sniff(header: bytes, path: str) -> bool:
return header[:4] == b"MYF0" # however you recognize the format
def prepare(self, path: str, workdir: str) -> PreparedImage:
# Format idalib already loads natively (ELF, PE, Mach-O)? Just parse
# the section/segment table — return the original path unchanged.
return PreparedImage(
binary_path=path,
image_base=0,
sections=[Section(name=".text", va=0x1000, raw_off=0x400,
raw_size=0x2000, vsize=0x2000, is_code=True)],
arch=None, # set "x86_64"/"arm64" only if IDA can't detect it itself
)
# Only needed if idalib has NO native loader for this format (NSO is the
# example): do any manual idaapi/ida_segment setup here, called right
# after idapro.open_database() succeeds, before analysis starts.
# def post_open(self) -> None: ...
HANDLER = MyFormatHandler()
Вот весь контракт:
| Метод | Обязателен? | Что делает |
|---|---|---|
| `sniff(header, path)` | да | Проверка магических байт/расширения — определяет, принадлежит ли файл этому обработчику? |
| `prepare(path, workdir)` | да | Возвращает `PreparedImage`: файл, который idalib должна открыть, и его таблицу секций |
| `post_open()` | нет (по умолчанию ничего не делает) | Ручная настройка сегментов для форматов без нативного загрузчика IDA (см. `nso.py`) |
| `make_shard_binary(image, dst, va_start, va_end)` | нет (по умолчанию работает) | Переопределять только если обнуление байтов секций за пределами шарда неверно для вашего формата (см. `nso.py` — никогда не обнуляйте сжатый файл) |
| `code_range(image)` | нет (по умолчанию работает) | Переопределять только если «мин/макс секций `is_code`» не является правильным ответом |
| `read_bytes(image, va_start, va_end)` | нет (по умолчанию работает) | Переопределять, если `prepare()` уже хранит нужные байты в памяти (NSO), а не на диске |
| `global_entry_points(image, text_start, text_end)` | нет (по умолчанию: None) | Переопределять только если локальное сканирование по шарду может пропустить реальные точки входа — NSO нужно это, потому что конечные функции AArch64 обнаруживаются только через цели BL, видимые в других частях бинарника, а не через локальное сканирование пролога |
Посмотрите на `formats/pe.py` как на самый простой возможный обработчик (чистый разбор заголовков, без переопределений) и
`formats/nso.py` как на полный случай (обёртка распаковки + ручная настройка сегментов + все переопределения).
Сторонние пакеты также могут зарегистрировать обработчик, вообще не касаясь исходного кода spectrIDA, через
группу точек входа `spectrida.formats`:```toml
# in a separate package's pyproject.toml
[project.entry-points."spectrida.formats"]
myformat = "spectrida_myformat_plugin:HANDLER"
Тестовое покрытие для системы форматов находится в tests/test_formats.py — чистый Python, не требует IDA/idalib, поэтому выполняется в CI.
Глава 1 была более быстрой и забавной IDA. Глава 2 — spectrIDA как напарник: постоянный, доступный для запросов граф знаний каждой функции, которую он когда-либо именовал, и MCP-сервер, чтобы Claude (или любой MCP-клиент — pi тоже подходит) мог искать и анализировать его напрямую, вместо того чтобы вы копировали вывод дизассемблера в окно чата.```bash spectrida install mcp
Вот и всё. Он автоматически регистрирует сервер с Claude Code и pi (подтягивая `mcp` + `neo4j`, если простая `pip install spectrida` их пропустила), записывает их конфиг и сообщает, какой рестарт вы ему должны.
**Что на самом деле получает Claude, когда Neo4j запущен (секция `[graph]` конфига `spectrida`, или просто укажите на локальный экземпляр):**
- `search_functions` / `get_function` / `get_callees` / `get_callers` / `trace_chain` — быстрые кэшированные чтения графа. `get_function` возвращает псевдокод **и** дизассемблер (точные границы инструкций и операнды — то, что псевдокод дать не может, а это важно в момент, когда вы переходите от «что это делает» к «где именно это нужно пропатчить») плюс встроенные вызывающие/вызываемые, так что Claude решает, углубляться ли по цепочке, глядя, является ли вызываемая функция всё ещё `sub_*` прямо в ответе — никакого лишнего обращения только чтобы узнать, что больше нечего смотреть.
- `get_full_pseudocode` / `rename_function` — «живые», авторитетные чтения/записи непосредственно в `.i64`, когда кэшированного фрагмента недостаточно или имя наконец выяснено.
- `analyze_binary` — передайте ему бинарник, который он никогда не видел (PE или NSO, в любом случае параллельно сегментированный), и он запускает весь конвейер: анализ → демангинг (Itanium *и* MSVC) → именование с помощью ИИ действительно обрезанных остатков → внесение всего в граф — как одна фоновая задача, которую вы опрашиваете, поэтому многочасовая работа никогда не блокирует разговор.
- `doctor` / `start_all` — проверьте или запустите llama-server + Neo4j, не выходя из чата. Если сам llama-server нигде не установлен, `start_all` сначала загружает его через winget (Windows) или brew (macOS) — никакой отдельной загрузки/установки llama.cpp не требуется.
Это не магия — функция, которая всё ещё называется `sub_140001234`, потому что никто на неё ещё не посмотрел, так и остаётся `sub_140001234`. Но граф запоминает всё, что модель *уже* выяснила, навсегда, между сеансами, и Claude может перемещаться по нему как коллега, который уже прочитал кодовую базу, вместо того чтобы пялиться на одну функцию за раз.
**Ещё в разработке:**
- **Глубокое контекстное именование** — проследить деревья вызовов на N уровней вглубь, передать полную цепочку модели. Функция на расстоянии 3 переходов от `encrypt_block` должна знать, что она на пути криптографии.
- **Деобфускация** — обнаружение шаблонов TigressVM и трассировка обработчиков
- **Реальное патчирование** — теперь дизассемблер находится в графе, поэтому агент *может* спланировать патч на уровне байтов; превращение «вот точная инструкция, которую нужно изменить» в «а вот и запись» — следующий шаг.
---
## Глава 3 — призрак проходит сквозь стены
Главы 1 и 2 читают бинарник и запоминают его. Но чтение функции говорит вам, чем она *является*, но никогда — что она *делает*, когда вы нажимаете на спусковой крючок. Глава 3 — [**phantomrt**](https://pypi.org/project/phantomrt/) — нажимает на спусковой крючок.```bash
pip install "spectrida[atlas]"
Она берёт уже именованную функцию spectrIDA и делает с ней одно из трёх жутких действий:
.nso от Switch или .so от Android.Затем он наносит вердикт на тот же узел Neo4j как свойства dyn_*, так что агент, обходящий граф, видит имя и поведение в одном месте. Шесть новых инструментов MCP — emulate_function, hunt_crashes, live_trace, dynamic_overview, risk_functions, learn_vm — все поддерживают единый граф. Уголок рассуждений — это любой LLM за рулём; phantomrt просто гарантирует, что у него есть реальные факты времени выполнения для рассуждений, а не ощущения.```
spectrIDA (names it) ─▶ phantomrt (runs it) ─▶ graph (dyn_status / crash / live args) ─▶ the agent reasons
**И, в духе истории ужасов NSO выше:** первая настоящая охота на баги честно не нашла *ничего* — и причина была интереснее, чем если бы был крэш. Направив на действительно старую, действительно уязвимую FreeType, слепой фаззинг получил ноль. Затем фаззинг, управляемый покрытием, торжественно сообщил о покрытии **64 000 рёбер** — что было ложью, потому что бинарник был PIE, и ASLR тихо перерандомизировал адреса при каждом запуске, так что «новое покрытие» в основном состояло из того, как загрузчик тасовал колоду. Отключаем ASLR — честное число падает до *реальных* 727 рёбер и останавливается на этом — потому что каждое начальное значение было шрифтом TrueType, и фаззер оказался заперт в одном парсере и не мог структурно мутировать, чтобы попасть в код CFF/Type1/BDF, где на самом деле живут баги. Исправление было не в более умном мутаторе, а в лучших начальных значениях: агент загрузил настоящие образцы OpenType/Type1/BDF, покрытие подскочило с 727 до 4 013 ещё до первой итерации фаззинга, и он начал исследовать правильно. Но крэша в рамках бюджета так и не произошло — это честное состояние 3-минутного прогона на одном ядре по сравнению с задачей, которая обычно занимает часы. Механизм реален. Он поймал своё *собственное* фальшивое число вместо того, чтобы сообщить его. В этом вся суть.
**Честный отказ призрака:** phantomrt — это `0.1.0`. Альфа. Это надёжный динамический слой, а не магический оракул багов — он мгновенно находит внедрённые баги в игрушечной цели, а на реальных целях он честно сообщает, когда застрял (`needs_state`), когда крэш — лишь *кандидат* (проверьте, действительно ли указатель управляется вводом), и когда ему просто нужно больше времени и лучшие начальные значения. Бинарники Switch не запускаются вживую — Frida нужно что-то, что можно запустить — так что там эмуляция — единственный путь, и он будет часто говорить `needs_state`. Это честно, а не сломано.
Это специально тяжёлое дополнение (`torch`, `unicorn`, `frida`) — базовая установка `pip install spectrida` не тянет ничего из этого. Исходный код находится в [`phantomrt/`](https://github.com/ggfuchsi-oss/spectrida-reverse_engineering_stack/blob/HEAD/phantomrt/); его собственная README в стиле призрака находится [прямо здесь](https://github.com/ggfuchsi-oss/spectrida-reverse_engineering_stack/blob/HEAD/phantomrt/README.md).
*Статический призрак называет ваши функции. Этот заставляет их признаваться.* 👻
---
## Лицензия
MIT. Делайте с ним что хотите. Если работает — круто.
Если нет — вините квантование GGUF.
Создано с помощью злобы, кофе и RTX 4070.
У модели 199 загрузок и нулевой маркетинг. Каждая добавляет 0.01% к скорости разработки.
(Это неправда. Но близко.) 👻
| бинарник | функции | задача | время / результат |
|---|
| test_small.dll (PE) | 189 | параллельный анализ, 4 рабочих процесса, CLI | 6.4с |
| test_small.dll (PE) | 164 | полный конвейер MCP (анализ + демангинг + запись графа) | 9.8с |
| main.nso — Mario Odyssey (NSO), 16 рабочих процессов | 28 038 начальных функций | фаза параллельного шардированного сканирования | 54.5с |
| main.nso — Mario Odyssey (NSO), 16 рабочих процессов | 74 790 всего функций | + фаза слияния/полного анализа | 143.1с |
| main.nso — Mario Odyssey (NSO), 16 рабочих процессов | 74 790 всего функций | общее астрономическое время | 197.6с |
| main.nso — Mario Odyssey (NSO) | 74 790 | разрешено только через демангинг (Itanium ABI, бесплатно, без ИИ) | 67 300 (90.0%) |
Player$$TakeDamageD переключает псевдокод Hex-Rays..idc или файл символов. .idc применяет все сгенерированные ИИ имена обратно в любую установку IDA одним кликом.from spectrida.api import open_i64. Управляйте всем из скриптов, блокнотов или Claude Code, не касаясь TUI.spectrida install mcp подключает его напрямую к Claude Code и/или pi, без ручного редактирования JSON. Затем Claude может искать/читать/выстраивать цепочки через граф функций на базе Neo4j (имя, псевдокод, дизассемблирование, вызывающие/вызываемые) и запускать свежий анализ нового бинарника самостоятельно — analyze_binary выполняет весь конвейер (параллельный анализ → демангинг → именование ИИ → граф) одним вызовом инструмента, в виде фоновой задачи, которую он опрашивает. Работает на PE и NSO. См. Главу 2 ниже.spectrida --demo) — попробуйте всё без никакой настройки. Не нужны ни IDA, ни Ollama.