Parallel 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 MB的il2cpp DLL,这需要*几分钟*。spectrIDA将二进制文件分割成N个分片,通过idalib并行运行,合并成一个`.i64`文件,然后让一个微调的8B模型**为每个函数命名**——所有这些都来自一个终端用户界面,具有赛博朋克主题和恰到好处的讽刺感。
这就是第1章,它独立存在:纯粹的速度,如果你不想要,就不需要AI。
第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)标记到与函数名称相同的图节点上。
它不是Ghidra。它能快速完成一件烦人的事(慢速分析+命名),而且使用起来确实很有趣。199次下载说明一切。
无云端。无遥测。完全在你的机器上运行。
| 任务 | 时间 |
|---|---|
| Among Us DLL — 单线程IDA | ~4 hours |
| Among Us DLL — spectrIDA(16个工作线程) | 67 seconds |
| 153,649函数二进制文件 — 完整命名过程 | overnight |
| 二进制文件概览(这个程序是干什么的?) | ~30 seconds |
测量所用硬件: AMD Ryzen 7 5800X3D(8核/16线程),32 GB RAM,RTX 4070 12 GB。不同的硬件会影响并行分析数据(更多核心、更多分片,更快);命名数据主要受GPU限制。Among Us的4小时/67秒数据是在第2章之前测得的,并非每次发布都独立重新验证——如果你想针对自己的机器和二进制文件获取数据,请自行运行spectrida analyze,结果会因分片密度和二进制文件大小而异。
实际上在第2章开发期间重新验证的数据,相同硬件:
那一行NSO数据实际上相当于之前Among Us的“4小时→67秒”说法,在本版本中基于一个74,790函数的Switch二进制文件重新测量,未涉及AI命名(仅反混淆——populate=False)。并行阶段(16核,~55秒)进行初始分片发现;合并阶段(~143秒)设计为单线程——一个IDA数据库,一个写入器——所以如果你在该阶段查看任务管理器,看到15个核心在休眠,那不是bug报告,那是物理限制。
获取一个真实的数字本身就是一出小恐怖故事。第一个版本的NSO支持运行正常,退出代码0,但骄傲地返回了727个函数,而该二进制文件大约有75,000个函数——不是崩溃,只是惊人地、自信地错误,这某种程度上更糟糕。结果发现IDA没有原生NSO加载器,因此文件静默地作为纯x86("metapc")加载,尽管Switch自发布以来就是ARM64架构。每个分片都对着纯AArch64指令运行x86序言扫描器,并将意外匹配到的任何内容称为"函数"。单独修复架构毫无作用,因为该二进制文件在内存中仍然是LZ4压缩的——所以扫描的内容中有一半,客气地说,是噪声。即使正确解压并标记为AArch64,每个分片也只在其小小的二进制切片内寻找调用目标,遗漏了所有跨越分片边界的调用——在这种大小的二进制文件中,大部分调用都是跨边界的。三个bug,一个数字,而且没有一个有抛出异常的体面。(我们还尝试通过跳过IDA的栈帧分析来缩减合并阶段——获得了漂亮的加速,但数据库导致Hex-Rays礼貌地拒绝反编译一半内容。那很快就被回退了。保留了更小、更安全的FLIRT签名跳过,这带来了约3%的微不足道的改进,且没有破坏任何东西,到这一步感觉这已经是一个值得保留的性格特征。)
关于命名准确性: 它并非Ghidra级别的真实答案,而是一个8B模型根据伪代码进行猜测。通用的辅助函数/获取器通常效果不错;深度游戏特定的逻辑则更像抛硬币。对任何错误结果进行重命名——这就是为什么rename_function会直接持久化回图中。
.i64。可通过标志、配置或环境变量配置工作线程数。.so/Linux)支持;添加新格式只需一个文件,无需更改核心。spectrida formats列出已注册的格式。参见添加新的二进制格式。N。看它思考。名称出现。B对列表中所有sub_*函数进行命名。离开。回来。O或运行spectrida overview file.i64。模型读取120个采样函数名称,告诉你该二进制文件的功能、子系统以及与安全相关的内容。30秒内正确识别了一个153k函数的IL2CPP运行时。C显示调用者和被调用者。模型在命名时使用这些作为上下文——被Player$$TakeDamage调用的函数比孤立命名的效果更好。D切换Hex-Rays伪代码显示。.idc脚本或符号文件。脚本可一键将AI生成的名称应用回任何IDA安装中。pip install spectrida
要求:**IDA Pro 9.x** 配合 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 快捷键
| 快捷键 | 操作 |
|--------|------|
| `N` | 函数命名选取 — AI 实时流式输出结果 |
| `R` | 重命名 — 预填充 AI 建议的名称 |
| `D` | 切换反编译伪代码(Hex-Rays) |
| `C` | 调用链 — 调用者与被调用者 |
| `B` | 批量命名当前列表中的所有 `sub_*` 函数 |
| `O` | 概览 — AI 对整个二进制文件的摘要 |
| `/` | 模糊搜索 |
| `?` | 帮助 |
| `Q` | 退出 |
---
## 编程 API
无需 TUI — 可从脚本、Claude Code、Jupyter 笔记本等任意方式驱动 spectrIDA:```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 的工具调用轨迹——使用 idalib 的无头 IDA训练方法: 神经元定向 SFT + GRPO。仅调整与逆向工程相关的神经元——基础 Qwen3 知识保持不变,只是在其上添加了非常特定的技能。
本地通过 Ollama 运行。GGUF 格式——可在 CPU、GPU 或两者上运行。
你正在进行逆向工程。你有一个包含 150,000 个函数的二进制文件。也许有 2,000 个函数从元数据中获得了名称。其余 148,000 个是 sub_XXXXXXXX。你想找到网络代码。你无法通过 grep 找到它,因为没有任何东西有名称。
一个人类逆向工程师如果速度够快,每小时可以命名大约 50-100 个函数。按照这个速度,150k 个函数需要 3 年。
spectrIDA 一夜之间就能为它们命名。并非完美——普通函数大约有 70% 的准确率,对于模型识别的模式则高得多。但现在,你不再面对 148k 个 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
Env var overrides: `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
一个格式处理器(format handler)的职责很狭窄——查看一个文件并判断它是否属于你所能处理的,然后描述其代码布局。其他所有工作(分片策略、GPU 序言扫描、将分片合并为一个 .i64 文件)都在格式包之外统一处理一次。NSO 是一个完整的例子:其 LZ4 解压缩 + idaapi mem2base/add_segm 逻辑已经存在于 nso_loader.py 中(这是对第二章历史中错误架构/仍压缩/本地盲入口点错误的修复)—— 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中运行。
第一章是一个更快、更有趣的IDA。第二章则是作为队友的spectrIDA:一个持久的、可查询的知识图谱,包含它命名过的每一个函数;以及一个MCP服务器,因此Claude(或任何MCP客户端 — pi 也可以工作)可以直接搜索并进行推理,而不是你手动复制粘贴反编译器的输出到聊天窗口。```bash spectrida install mcp
就是这样。它会自动将服务器注册到 Claude Code 和 pi(如果裸 `pip install spectrida` 跳过了它们,它就会拉取 `mcp` + `neo4j`),写入它们的配置,并告诉你需要重启哪个部分。
**一旦 Neo4j 运行起来(`spectrida` 配置的 `[graph]` 部分,或者直接指向一个本地实例),Claude 实际得到的是:**
- `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)→ AI 命名真正被剥离的剩余部分 → 全部推入图 —— 作为一个你可以轮询的后台任务,这样一次耗时数分钟的运行就不会阻塞对话。
- `doctor` / `start_all` — 在不离开聊天的情况下检查或启动 llama-server + Neo4j。如果系统中根本未安装 llama-server,`start_all` 会首先通过 winget(Windows)或 brew(macOS)获取它 —— 无需单独下载/设置 llama.cpp。
这不是魔法——一个因为没人看过而仍然叫 `sub_140001234` 的函数仍然是 `sub_140001234`。但图会永久记住模型 *已经* 搞清楚的一切,跨会话持久化,Claude 可以像一位已经阅读过代码库的同事一样浏览它,而不是一次盯着一个函数看。
**仍在开发中:**
- **深度上下文命名** — 向下跟踪 N 层调用树,将完整链提供给模型。一个距离 `encrypt_block` 三层调用的函数应该知道它处于加密路径中。
- **反混淆** — TigressVM 模式检测和处理器跟踪
- **实际修补** — 现在反汇编已经在图中,因此代理 *可以* 规划字节级补丁;将“这是要更改的确切指令”变成“这是要写入的内容”是下一步。
---
## 第 3 章 —— 幽灵穿墙而过
第 1 章和第 2 章读取二进制文件并记住它。但是读取一个函数只告诉你它 *是什么*,绝不会告诉你当你扣动扳机时它 *做什么*。第 3 章 —— [**phantomrt**](https://pypi.org/project/phantomrt/) —— 扣动扳机。```bash
pip install "spectrida[atlas]"
它接收一个已命名的函数spectrIDA,并对其施以三种恐怖操作之一:
.nso或Android的.so)的方式。然后它将判定结果以dyn_*属性的形式标记到同一个Neo4j节点上,这样遍历图谱的代理就能在同一处看到函数名称和行为。六个新的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,盲模糊(blind fuzzing)一无所获。基于覆盖率引导的模糊测试随后报告了辉煌的 **64,000条边** 被覆盖 —— 这是一个谎言,因为二进制文件是PIE,而ASLR每次运行都在悄悄地重新随机化地址,所以“新覆盖”大部分只是加载程序在洗牌。禁用ASLR后,诚实的数字降到了 *真正的* 727条边,并且在那里趋于平稳 —— 因为每个种子都是TrueType字体,所以模糊器被困在了一个解析器内,无法通过结构变异进入实际存在漏洞的CFF/Type1/BDF代码。解决方法不是更智能的变异器,而是更好的种子:代理抓取了真实的OpenType/Type1/BDF样本,在单次模糊迭代之前,覆盖率就从727跳到了4,013,并且正确进行了探索。在预算内仍然没有崩溃 —— 这是在单个核心上运行3分钟的任务与通常需要数小时的任务之间的真实状态。机制是真实的。它捕捉到了自己的*虚假数字*而没有报告它。这才是重点。
**诚实的幽灵声明:** phantomrt 是 `0.1.0` 版本。Alpha 阶段。它是一个可靠的动态层,而不是一个魔法漏洞预言器 —— 它立即在玩具目标上找到了植入的漏洞,而在真实目标上,它诚实地告诉你它何时卡住了(`needs_state`),何时一个崩溃只是*候选*(去验证指针是否实际受输入控制),以及何时它只需要更多时间和更好的种子。Switch 二进制文件无法实时运行 —— Frida 需要一些可以启动的东西 —— 所以在这里,模拟是唯一的途径,并且它会大量输出 `needs_state`。这是诚实的,而不是坏的。
它是故意增加了重量级依赖(`torch`、`unicorn`、`frida`)—— 基础的 `pip install spectrida` 并不拉取这些。源代码位于 [`phantomrt/`](https://github.com/ggfuchsi-oss/spectrida-reverse_engineering_stack/blob/master/phantomrt);它自己的幽灵风格 README 就在[那里](https://github.com/ggfuchsi-oss/spectrida-reverse_engineering_stack/blob/master/phantomrt/README.md)。
*静态幽灵命名了你的函数。这个则让它们招供。* 👻
---
## 许可证
MIT。你想怎么用就怎么用。如果它好用,那不错。
如果不好用,那怪 GGUF 量化吧。
用怨气、咖啡和一块 RTX 4070 构建而成。
该模型在零营销的情况下获得了 199 次下载。每次下载为开发速度增加 0.01%。
(这不是真的。但差不多。)👻
| 二进制文件 | 函数数 | 任务 | 时间 / 结果 |
|---|
| test_small.dll (PE) | 189 | 并行分析,4个工作线程,CLI | 6.4s |
| test_small.dll (PE) | 164 | 完整MCP流水线(分析 + 反混淆 + 图写入) | 9.8s |
| main.nso — Mario Odyssey (NSO), 16 workers | 28,038 种子函数 | 并行分片扫描阶段 | 54.5s |
| main.nso — Mario Odyssey (NSO), 16 workers | 74,790 总函数数 | + 合并/完整分析阶段 | 143.1s |
| main.nso — Mario Odyssey (NSO), 16 workers | 74,790 总函数数 | 端到端实际时间 | 197.6s |
| main.nso — Mario Odyssey (NSO) | 74,790 | 仅通过反混淆解析(Itanium ABI,免费,无AI) | 67,300 (90.0%) |
.idcfrom spectrida.api import open_i64。通过脚本、笔记本或Claude Code驱动所有功能,无需接触TUI。spectrida install mcp直接连接到Claude Code和/或pi,无需手动编辑JSON。然后Claude可以搜索/读取/遍历基于Neo4j的函数图(名称、伪代码、反汇编、调用者/被调用者),并且可以独立启动对新二进制文件的全新分析——analyze_binary通过一次工具调用运行整个流水线(并行分析→反混淆→AI命名→图),作为后台任务由它轮询。支持PE和NSO。见下文第2章。spectrida --demo)— 在零配置下尝试所有功能。无需IDA,无需Ollama。