IDA Domain MCP: no GUI, no binary preloading, multi IDB analysis
要让 AI Agent 有逆向分析能力,就得给它 IDA tools。
背景
ida-pro-mcp 是目前比较常用的 ida MCP 服务器工具,它可以用GUI 界面、headless 模式两种方式启动。主要原理为,
- 通过 Plugin 提供 JSON-RPC 接口,调用 IDA 的各种功能;
- 通过 FastMCP 接收 MCP 客户端(AI Agent 或其它)的请求,调用对应的 Plugin 接口来完成任务。
但是无论在 GUI 还是 headless 模式下,MCP server 的启动都需要人工预先加载好一个二进制文件,才能进行后续的分析工作。当出现如下场景的时候,使用 ida-pro-mcp 就会比较麻烦:
- AI Agent 需要根据 thinking 的结果,动态选择需要加载的二进制文件进行分析的时候;
- 需要对多个二进制文件进行分析,甚至需要同时进行关联分析的时候。
针对上述场景,我们开发了一个新的 ida MCP 工具 ida-domain-mcp,它可以实现:
- 无需 GUI 界面,完全 headless 运行;
- 无需人工预加载二进制文件,AI Agent 可以动态指定需要分析的二进制文件路径;
- 可以同时对多个 IDB 文件进行分析,方便 AI Agent 使用进行跨 IDB 关联分析。
为什么 ida-pro-mcp 的实现必须要预加载二进制文件?
IDA 是一个静态 / 交互式反汇编器/反编译器。其基本工作流程是:加载一个二进制,将其内容映射 / 解析 / 建立交叉引用 / 构造函数 / 数据类型 / 符号表 / 代码&数据区。也就是说,IDA 的各种分析功能,都是基于当前已经加载的二进制文件(或 IDB)来进行的。
如图1所示,所有基于 IDB 分析的 core API,都是在加载了二进制文件之后,与对应的 IDB 强绑定的。而需要调用这些 core API 的 IDA 插件,比如 ida-pro-mcp plugin,因此在加载二进制文件之后才能正常工作。
flowchart LR
subgraph s1["IDA pro 启动"]
n1["加载 Binary / IDB, 建立 Database Context"]
n2["IDA core API(反汇编/反编译/符号/类型/xref 等)关联到具体 IDB"]
n1 --> n2
end
subgraph s2["IDA 插件注入"]
n3["ida-pro-mcp plugin 调用 IDA core API"]
n4["MCP server"]
n3 <--JSON-RPC--> n4
end
n2 --> n3
n4 <--FastMCP--> n5["MCP client(AI Agent、VS code插件等)"]
为了让 MCP tools 本身具有管理 loader / database 的能力,不能仅从插件入手,还需要对 IDA 的启动流程进行定制化开发。我们希望 MCP tools 与 IDA 的关系应该是如图2所示:
flowchart LR
%% =====================
%% 顶层结构
%% =====================
Client["MCP Client"]
Server["MCP Server"]
Worker1["Worker Process project=foo"]
Worker2["Worker Process project=bar"]
WorkerN["Worker Process project=xxx"]
%% Worker 内部 IDA
subgraph W1_IDA["IDA headless (idat/idat64)"]
Binary1["根据 foo_path 加载 Binary 文件,建立 foo_idb"]
CoreAPI1["IDA core API 关联到 foo_idb"]
end
subgraph W2_IDA["IDA headless (idat/idat64)"]
Binary2["根据 bar_path 加载 Binary 文件,建立 bar_idb"]
CoreAPI2["IDA core API 关联到 bar_idb"]
end
subgraph WN_IDA["IDA headless (idat/idat64)"]
BinaryN["根据 xxx_path 加载 Binary 文件,建立 xxx_idb"]
CoreAPIN["IDA core API 关联到 xxx_idb"]
end
%% =====================
%% 关系与调用
%% =====================
%% 加载数据库
Client -- "load_database(project=foo)" --> Server
Client -- "load_database(project=bar)" --> Server
Client -- "load_database(project=xxx)" --> Server
Server -->|forks on demand| Worker1
Server -->|forks on demand| Worker2
Server -->|forks on demand| WorkerN
Worker1 --> Binary1
Binary1 --> CoreAPI1
Worker2 --> Binary2
Binary2 --> CoreAPI2
WorkerN --> BinaryN
BinaryN --> CoreAPIN
%% 具体分析
Client -- "list_functions(project=foo)" --> Server
Server -- "find the worker" --> Worker1
Worker1 -- "调用 IDA core API" --> CoreAPI1
%% 给节点或连线定义样式
%% classDef highlight fill:#f9f,stroke:#333,stroke-width:1px;
%% class CoreAPI1,CoreAPI2,CoreAPIN highlight;
%% 连线样式(注意:索引从 0 开始,按连线在上面出现的顺序计数)
%% 12: Client -- "list_functions(project=foo)" --> Server
%% 13: Server -- "find the worker" --> Worker1
%% 14: Worker1 -- "调用 IDA core API" --> CoreAPI1
linkStyle 12,13,14 stroke:#2ca02c,stroke-width:5px;
ida-domain-mcp 的设计思路
ida-domain-mcp 的设计目标则是:
- 无图形界面(No GUI):运行在完全 headless 的环境中,不依赖任何 UI;
- 无需预加载二进制(No binary preloading):二进制数据库(IDB/binary)由 Agent 动态指定加载;
- 多 IDB 并发分析(Multi IDB analysis):每个项目拥有独立的进程上下文,可同时进行分析,互不干扰。
为了实现上述目标,我们采用了如下设计,参见图2:
- 将 IDA 进程完全抽象为一个可控制的 worker;
- 用 ida-domain 作为与 IDA headless runner (idat/idat64)之间的桥梁;
- 用一个主 MCP Server 进程负责调度多个 IDA worker;
- 用字典结构记录每个 project 与 Worker 的对应关系;
- 用管道/IPC 实现 MCP Server 与各 Worker 之间的双向调用。
1. 无图形界面(No GUI)
idat 是 IDA 官方提供的无界面 runner,可以在 CLI 或服务端环境下稳定运行,因此整个服务流程中不再有任何图形界面被触发,适合自动化运行和容器化部署。
2. 无需预加载二进制(No binary preloading)
为了摆脱必须手动预加载二进制文件这个约束,ida-domain-mcp 以 ida-domain 作为桥梁,将数据库加载过程也纳入 MCP 可控制的生命周期来解决这一问题。
ida-domain 是 Hex-Rays 官方提供的一个 Python 库,可以用来在 headless 环境下加载二进制文件,建立 IDB 数据库,并调用 IDA core API 进行分析。通过 ida-domain,我们可以在 MCP worker 进程中动态加载指定路径的二进制文件,从而实现无需预加载二进制的目标。用法示例如下:
1 | |
3. 多 IDB 并发分析(Multi IDB analysis)
为了支持多目标分析(例如同时关联分析多个 IDB、或者不同二进制的对比分析),我们引入了多进程模型,用一个主 Server 进程管理多个 IDA worker 进程。
- 每个需要分析的二进制 / IDB 都由一个独立的 IDA worker 进程负责运行;
1
2
3
4
5
6
7
8def _worker(child_conn: Connection):
msg = child_conn.recv()
func_name = msg.get("func")
func = getattr(tools, func_name)
args = msg.get("args", [])
kwargs = msg.get("kwargs", {})
result = func(*args, **kwargs)
child_conn.send({"ok": True, "result": result}) - 主 MCP Server 用一个字典结构来维护 project_name(创建时用户指定的标识符)与 worker 的对应关系,主 MCP Server 与每个 worker 之间是通过管道/IPC 保持通信;
1
2
3
4
5
6
7
8# project_name -> (Process, parent_conn)
PROJECTS: Dict[str, Tuple[multiprocessing.Process, Connection]] = {}
def open_database(project_name, path):
parent_conn, child_conn = multiprocessing.Pipe()
proc = multiprocessing.Process(target=_worker, args=(child_conn,), daemon=True)
proc.start()
reply = parent_conn.recv()
PROJECTS[project_name] = (proc, parent_conn) - 收到客户端请求时,主 MCP Server 根据 project name 定位到对应的 worker 进程,将调用通过管道送过去,由 worker 进程调用 IDA core API 并返回结果;
1
2
3
4
5def call_worker(project_name, func_name, *args, **kwargs):
proc, parent_conn = PROJECTS[project_name]
parent_conn.send({"func": func_name, "args": args, "kwargs": kwargs})
reply = parent_conn.recv()
return reply.get("result") - 不同 worker 进程之间互不干扰,可以同时对多个 IDB 进行分析。
如何使用 ida-domain-mcp
安装
方法一:从源码安装
1 | |
1 | |
方法二:通过包管理器安装
1 | |
安装 ida-domain-mcp 会同时安装其运行时依赖:
- ida-domain (通过 headless IDA 控制数据库)
❗️在运行前,需要确认 IDA 的 headless 可执行文件可以被找到:
1 | |
如果没有找到,请将 IDA 安装目录添加到 PATH 中,或者使用 ida-domain 提供的配置选项来指定 IDA 的安装路径。
启动
ida-domain-mcp 支持两种传输模式启动服务端:
- stdio(默认,适合直接与 MCP 客户端集成)
1
uv run ida-domain-mcp --transport stdio
- HTTP SSE(适合与 MCP Inspector 及远程客户端使用)
1
2uv run ida-domain-mcp --transport http://127.0.0.1:8744
# Server prints: MCP Server available at http://127.0.0.1:8744/sse
你可以使用 MCP Inspector 进行快速尝试:
1 | |
具体使用示例可参考 ida-domain-mcp example