文章目录
- [一个 MCP 端点,两个 IDA:用 ida-multi-mcp 做跨版本逆向的符号兜底](#一个 MCP 端点,两个 IDA:用 ida-multi-mcp 做跨版本逆向的符号兜底)
-
- [0. 结论先行](#0. 结论先行)
- [1. 场景:为什么必须"同时"看两个库](#1. 场景:为什么必须"同时"看两个库)
- [2. 它是什么:router + 注册表 + instance_id 路由](#2. 它是什么:router + 注册表 + instance_id 路由)
-
- [2.1 架构](#2.1 架构)
- [2.2 对我们最有用的内置工具](#2.2 对我们最有用的内置工具)
- [3. 下载与安装:官方路径优先,附我们的 Windows 落地细节](#3. 下载与安装:官方路径优先,附我们的 Windows 落地细节)
-
- [3.0 官方下载与安装(推荐先读)](#3.0 官方下载与安装(推荐先读))
-
- [3.0.1 它到底是什么形态,去哪里拿](#3.0.1 它到底是什么形态,去哪里拿)
- [3.0.2 先确认满足要求](#3.0.2 先确认满足要求)
- [3.0.3 路径 A:让 AI 一键装(官方首推)](#3.0.3 路径 A:让 AI 一键装(官方首推))
- [3.0.4 路径 B:手动 pip / pipx(三平台命令)](#3.0.4 路径 B:手动 pip / pipx(三平台命令))
- [3.0.5 `--install` 到底做了什么](#3.0.5
--install到底做了什么) - [3.0.6 CLI 速查](#3.0.6 CLI 速查)
- [3.0.7 支持的 MCP 客户端(`--install` 会自动配置检测到的)](#3.0.7 支持的 MCP 客户端(
--install会自动配置检测到的)) - [3.0.8 首次使用的三条验证(官方 post-flight 契约同款)](#3.0.8 首次使用的三条验证(官方 post-flight 契约同款))
- [3.0.9 我们的取舍(为什么下面还有一节"自己的路")](#3.0.9 我们的取舍(为什么下面还有一节"自己的路"))
- [3.1 先探测 IDA 到底用哪个 Python(最容易踩的坑)](#3.1 先探测 IDA 到底用哪个 Python(最容易踩的坑))
- [3.2 把包装进 IDA 的 Python](#3.2 把包装进 IDA 的 Python)
- [3.3 只部署 IDA 插件,先不动客户端配置](#3.3 只部署 IDA 插件,先不动客户端配置)
- [3.4 接进客户端(Trae)](#3.4 接进客户端(Trae))
- [3.5 打开两个库,让它自己注册](#3.5 打开两个库,让它自己注册)
- [4. 实战示例:这套链路怎么用](#4. 实战示例:这套链路怎么用)
-
- [4.1 双库交叉查询的心跳](#4.1 双库交叉查询的心跳)
- [4.2 从客户端确认双实例](#4.2 从客户端确认双实例)
- [4.3 跨实例并行(实测)](#4.3 跨实例并行(实测))
- [4.4 `idb_save`:改名不再丢](#4.4
idb_save:改名不再丢) - [4.5 分析门控:别在"半成品"上下结论](#4.5 分析门控:别在"半成品"上下结论)
- [5. 逆向技巧:跨版本符号定位的七种武器](#5. 逆向技巧:跨版本符号定位的七种武器)
-
- [5.1 决策总览](#5.1 决策总览)
- [5.2 武器① 尺寸谱 + 位序对齐](#5.2 武器① 尺寸谱 + 位序对齐)
- [5.3 武器② 调用者集合 + 被调位序(本批最硬)](#5.3 武器② 调用者集合 + 被调位序(本批最硬))
- [5.4 武器③ 被调元素计数](#5.4 武器③ 被调元素计数)
- [5.5 武器④ 自递归指纹](#5.5 武器④ 自递归指纹)
- [5.6 武器⑤ 唯一调用者链](#5.6 武器⑤ 唯一调用者链)
- [5.7 武器⑥ 内联检测(Δ体积法)](#5.7 武器⑥ 内联检测(Δ体积法))
- [5.8 武器⑦ 体积碰撞排雷](#5.8 武器⑦ 体积碰撞排雷)
- [6. 假阳性与排雷:体积碰撞、内联、陷阱](#6. 假阳性与排雷:体积碰撞、内联、陷阱)
-
- [6.1 体积碰撞实例:两个 0x53](#6.1 体积碰撞实例:两个 0x53)
- [6.2 体积的第二种误用:别把"版本差异"当"不同函数"](#6.2 体积的第二种误用:别把"版本差异"当"不同函数")
- [6.3 该说"未定"的时候就别说](#6.3 该说"未定"的时候就别说)
- [6.4 证据分级(我们在文档里坚持的写法)](#6.4 证据分级(我们在文档里坚持的写法))
- [7. 踩过的坑](#7. 踩过的坑)
-
- [7.1 环境类](#7.1 环境类)
- [7.2 工具行为类(这几个坑最费时间)](#7.2 工具行为类(这几个坑最费时间))
- [7.3 分析语义类](#7.3 分析语义类)
- [8. 适用边界与一句话评价](#8. 适用边界与一句话评价)
- [9. 回滚](#9. 回滚)
一个 MCP 端点,两个 IDA:用 ida-multi-mcp 做跨版本逆向的符号兜底
副标题:从"每次切库"到"双库同开、按 instance_id 双向读写"------工具落地 + 跨版本符号定位方法论(Windows + IDA Pro 9.4 + Trae)
目录
- [0. 结论先行](#0. 结论先行)
- [1. 场景:为什么必须"同时"看两个库](#1. 场景:为什么必须"同时"看两个库)
- [2. 它是什么:router + 注册表 + instance_id 路由](#2. 它是什么:router + 注册表 + instance_id 路由)
- [3. 下载与安装:官方路径优先,附我们的 Windows 落地细节](#3. 下载与安装:官方路径优先,附我们的 Windows 落地细节)
- [4. 实战示例:这套链路怎么用](#4. 实战示例:这套链路怎么用)
- [5. 逆向技巧:跨版本符号定位的七种武器](#5. 逆向技巧:跨版本符号定位的七种武器)
- [6. 假阳性与排雷:体积碰撞、内联、陷阱](#6. 假阳性与排雷:体积碰撞、内联、陷阱)
- [7. 踩过的坑](#7. 踩过的坑)
- [8. 适用边界与一句话评价](#8. 适用边界与一句话评价)
- [9. 回滚](#9. 回滚)
0. 结论先行
ida-multi-mcp 解决的是一个很具体、很痛的问题:
它把 N 个正在运行的 IDA 实例,收敛到同一个 MCP 端点后面 ,由 AI 客户端用 instance_id 直接指定操作哪个库。不需要在 IDA 窗口之间来回切,也不需要为每个二进制单独配一个 MCP 服务。
在我们这个「跨版本二进制符号匹配」项目里,它带来三件立竿见影的事:
| 变化 | 之前 | 之后 |
|---|---|---|
| 双库交叉查询 | 手工切窗口 / 离线批量导出 | 一句话里指定 instance_id 直接读 |
| 改名落盘 | 人手点 File → Save(忘一次回退几十处) |
idb_save 由 AI 落盘,文件时间戳可核 |
| 跨库并发 | 单实例串行 | 实测 串行 0.07s → 并行 0.02s |
本次会话的净产出:51 处符号改名 (分 5 批),全部落盘;PageContentGenerator 整个类族从"完全未命名"到收口。
1. 场景:为什么必须"同时"看两个库
目标是对 macOS 版 PDFelement 的核心库 libPdfCore.dylib 做跨版本符号匹配。
#mermaid-svg-c4SXaBl1fua2rJoA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c4SXaBl1fua2rJoA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c4SXaBl1fua2rJoA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c4SXaBl1fua2rJoA .error-icon{fill:#552222;}#mermaid-svg-c4SXaBl1fua2rJoA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c4SXaBl1fua2rJoA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c4SXaBl1fua2rJoA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c4SXaBl1fua2rJoA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c4SXaBl1fua2rJoA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c4SXaBl1fua2rJoA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c4SXaBl1fua2rJoA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c4SXaBl1fua2rJoA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c4SXaBl1fua2rJoA .marker.cross{stroke:#333333;}#mermaid-svg-c4SXaBl1fua2rJoA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c4SXaBl1fua2rJoA p{margin:0;}#mermaid-svg-c4SXaBl1fua2rJoA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c4SXaBl1fua2rJoA .cluster-label text{fill:#333;}#mermaid-svg-c4SXaBl1fua2rJoA .cluster-label span{color:#333;}#mermaid-svg-c4SXaBl1fua2rJoA .cluster-label span p{background-color:transparent;}#mermaid-svg-c4SXaBl1fua2rJoA .label text,#mermaid-svg-c4SXaBl1fua2rJoA span{fill:#333;color:#333;}#mermaid-svg-c4SXaBl1fua2rJoA .node rect,#mermaid-svg-c4SXaBl1fua2rJoA .node circle,#mermaid-svg-c4SXaBl1fua2rJoA .node ellipse,#mermaid-svg-c4SXaBl1fua2rJoA .node polygon,#mermaid-svg-c4SXaBl1fua2rJoA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c4SXaBl1fua2rJoA .rough-node .label text,#mermaid-svg-c4SXaBl1fua2rJoA .node .label text,#mermaid-svg-c4SXaBl1fua2rJoA .image-shape .label,#mermaid-svg-c4SXaBl1fua2rJoA .icon-shape .label{text-anchor:middle;}#mermaid-svg-c4SXaBl1fua2rJoA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c4SXaBl1fua2rJoA .rough-node .label,#mermaid-svg-c4SXaBl1fua2rJoA .node .label,#mermaid-svg-c4SXaBl1fua2rJoA .image-shape .label,#mermaid-svg-c4SXaBl1fua2rJoA .icon-shape .label{text-align:center;}#mermaid-svg-c4SXaBl1fua2rJoA .node.clickable{cursor:pointer;}#mermaid-svg-c4SXaBl1fua2rJoA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c4SXaBl1fua2rJoA .arrowheadPath{fill:#333333;}#mermaid-svg-c4SXaBl1fua2rJoA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c4SXaBl1fua2rJoA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c4SXaBl1fua2rJoA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c4SXaBl1fua2rJoA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c4SXaBl1fua2rJoA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c4SXaBl1fua2rJoA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c4SXaBl1fua2rJoA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c4SXaBl1fua2rJoA .cluster text{fill:#333;}#mermaid-svg-c4SXaBl1fua2rJoA .cluster span{color:#333;}#mermaid-svg-c4SXaBl1fua2rJoA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c4SXaBl1fua2rJoA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c4SXaBl1fua2rJoA rect.text{fill:none;stroke-width:0;}#mermaid-svg-c4SXaBl1fua2rJoA .icon-shape,#mermaid-svg-c4SXaBl1fua2rJoA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c4SXaBl1fua2rJoA .icon-shape p,#mermaid-svg-c4SXaBl1fua2rJoA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c4SXaBl1fua2rJoA .icon-shape .label rect,#mermaid-svg-c4SXaBl1fua2rJoA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c4SXaBl1fua2rJoA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c4SXaBl1fua2rJoA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c4SXaBl1fua2rJoA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 以符号体系为基准
结构/常量/偏移/调用图 比对
认出来 → 命名 → 落盘
目标版 tgt · 12.1.28
libPdfCore.dylib.i64
140,442 个函数
几乎全是 sub_XXXXXX
仅 738 个具名
工厂 vtable @0x3596570
优化器 vtable @0x35995C0
参考版 ref · 11.4.22
libPdfCore.dylib.i64
138,552 个函数
符号相对完整
CPDF_* / ContentList* / CBaseOptimizer...
工厂 vtable @0x32AA640
优化器 vtable @0x32AD318
12.1.28 可读的符号体系
这个任务的工作模式,决定了工具需求:
- 每认一个函数,都要「左边看一眼 ref 的地址/尺寸/反编译,右边看一眼 tgt 的对应候选」------双向高频交叉;
- 判断依据是结构、常量、字段偏移、调用关系(我们刻意不碰哈希/字符串指纹);
- 单次会话涉及几十上百次交叉查询。
在一个 MCP 端点出现之前,我们的做法只有两条路,都很别扭:
- 单库切库:断开重连、上下文串味,一次会话只能深挖一个版本;
- 离线批量导出:把参考版做无头脚本一次性 dump 出来,再离线比对------不实时、不交互,遇到新问题就得重跑一轮。
ida-multi-mcp 把这条路打通了:两个库同时开着,一个端点,双向可编程。
2. 它是什么:router + 注册表 + instance_id 路由
2.1 架构
#mermaid-svg-tRIYIyOZRJMDNRIy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tRIYIyOZRJMDNRIy .error-icon{fill:#552222;}#mermaid-svg-tRIYIyOZRJMDNRIy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tRIYIyOZRJMDNRIy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tRIYIyOZRJMDNRIy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tRIYIyOZRJMDNRIy .marker.cross{stroke:#333333;}#mermaid-svg-tRIYIyOZRJMDNRIy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tRIYIyOZRJMDNRIy p{margin:0;}#mermaid-svg-tRIYIyOZRJMDNRIy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tRIYIyOZRJMDNRIy .cluster-label text{fill:#333;}#mermaid-svg-tRIYIyOZRJMDNRIy .cluster-label span{color:#333;}#mermaid-svg-tRIYIyOZRJMDNRIy .cluster-label span p{background-color:transparent;}#mermaid-svg-tRIYIyOZRJMDNRIy .label text,#mermaid-svg-tRIYIyOZRJMDNRIy span{fill:#333;color:#333;}#mermaid-svg-tRIYIyOZRJMDNRIy .node rect,#mermaid-svg-tRIYIyOZRJMDNRIy .node circle,#mermaid-svg-tRIYIyOZRJMDNRIy .node ellipse,#mermaid-svg-tRIYIyOZRJMDNRIy .node polygon,#mermaid-svg-tRIYIyOZRJMDNRIy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tRIYIyOZRJMDNRIy .rough-node .label text,#mermaid-svg-tRIYIyOZRJMDNRIy .node .label text,#mermaid-svg-tRIYIyOZRJMDNRIy .image-shape .label,#mermaid-svg-tRIYIyOZRJMDNRIy .icon-shape .label{text-anchor:middle;}#mermaid-svg-tRIYIyOZRJMDNRIy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tRIYIyOZRJMDNRIy .rough-node .label,#mermaid-svg-tRIYIyOZRJMDNRIy .node .label,#mermaid-svg-tRIYIyOZRJMDNRIy .image-shape .label,#mermaid-svg-tRIYIyOZRJMDNRIy .icon-shape .label{text-align:center;}#mermaid-svg-tRIYIyOZRJMDNRIy .node.clickable{cursor:pointer;}#mermaid-svg-tRIYIyOZRJMDNRIy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tRIYIyOZRJMDNRIy .arrowheadPath{fill:#333333;}#mermaid-svg-tRIYIyOZRJMDNRIy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tRIYIyOZRJMDNRIy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tRIYIyOZRJMDNRIy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tRIYIyOZRJMDNRIy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tRIYIyOZRJMDNRIy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tRIYIyOZRJMDNRIy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tRIYIyOZRJMDNRIy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tRIYIyOZRJMDNRIy .cluster text{fill:#333;}#mermaid-svg-tRIYIyOZRJMDNRIy .cluster span{color:#333;}#mermaid-svg-tRIYIyOZRJMDNRIy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tRIYIyOZRJMDNRIy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tRIYIyOZRJMDNRIy rect.text{fill:none;stroke-width:0;}#mermaid-svg-tRIYIyOZRJMDNRIy .icon-shape,#mermaid-svg-tRIYIyOZRJMDNRIy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tRIYIyOZRJMDNRIy .icon-shape p,#mermaid-svg-tRIYIyOZRJMDNRIy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tRIYIyOZRJMDNRIy .icon-shape .label rect,#mermaid-svg-tRIYIyOZRJMDNRIy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tRIYIyOZRJMDNRIy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tRIYIyOZRJMDNRIy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tRIYIyOZRJMDNRIy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} stdio / MCP(单一端点)
HTTP JSON-RPC
HTTP JSON-RPC
HTTP JSON-RPC
打开数据库即注册
打开数据库即注册
发现
MCP 客户端
Trae / Claude Code / Cursor ...
ida-multi-mcp Router
动态工具发现(本机实测 76 个工具)
instance_id 路由 · worker pool
健康检查 · idalib 生命周期
~/.ida-mcp/instances.json
共享注册表
IDA #1(GUI)
12.1.28 · id=3u2h · port 8736
IDA #2(GUI)
11.4.22 · id=reiw · port 7461
idalib #1(无头)
可选
ida-multi-mcp 是在 ida-pro-mcp(mrexodia 的 IDA MCP 插件,本项目的工具实现来源)之上加的一层 router。三个关键机制:
- 零配置发现 :装好插件后,IDA 每次打开数据库 都会把自己注册进
~/.ida-mcp/instances.json,分配一个 4 字符短 id(我们拿到3u2h/reiw)。 - 按 id 路由 :所有工具带
instance_id。注册 ≥2 个实例时该参数必填------漏填直接报错并列出可用实例,从协议层面堵死"改错库"。 - 跨实例并行:请求走 worker pool,对实例 B 的调用不被实例 A 的慢调用堵住。
2.2 对我们最有用的内置工具
| 工具 | 作用 | 解决了什么 |
|---|---|---|
list_instances |
列出已注册实例 | 每次开工先确认 id(id 会随 IDA 重启变化) |
idb_save |
无对话框直接落盘 | 根治"改名未保存丢失" |
analysis_status / analysis_wait |
自动分析门控 | 避免在分析未完成时拿到残缺函数表 |
analysis_step |
有界推进分析队列 | 大库分析不卡死实例 |
callees / xrefs_to |
调用关系 | 符号定位的核心证据来源 |
idalib_open |
无头开启会话 | 参考版可以不开 GUI |
py_eval |
进程内任意 IDAPython | 逃生舱 |
项目还自带 BCSD「跨二进制函数相似度」(MinHash + CFG + 锚点)。我们刻意没用------方法论约束是「只用结构/常量/偏移/调用图,不用哈希或字符串指纹」。这不影响使用:路由、落盘、门控与相似度是正交能力,随取随用。
3. 下载与安装:官方路径优先,附我们的 Windows 落地细节
3.0 官方下载与安装(推荐先读)
3.0.1 它到底是什么形态,去哪里拿
项目在 GitHub:https://github.com/MeroZemory/ida-multi-mcp(MIT )。没有预编译安装包 / Release 二进制,它是两件东西:
- 一个 pip 安装的 Python 包(router + 全套 IDA 工具实现);
- 包内自带一个 IDA 插件加载器 (按需部署到 IDA 的
plugins目录)。
所以"下载"就是"pip 装包","安装"就是"部署加载器 + 写客户端配置"。
3.0.2 先确认满足要求
| 要求 | 说明 |
|---|---|
| Python | 3.11+(IDA 侧那个解释器也要满足) |
| IDA Pro | 8.5+ (推荐 9.1+);README 徽章标注支持 8.5--9.3 |
| ⚠️ 特例 | IDA 9.0 SP0(build 240925)不支持 ------该版本缺 func_t.get_name / func_t.get_prototype / tinfo_t.get_udm。请用 9.0 SP1(241217) 或更高 |
| 无头 idalib | 需 IDA Pro 授权 (Home / Free 不带 idalib) |
3.0.3 路径 A:让 AI 一键装(官方首推)
README 的 "Install in one prompt" 就是给 AI 客户端用的提示词,复制进对话即可:
Claude Code / Codex
Install and configure ida-multi-mcp by following the instructions here: https://raw.githubusercontent.com/MeroZemory/ida-multi-mcp/main/docs/installation.mdCursor
@Web fetch https://raw.githubusercontent.com/MeroZemory/ida-multi-mcp/main/docs/installation.md and follow the installation steps.
它会按官方 docs/installation.md 的 agent 契约执行:pre-flight 自检 → 平台安装 → post-flight 自检 → 未通过则走 auto-remediation 再复检,全部通过才报成功。
3.0.4 路径 B:手动 pip / pipx(三平台命令)
Windows
powershell
# 装包(若 IDA 用的不是系统默认 Python,换成 IDA 那个版本,例如 py -3.12)
python -m pip install git+https://github.com/MeroZemory/ida-multi-mcp.git
# 部署插件 + 自动配置所有检测到的 MCP 客户端
ida-multi-mcp --install
macOS(IDA 的 Python 常与系统 Python 不同,两边都要装)
bash
# 1) 终端用 CLI(pipx 隔离安装)
pipx install git+https://github.com/MeroZemory/ida-multi-mcp.git
# 2) 给 IDA 的 Python 装包(把 3.11 换成 IDA 实际版本)
python3.11 -m pip install --user git+https://github.com/MeroZemory/ida-multi-mcp.git
# 3) 部署插件 + 配置客户端
ida-multi-mcp --install
# 4) Claude Code 更推荐手动钉住 pipx 的 CLI
claude mcp add ida-multi-mcp -s user -- ida-multi-mcp
Linux
bash
pip install --user git+https://github.com/MeroZemory/ida-multi-mcp.git
ida-multi-mcp --install
自定义 IDA 路径:
ida-multi-mcp --install --ida-dir "C:/Program Files/IDA Pro 9.0"指定无头会话的 Python:
ida-multi-mcp --idalib-python /path/to/python3.11内网 / 离线环境:
git clone https://github.com/MeroZemory/ida-multi-mcp.git,然后cd ida-multi-mcp && pip install .(不依赖 pip 直连 git)。
3.0.5 --install 到底做了什么
它同时做两件事:
- 部署 IDA 插件加载器 到 IDA 的
plugins目录(优先创建符号链接以便跟随包升级,失败则复制;Windows 上复制更常见); - 把 MCP 服务条目写入它检测到的所有客户端配置(20+ 种)。
#mermaid-svg-kaVTUY2DrcNMgdk5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kaVTUY2DrcNMgdk5 .error-icon{fill:#552222;}#mermaid-svg-kaVTUY2DrcNMgdk5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kaVTUY2DrcNMgdk5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .marker.cross{stroke:#333333;}#mermaid-svg-kaVTUY2DrcNMgdk5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kaVTUY2DrcNMgdk5 p{margin:0;}#mermaid-svg-kaVTUY2DrcNMgdk5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .cluster-label text{fill:#333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .cluster-label span{color:#333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .cluster-label span p{background-color:transparent;}#mermaid-svg-kaVTUY2DrcNMgdk5 .label text,#mermaid-svg-kaVTUY2DrcNMgdk5 span{fill:#333;color:#333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .node rect,#mermaid-svg-kaVTUY2DrcNMgdk5 .node circle,#mermaid-svg-kaVTUY2DrcNMgdk5 .node ellipse,#mermaid-svg-kaVTUY2DrcNMgdk5 .node polygon,#mermaid-svg-kaVTUY2DrcNMgdk5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kaVTUY2DrcNMgdk5 .rough-node .label text,#mermaid-svg-kaVTUY2DrcNMgdk5 .node .label text,#mermaid-svg-kaVTUY2DrcNMgdk5 .image-shape .label,#mermaid-svg-kaVTUY2DrcNMgdk5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-kaVTUY2DrcNMgdk5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kaVTUY2DrcNMgdk5 .rough-node .label,#mermaid-svg-kaVTUY2DrcNMgdk5 .node .label,#mermaid-svg-kaVTUY2DrcNMgdk5 .image-shape .label,#mermaid-svg-kaVTUY2DrcNMgdk5 .icon-shape .label{text-align:center;}#mermaid-svg-kaVTUY2DrcNMgdk5 .node.clickable{cursor:pointer;}#mermaid-svg-kaVTUY2DrcNMgdk5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .arrowheadPath{fill:#333333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kaVTUY2DrcNMgdk5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kaVTUY2DrcNMgdk5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kaVTUY2DrcNMgdk5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kaVTUY2DrcNMgdk5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kaVTUY2DrcNMgdk5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kaVTUY2DrcNMgdk5 .cluster text{fill:#333;}#mermaid-svg-kaVTUY2DrcNMgdk5 .cluster span{color:#333;}#mermaid-svg-kaVTUY2DrcNMgdk5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kaVTUY2DrcNMgdk5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kaVTUY2DrcNMgdk5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-kaVTUY2DrcNMgdk5 .icon-shape,#mermaid-svg-kaVTUY2DrcNMgdk5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kaVTUY2DrcNMgdk5 .icon-shape p,#mermaid-svg-kaVTUY2DrcNMgdk5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kaVTUY2DrcNMgdk5 .icon-shape .label rect,#mermaid-svg-kaVTUY2DrcNMgdk5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kaVTUY2DrcNMgdk5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kaVTUY2DrcNMgdk5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kaVTUY2DrcNMgdk5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pip install git+...
① 装 Python 包
ida-multi-mcp --install
② 部署 IDA 插件加载器
plugins/ida_multi_mcp.py
③ 写入客户端配置
Claude Code / Cursor / VS Code / Trae ...
打开任意二进制
插件自动注册 instance
客户端出现
mcp_ida-multi-mcp_* 工具
list_instances() 看到实例
3.0.6 CLI 速查
| 命令 | 作用 |
|---|---|
ida-multi-mcp --install |
装插件 + 配置所有客户端 |
ida-multi-mcp --list |
列出已注册实例(id / 路径 / 架构 / 端口 / PID) |
ida-multi-mcp --config |
打印客户端配置 JSON(手动粘贴用) |
ida-multi-mcp --verify |
校验插件加载器(JSON 输出,match = sha256 比对结果) |
ida-multi-mcp --uninstall |
移除插件 + 清理注册表 + 移除客户端配置 |
ida-multi-mcp --ida-dir <路径> |
自定义 IDA 目录(--install/--uninstall/--verify 适用) |
ida-multi-mcp --registry <路径> |
自定义注册表文件(默认 ~/.ida-mcp/instances.json) |
ida-multi-mcp --idalib-python <python> |
无头 idalib 会话用哪个解释器 |
3.0.7 支持的 MCP 客户端(--install 会自动配置检测到的)
- CLI:Claude Code / Codex / Gemini CLI / Copilot CLI / Amazon Q Developer CLI / Qwen Coder / Opencode / Crush / Factory Droid
- IDE :Cursor / VS Code(Copilot)/ Windsurf / Zed / Kiro / Trae / Antigravity / Augment Code
- Desktop:Claude Desktop / LM Studio / BoltAI / Perplexity
- 编辑器扩展:Cline / Roo Code / Kilo Code / Qodo Gen
- 终端:Warp
没被自动检测到的,用 ida-multi-mcp --config 打印 JSON 自己粘。
3.0.8 首次使用的三条验证(官方 post-flight 契约同款)
- 打开任意二进制 → IDA 输出窗出现
[ida-multi-mcp] Registered as instance 'xxxx' - 终端执行
ida-multi-mcp --list→ 至少 1 个实例 - 客户端调用
list_instances()+ 任一带显式instance_id的安全工具(如analysis_status)
第 2、3 条都没问题,才算真正可用。
3.0.9 我们的取舍(为什么下面还有一节"自己的路")
--install 会顺带改写客户端配置 。我们当时要满足两件事:① 先做非侵入式验证 ;② 保留机器上原本并存的另一套 IDA MCP 插件。所以我们跳过了 --install,改成"只手动部署插件、客户端配置自己写一条新增条目"。
如果你是从零开始、也没有并存需求,直接用 --install 最省事,可以跳过下面 3.3 的手动步骤。
以下是我们自己走的那条路(非侵入式)。先看全流程,再逐段展开:
#mermaid-svg-a1RwzvwEfvOBgXOc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a1RwzvwEfvOBgXOc .error-icon{fill:#552222;}#mermaid-svg-a1RwzvwEfvOBgXOc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a1RwzvwEfvOBgXOc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a1RwzvwEfvOBgXOc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a1RwzvwEfvOBgXOc .marker.cross{stroke:#333333;}#mermaid-svg-a1RwzvwEfvOBgXOc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a1RwzvwEfvOBgXOc p{margin:0;}#mermaid-svg-a1RwzvwEfvOBgXOc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a1RwzvwEfvOBgXOc .cluster-label text{fill:#333;}#mermaid-svg-a1RwzvwEfvOBgXOc .cluster-label span{color:#333;}#mermaid-svg-a1RwzvwEfvOBgXOc .cluster-label span p{background-color:transparent;}#mermaid-svg-a1RwzvwEfvOBgXOc .label text,#mermaid-svg-a1RwzvwEfvOBgXOc span{fill:#333;color:#333;}#mermaid-svg-a1RwzvwEfvOBgXOc .node rect,#mermaid-svg-a1RwzvwEfvOBgXOc .node circle,#mermaid-svg-a1RwzvwEfvOBgXOc .node ellipse,#mermaid-svg-a1RwzvwEfvOBgXOc .node polygon,#mermaid-svg-a1RwzvwEfvOBgXOc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a1RwzvwEfvOBgXOc .rough-node .label text,#mermaid-svg-a1RwzvwEfvOBgXOc .node .label text,#mermaid-svg-a1RwzvwEfvOBgXOc .image-shape .label,#mermaid-svg-a1RwzvwEfvOBgXOc .icon-shape .label{text-anchor:middle;}#mermaid-svg-a1RwzvwEfvOBgXOc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a1RwzvwEfvOBgXOc .rough-node .label,#mermaid-svg-a1RwzvwEfvOBgXOc .node .label,#mermaid-svg-a1RwzvwEfvOBgXOc .image-shape .label,#mermaid-svg-a1RwzvwEfvOBgXOc .icon-shape .label{text-align:center;}#mermaid-svg-a1RwzvwEfvOBgXOc .node.clickable{cursor:pointer;}#mermaid-svg-a1RwzvwEfvOBgXOc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a1RwzvwEfvOBgXOc .arrowheadPath{fill:#333333;}#mermaid-svg-a1RwzvwEfvOBgXOc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a1RwzvwEfvOBgXOc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a1RwzvwEfvOBgXOc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a1RwzvwEfvOBgXOc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a1RwzvwEfvOBgXOc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a1RwzvwEfvOBgXOc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a1RwzvwEfvOBgXOc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a1RwzvwEfvOBgXOc .cluster text{fill:#333;}#mermaid-svg-a1RwzvwEfvOBgXOc .cluster span{color:#333;}#mermaid-svg-a1RwzvwEfvOBgXOc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a1RwzvwEfvOBgXOc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a1RwzvwEfvOBgXOc rect.text{fill:none;stroke-width:0;}#mermaid-svg-a1RwzvwEfvOBgXOc .icon-shape,#mermaid-svg-a1RwzvwEfvOBgXOc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a1RwzvwEfvOBgXOc .icon-shape p,#mermaid-svg-a1RwzvwEfvOBgXOc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a1RwzvwEfvOBgXOc .icon-shape .label rect,#mermaid-svg-a1RwzvwEfvOBgXOc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a1RwzvwEfvOBgXOc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a1RwzvwEfvOBgXOc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a1RwzvwEfvOBgXOc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} match:false
match:true
no
yes
① 探测 IDA 用的是哪个 Python
idapyswitch -r -v / 读注册表 Python3TargetDLL
拿到 IDA 侧解释器路径
② pip install 到那个 解释器
PEP668 → --break-system-packages
③ 只部署插件(先不动客户端配置)
plugins/ida_multi_mcp.py
--verify
sha256 match?
④ 客户端 mcp.json 新增一条
ida-multi-mcp
⑤ 刷新/重启客户端 MCP 服务
⑥ 重启 IDA + 分别打开两个库
ida.exe -A xxx.i64
--list / list_instances
count ≥ 2 ?
检查:插件是否加载 / Python 是否匹配
看 IDA 输出窗的注册日志
✅ 一个端点,两个库
3.1 先探测 IDA 到底用哪个 Python(最容易踩的坑)
插件必须能被 IDA 自己的 Python import 到。IDA 9.4 不自带 Python,而是用 idapyswitch 外接------别假设它等于系统 Python。
powershell
# 方式一:看 IDA 当前选的 Python(-r = dry-run,不改配置)
& "C:\Program Files\IDA Professional 9.4\idapyswitch.exe" -r -v
# 方式二(最准):直接读注册表
Get-ItemProperty "HKCU:\Software\Hex-Rays\IDA" | Select-Object Python3TargetDLL
我们的结果(IDA 的 Python 和"系统默认 Python"根本不是同一个):
Python3TargetDLL : C:\Users\<用户>\AppData\Roaming\uv\python\cpython-3.14.6-windows-x86_64-none\python314.dll
即 IDA 用的是 uv 管理的 CPython 3.14.6 。插件目录(Windows):%APPDATA%\Hex-Rays\IDA Pro\plugins。
3.2 把包装进 IDA 的 Python
powershell
$py = "C:\Users\<用户>\AppData\Roaming\uv\python\cpython-3.14.6-windows-x86_64-none\python.exe"
# uv / py 管理的解释器带 PEP 668 保护,会报 externally-managed-environment
& $py -m pip install --break-system-packages --upgrade `
"git+https://github.com/MeroZemory/ida-multi-mcp.git"
& $py -c "import ida_multi_mcp as m; print(m.__version__)" # → 0.1.0
--break-system-packages是官方文档在 macOS 上就推荐的做法;这里只新增 一个包,不动其他依赖,随时可pip uninstall。我们还多做了一步"消除歧义":把包同时装进两个 3.14.6 (uv 那个 +
pythoncore-3.14-64那个)。因为当时发现旧插件mcp-plugin.py是指向pythoncore-3.14-64的符号链接,而注册表指向 uv 那个------两个版本号相同,装两边最省事。
3.3 只部署 IDA 插件,先不动客户端配置
ida-multi-mcp --install 会同时 装插件 + 改写所有检测到的 MCP 客户端配置。我们想先做非侵入式验证,所以只做插件那一步:
powershell
$sp = "C:\Users\<用户>\AppData\Roaming\uv\python\cpython-3.14.6-windows-x86_64-none\Lib\site-packages\ida_multi_mcp"
$src = Join-Path $sp "plugin\ida_multi_mcp_loader.py"
$dst = "$env:APPDATA\Hex-Rays\IDA Pro\plugins\ida_multi_mcp.py"
Copy-Item $src $dst -Force
(--install 的实现也是这个动作:优先符号链接、失败则复制为 plugins/ida_multi_mcp.py。手动复制等价,只是升级包后需重新复制。)
powershell
& $py -m ida_multi_mcp --verify
# → {"mode":"copy","exists":true,"match":true, ...}
3.4 接进客户端(Trae)
Trae 的 MCP 配置在 %APPDATA%\Trae CN\User\mcp.json。用 --config 拿官方格式,然后新增一条(原有条目保留):
powershell
& $py -m ida_multi_mcp --config
json
{
"mcpServers": {
"ida-multi-mcp": {
"command": "C:\\Users\\<用户>\\AppData\\Roaming\\uv\\python\\cpython-3.14.6-windows-x86_64-none\\python.exe",
"args": ["-m", "ida_multi_mcp"],
"timeout": 1800
}
}
}
改完在客户端刷新/重启 MCP 服务 (我们重启了 Trae CN),mcp_ida-multi-mcp_* 工具就出现在可用列表里。
3.5 打开两个库,让它自己注册
powershell
# -A(autonomous)免掉 "Load a new file" 对话框
ida.exe -A "C:\...\PDFelement 12.1.28\...\libPdfCore.dylib.i64"
ida.exe -A "C:\...\PDFelement 11.4.22\...\libPdfCore.dylib.i64"
powershell
& $py -m ida_multi_mcp --list
Registered IDA instances (2):
3u2h libPdfCore.dylib C:\...\12.1.28\... metapc-64 port 8736 PID 4808
reiw libPdfCore.dylib C:\...\11.4.22\... metapc-64 port 7461 PID 15224
4. 实战示例:这套链路怎么用
4.1 双库交叉查询的心跳
IDA reiw (11.4.22) IDA 3u2h (12.1.28) Router AI 客户端 IDA reiw (11.4.22) IDA 3u2h (12.1.28) Router AI 客户端 #mermaid-svg-TKMDRsy8QhjRclO4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TKMDRsy8QhjRclO4 .error-icon{fill:#552222;}#mermaid-svg-TKMDRsy8QhjRclO4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TKMDRsy8QhjRclO4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TKMDRsy8QhjRclO4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TKMDRsy8QhjRclO4 .marker.cross{stroke:#333333;}#mermaid-svg-TKMDRsy8QhjRclO4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TKMDRsy8QhjRclO4 p{margin:0;}#mermaid-svg-TKMDRsy8QhjRclO4 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TKMDRsy8QhjRclO4 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-TKMDRsy8QhjRclO4 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TKMDRsy8QhjRclO4 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-TKMDRsy8QhjRclO4 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-TKMDRsy8QhjRclO4 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-TKMDRsy8QhjRclO4 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-TKMDRsy8QhjRclO4 .sequenceNumber{fill:white;}#mermaid-svg-TKMDRsy8QhjRclO4 #sequencenumber{fill:#333;}#mermaid-svg-TKMDRsy8QhjRclO4 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-TKMDRsy8QhjRclO4 .messageText{fill:#333;stroke:none;}#mermaid-svg-TKMDRsy8QhjRclO4 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TKMDRsy8QhjRclO4 .labelText,#mermaid-svg-TKMDRsy8QhjRclO4 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-TKMDRsy8QhjRclO4 .loopText,#mermaid-svg-TKMDRsy8QhjRclO4 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-TKMDRsy8QhjRclO4 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TKMDRsy8QhjRclO4 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TKMDRsy8QhjRclO4 .noteText,#mermaid-svg-TKMDRsy8QhjRclO4 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-TKMDRsy8QhjRclO4 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TKMDRsy8QhjRclO4 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TKMDRsy8QhjRclO4 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TKMDRsy8QhjRclO4 .actorPopupMenu{position:absolute;}#mermaid-svg-TKMDRsy8QhjRclO4 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-TKMDRsy8QhjRclO4 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TKMDRsy8QhjRclO4 .actor-man circle,#mermaid-svg-TKMDRsy8QhjRclO4 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-TKMDRsy8QhjRclO4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} par 并行取两侧基准 list_instances() count=2 · 3u2h / reiw analysis_status(3u2h) HTTP JSON-RPC function_count=140442 idle · 140442 analysis_status(reiw) HTTP JSON-RPC function_count=138552 idle · 138552 decompile(reiw, 0x4031b0) ref 侧伪代码 decompile(3u2h, 0x48AB50) tgt 侧伪代码 → 逐语句同构 → 定名 rename(3u2h, 0x48AB50, "GetResourcIndex") idb_save(3u2h) {"ok":true,"mode":"gui-in-place"}
4.2 从客户端确认双实例
list_instances()
→ {"count":2,"instances":[
{"id":"3u2h","type":"gui","binary_name":"libPdfCore.dylib",
"binary_path":"...\\PDFelement 12.1.28\\...","arch":"metapc-64","port":8736,"pid":4808},
{"id":"reiw","type":"gui","binary_name":"libPdfCore.dylib",
"binary_path":"...\\PDFelement 11.4.22\\...","arch":"metapc-64","port":7461,"pid":15224}]}
双实例下故意漏填 instance_id,路由器给出可行动的错误:
server_health()
→ Error: {"error":"Missing required parameter 'instance_id'.",
"hint":"Call list_instances() and pass instance_id explicitly.",
"available_instances":[{"id":"3u2h",...},{"id":"reiw",...}]}
4.3 跨实例并行(实测)
SERIAL reiw=0.05s 3u2h=0.02s total=0.07s
PARALLEL both_returned=True (reiw=True, 3u2h=True) total=0.02s
两个实例同时在跑,总耗时 ≈ 较慢那一个,而不是两者之和。
4.4 idb_save:改名不再丢
#mermaid-svg-j5Zn9bXE0TliMrWe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j5Zn9bXE0TliMrWe .error-icon{fill:#552222;}#mermaid-svg-j5Zn9bXE0TliMrWe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j5Zn9bXE0TliMrWe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j5Zn9bXE0TliMrWe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j5Zn9bXE0TliMrWe .marker.cross{stroke:#333333;}#mermaid-svg-j5Zn9bXE0TliMrWe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j5Zn9bXE0TliMrWe p{margin:0;}#mermaid-svg-j5Zn9bXE0TliMrWe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-j5Zn9bXE0TliMrWe .cluster-label text{fill:#333;}#mermaid-svg-j5Zn9bXE0TliMrWe .cluster-label span{color:#333;}#mermaid-svg-j5Zn9bXE0TliMrWe .cluster-label span p{background-color:transparent;}#mermaid-svg-j5Zn9bXE0TliMrWe .label text,#mermaid-svg-j5Zn9bXE0TliMrWe span{fill:#333;color:#333;}#mermaid-svg-j5Zn9bXE0TliMrWe .node rect,#mermaid-svg-j5Zn9bXE0TliMrWe .node circle,#mermaid-svg-j5Zn9bXE0TliMrWe .node ellipse,#mermaid-svg-j5Zn9bXE0TliMrWe .node polygon,#mermaid-svg-j5Zn9bXE0TliMrWe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-j5Zn9bXE0TliMrWe .rough-node .label text,#mermaid-svg-j5Zn9bXE0TliMrWe .node .label text,#mermaid-svg-j5Zn9bXE0TliMrWe .image-shape .label,#mermaid-svg-j5Zn9bXE0TliMrWe .icon-shape .label{text-anchor:middle;}#mermaid-svg-j5Zn9bXE0TliMrWe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-j5Zn9bXE0TliMrWe .rough-node .label,#mermaid-svg-j5Zn9bXE0TliMrWe .node .label,#mermaid-svg-j5Zn9bXE0TliMrWe .image-shape .label,#mermaid-svg-j5Zn9bXE0TliMrWe .icon-shape .label{text-align:center;}#mermaid-svg-j5Zn9bXE0TliMrWe .node.clickable{cursor:pointer;}#mermaid-svg-j5Zn9bXE0TliMrWe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-j5Zn9bXE0TliMrWe .arrowheadPath{fill:#333333;}#mermaid-svg-j5Zn9bXE0TliMrWe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-j5Zn9bXE0TliMrWe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-j5Zn9bXE0TliMrWe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j5Zn9bXE0TliMrWe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-j5Zn9bXE0TliMrWe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j5Zn9bXE0TliMrWe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-j5Zn9bXE0TliMrWe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-j5Zn9bXE0TliMrWe .cluster text{fill:#333;}#mermaid-svg-j5Zn9bXE0TliMrWe .cluster span{color:#333;}#mermaid-svg-j5Zn9bXE0TliMrWe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-j5Zn9bXE0TliMrWe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-j5Zn9bXE0TliMrWe rect.text{fill:none;stroke-width:0;}#mermaid-svg-j5Zn9bXE0TliMrWe .icon-shape,#mermaid-svg-j5Zn9bXE0TliMrWe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j5Zn9bXE0TliMrWe .icon-shape p,#mermaid-svg-j5Zn9bXE0TliMrWe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-j5Zn9bXE0TliMrWe .icon-shape .label rect,#mermaid-svg-j5Zn9bXE0TliMrWe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j5Zn9bXE0TliMrWe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-j5Zn9bXE0TliMrWe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-j5Zn9bXE0TliMrWe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 时间刷新 & 体积增长
无变化
rename() 写入 IDB 内存
idb_save()
gui-in-place 落盘
校验:.i64 写入时间 + 体积
✅ 确认落盘
⚠️ 未真正写入 → 重试/排查
过去改完名靠人手点 File → Save,忘一次或 IDA 崩了就回退成 sub_。现在由 AI 落盘,并且用文件时间戳 + 体积做交叉验证(比"没看到报错"可靠得多):
| 批次 | 内容 | .i64 写入时间 |
体积 |
|---|---|---|---|
| 1 | 5 处(StatesOrOperatorStack 等) |
22:20:33 | 556,373,310 → 556,406,078 |
| 2 | 2 处(stack / set_stack) |
22:24:00 | → 556,414,270 |
| 3 | 18 处(PageContentGenerator 类族) |
22:29:58 | → 556,619,070 |
| 4 | 19 处(该族中段 + 尾段) | 22:36:33 | → 557,643,070 |
| 5 | 7 处(疑难锚点) | 22:41:58 | → 557,856,062 |
累计 51 处,全部落盘。
4.5 分析门控:别在"半成品"上下结论
这是新手最容易翻车的地方。IDA 打开二进制后后台还在分析 ,函数表 / xrefs / 反编译都不完整。
analysis_status(instance_id="3u2h")
→ {"queue_empty":true,"function_count":140442,
"hint":"queue_empty 是快照,不是闩锁 ------ 要确认 function_count 已停止变化..."}
两个要点:
queue_empty=false⇒ definitely 没结束,别信结果;queue_empty=true⇒ 只是"此刻队列为空"。持久可靠的信号是functions_added连续为 0。
5. 逆向技巧:跨版本符号定位的七种武器
本节是方法论主体。总约束:只用结构、常量、字段偏移、调用图------不使用哈希或字符串指纹。
全部技巧都在本次会话中真实跑通,数字与地址取自实际 IDB。
5.1 决策总览
#mermaid-svg-0J2pmoqYXBCE9WXx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0J2pmoqYXBCE9WXx .error-icon{fill:#552222;}#mermaid-svg-0J2pmoqYXBCE9WXx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0J2pmoqYXBCE9WXx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0J2pmoqYXBCE9WXx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0J2pmoqYXBCE9WXx .marker.cross{stroke:#333333;}#mermaid-svg-0J2pmoqYXBCE9WXx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0J2pmoqYXBCE9WXx p{margin:0;}#mermaid-svg-0J2pmoqYXBCE9WXx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0J2pmoqYXBCE9WXx .cluster-label text{fill:#333;}#mermaid-svg-0J2pmoqYXBCE9WXx .cluster-label span{color:#333;}#mermaid-svg-0J2pmoqYXBCE9WXx .cluster-label span p{background-color:transparent;}#mermaid-svg-0J2pmoqYXBCE9WXx .label text,#mermaid-svg-0J2pmoqYXBCE9WXx span{fill:#333;color:#333;}#mermaid-svg-0J2pmoqYXBCE9WXx .node rect,#mermaid-svg-0J2pmoqYXBCE9WXx .node circle,#mermaid-svg-0J2pmoqYXBCE9WXx .node ellipse,#mermaid-svg-0J2pmoqYXBCE9WXx .node polygon,#mermaid-svg-0J2pmoqYXBCE9WXx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0J2pmoqYXBCE9WXx .rough-node .label text,#mermaid-svg-0J2pmoqYXBCE9WXx .node .label text,#mermaid-svg-0J2pmoqYXBCE9WXx .image-shape .label,#mermaid-svg-0J2pmoqYXBCE9WXx .icon-shape .label{text-anchor:middle;}#mermaid-svg-0J2pmoqYXBCE9WXx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0J2pmoqYXBCE9WXx .rough-node .label,#mermaid-svg-0J2pmoqYXBCE9WXx .node .label,#mermaid-svg-0J2pmoqYXBCE9WXx .image-shape .label,#mermaid-svg-0J2pmoqYXBCE9WXx .icon-shape .label{text-align:center;}#mermaid-svg-0J2pmoqYXBCE9WXx .node.clickable{cursor:pointer;}#mermaid-svg-0J2pmoqYXBCE9WXx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0J2pmoqYXBCE9WXx .arrowheadPath{fill:#333333;}#mermaid-svg-0J2pmoqYXBCE9WXx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0J2pmoqYXBCE9WXx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0J2pmoqYXBCE9WXx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0J2pmoqYXBCE9WXx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0J2pmoqYXBCE9WXx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0J2pmoqYXBCE9WXx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0J2pmoqYXBCE9WXx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0J2pmoqYXBCE9WXx .cluster text{fill:#333;}#mermaid-svg-0J2pmoqYXBCE9WXx .cluster span{color:#333;}#mermaid-svg-0J2pmoqYXBCE9WXx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0J2pmoqYXBCE9WXx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0J2pmoqYXBCE9WXx rect.text{fill:none;stroke-width:0;}#mermaid-svg-0J2pmoqYXBCE9WXx .icon-shape,#mermaid-svg-0J2pmoqYXBCE9WXx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0J2pmoqYXBCE9WXx .icon-shape p,#mermaid-svg-0J2pmoqYXBCE9WXx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0J2pmoqYXBCE9WXx .icon-shape .label rect,#mermaid-svg-0J2pmoqYXBCE9WXx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0J2pmoqYXBCE9WXx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0J2pmoqYXBCE9WXx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0J2pmoqYXBCE9WXx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 能
不能
有唯一调用者
有精确尺寸
都没有
计数极小/精确相等
是递归
是
有差异
是
否
待定位的目标函数
能找到
同构的父函数吗?
武器② 调用者集合 + 被调位序
(一次可定多项)
有精确相等的尺寸
或唯一调用者吗?
武器⑤ 唯一调用者链
武器① 尺寸谱 + 位序对齐
能被调的
'元素个数'特殊吗?
武器③ 被调元素计数
武器④ 自递归指纹
逐语句同构验证
两侧行为完全一致?
定名 + 证据分级 + idb_save
差异是'内联/合并'造成的吗?
武器⑥ 内联检测(Δ体积法)
武器⑦ 排雷:体积碰撞
→ 标'未定',不硬凑名字
5.2 武器① 尺寸谱 + 位序对齐
原理 :ref 与 tgt 若来自同一套源码、同一编译单元,同类函数保持相对顺序,且尺寸高度接近。
实战命中(最短路径):
ref GetResourcIndex(fxcrt::ByteString const&) @0x4031b0 size 0xaf
tgt sub_48AB50 @0x48AB50 size 0xaf ← 尺寸精确相等
位置关系也一致(两端都"紧邻 InitResource``",且两侧 InitResource` 前的整簇顺序完全相同):
ref ... Pop(0x403160,0x50) → GetResourcIndex(0x4031b0,0xaf) → InitResource(0x403260,0x512)
tgt ... Pop(0x48AAF0,0x53) → GetResourcIndex(0x48AB50,0xaf) → InitResource(0x48AC00,0x512)
再补一次逐语句同构 (6 连 ByteString::operator== 依次返回 1...6,否则 0)即定名。
尺寸精确相等的批量命中(我们真实拿到的):
| 名称 | ref 尺寸 | tgt 尺寸 |
|---|---|---|
GenerateSingleObject |
0xe6 | 0xe6 |
Push |
0x2a | 0x2a |
StatesOrOperator::GetModifyStates |
0xdc | 0xdc |
PageContentGenerator C2 / C1 |
0x56 / 0x56 | 0x56 / 0x56 |
InitStates |
0xe | 0xe |
StatesOrOperator C2 / C1 |
0xb3 / 0xa | 0xb3 / 0xa |
ResetGraphicsStates |
0x83 | 0x83 |
GenerateGeneralStateData |
0x432 | 0x435 |
GenerateTextStateData |
0x6d0 | 0x6da |
注意:尺寸只是"入场券",不是判决书 ------ 见 §6.1 的 0x53 碰撞案例。
5.3 武器② 调用者集合 + 被调位序(本批最硬)
做法分两半:
- 调用者集合 :
xrefs_to(候选),把返回的父函数按地址升序与 ref 侧对应父函数逐一比对; - 被调位序 :取父函数内部调用点的地址升序 ,与 ref 对应父函数内部调用点升序逐一对应。
一次定死 5 项的真实案例:
ref GenerateSingleObject (size 0xe6) tgt 0x4D4BF0 (size 0xe6 ← 精确)
0x448790 → GenerateTextContent 0x4d4c70 → 0x4D7000
0x4487af → GeneratePathContent 0x4d4cd1 → 0x4D7740
0x4487c5 → ProcessImage 0x4d4ca5 → 0x4D5CE0
0x4487db → GenerateShadingContent 0x4d4cbb → 0x4D8A20
0x4487f1 → GenerateFormContent 0x4d4c8f → 0x4D8C10
再做一层交叉验证 :RealizeResource 两侧各 9 处调用者 ,身份与顺序全对齐(含 gGenClrData×2 + GenerateTextStateData)。这一步的额外价值是顺手把两个还没名字的函数定了 ------gGenClrData(tgt 0x4DC660)、GenExtGSName(tgt 0x4D9450)。
为什么它比尺寸更硬 :调用关系是语义 层面的,编译器换版本、换优化级别也难改掉它;尺寸是形态层面的,容易碰巧相等。
5.4 武器③ 被调元素计数
编译器生成的模板实例化,被调集合通常极小 ,于是"元素个数精确相等"本身就成了强指纹。
tgt 0x4DDCB0 (0x9f) ← 唯一调用者 = StatesOrOperator::GetModifyStates
被调:仅 1 个 → sub_470E70
ref 0x450c10 ← 唯一调用者 = 同上
被调:仅 1 个 → CBaseObject::CBaseObject(...)
⇒ std::__shared_ptr_emplace<CPDF_GraphicStates> 的构造函数
并且顺带坐实:tgt sub_470E70 = CBaseObject::CBaseObject
同类命中:
| 目标 | 被调数(ref : tgt) | 结论 |
|---|---|---|
gGetSameItemCount |
5 : 5 | CountItems / GetItem / ByteString::== / GetParam / IsIdentical |
IsChanged(ExtGSKeyIndex 重载) |
7 : 7 | 全为 CPDF_GeneralState getter 链 |
__shared_ptr_emplace<CPDF_GraphicStates> ctor |
1 : 1 | 唯一的基类 ctor 调用 |
技巧要点 :计数相等比尺寸相等更难巧合。当候选函数又小又"单薄"(被调 1~7 个)时,优先用这招。
5.5 武器④ 自递归指纹
有些辅助函数会调用自己(递归遍历对象树)。这个特征极具辨识度,且不需要知道它调了谁:
tgt 0x4D7F90 (0x8b1) = gGenObjData ← xrefs_to 里出现 2 次自身
tgt 0x4DD6D0 (0x509) = gToIndirectObj ← xrefs_to 里出现 3 次自身
配合"被调集合轮廓"(ByteString 析构、流/字符串机制、对象 getter、_katanafree)即可确认。ref 侧同名函数同样是自递归------这一点两侧必须一致,不一致就说明认错了。
5.6 武器⑤ 唯一调用者链
当候选函数只有一个调用者时,"调用者是谁"就等价于"它是谁"。
tgt 0x4D5F90 (0x4da) ← 唯一调用者 = ProcessImage ⇒ GenerateInlineImageContent
tgt 0x4D5CE0 (0x235) ← 唯一调用者 = GenerateSingleObject ⇒ ProcessImage
tgt 0x4DDDA0 (0x287) ← 唯一调用者 = Stack::Init ⇒ vector::__push_back_slow_path
tgt 0x4DB1C0 (0x1eb) ← 唯一调用者 = GenExtGSName ⇒ anon::IsChanged(ExtGSKeyIndex)
连锁推论 尤其好用:GenerateSingleObject ← 什么? 一旦上层的 GenerateSingleObject 定了名,它下面"唯一被调"的小函数常常能顺着链子一次性定死。
5.7 武器⑥ 内联检测(Δ体积法)
同一份源码在不同版本里,内联策略可能不同 ------函数会"消失"。我们用体积增量把它抓出来:
ref ContentListContentStreamGenerate ctor @0x402be0 size 0x97
+ StatesOrOperatorStack::Init @0x402e40 size 0x173
ref BaseContentStreamOptimizer::Compress @0x402570 size 0x4d0
(ref 链路:Compress → ctor → Init)
tgt BaseContentStreamOptimizer::Compress @0x489E00 size 0x5e1 ← 比 ref 大 0x111
tgt 首段直接调用 Init(0x48A780),中间没有 ctor 这一跳
tgt 不存在独立的 CLCSG ctor 函数
Δ = 0x5e1 − 0x4d0 = 0x111 ≈ 被内联的 ctor 的 0x97(+ 少量寄存器分配调整)
结论 :12.1.28 的 CLCSG ctor 已被内联进 Compress 。这个发现顺带纠正了文档上一版的错误标注 ------原先它把 0x48A780(size 0x198,位置猜测、无证据)标成了 CLCSG ctor,实际是 StatesOrOperatorStack::Init。
推广用法:当 ref 有 A→B→C 的链路,而 tgt 只剩 A→C 且 A 的体积"胖"了 Budget(B) 那么多,就优先怀疑 B 被内联,而不要去找一个"不存在的函数"。
5.8 武器⑦ 体积碰撞排雷
见下一节(它太重要,单独展开)。
6. 假阳性与排雷:体积碰撞、内联、陷阱
6.1 体积碰撞实例:两个 0x53
本次会话最值钱的一次"排雷"。
背景 :我们有一批同类的 shared_ptr 操作函数,尺寸高度雷同。当时出现了这样的局面:
ref StatesOrOperatorStack::Pop @0x403160 size 0x50
ref BaseContentStreamOptimizer::set_stack @0x402510 size 0x53 ← 53
tgt 0x48AAF0 size 0x53 ← 也 53!
tgt 0x489DA0 size 0x53 ← 还是 53!
如果只看尺寸 :两个 tgt 候选都可能被判成 set_stack,或者互相张冠李戴。
排雷办法------逐语句同构:
tgt 0x48AAF0 = 状态栈回退:v = *(this+8) → *(v-8) 控制块 → 原子递减归零则
on_zero_shared + __release_weak → this+8 = v-16
tgt 0x489DA0 = 16 字节原位覆写:取 xmm0 入参 → 对 a2[1] 加引用 → *(_OWORD*)this = v2
→ 释放旧 this+8 控制块
语义完全不同:前者是"弹栈",后者是"注入栈"。最终:
tgt 0x48AAF0 = StatesOrOperatorStack::Pop (ref 0x403160, 0x50)
tgt 0x489DA0 = BaseContentStreamOptimizer::set_stack (ref 0x402510, 0x53)
教训 :这份二进制里 0x53 尺寸出现 ≥3 次 (还有
ref StatesOrOperatorStack::Push附近的邻居)。尺寸只能用来"缩小候选集",绝不能用来"终止判断"。凡是候选里出现"尺寸相同的多个",必须逐语句比。
6.2 体积的第二种误用:别把"版本差异"当"不同函数"
tgt 代码普遍比 ref 大 10%~20%(编译器/优化级别差异)。所以:
0x435 vs 0x432、0x6da vs 0x6d0、0x8f2 vs 0x90b都是同一函数;- 不要设"尺寸必须精确相等"的硬门槛,否则会错过大量命中。
我们的实用判定区间 :|Δ| ≤ 0x40 视为"近似";再大就要靠调用关系或语义兜底。
6.3 该说"未定"的时候就别说
上一批有 7 个锚点,我们的第一版结论是全部标"未定、留待下批、勿乱命名"。原因:它们尺寸对不上、名字里不含类名(是匿名/静态辅助函数)。后来用「共同被调 + 被调元素计数」才攻破。
这个纪律很重要 :宁可留 sub_XXXXXX,也不要塞一个错的名字 ------ 错误符号会污染后续所有基于调用图的推断。
6.4 证据分级(我们在文档里坚持的写法)
| 级别 | 含义 | 例 |
|---|---|---|
| 尺寸精确 + 逐语句同构 | 最强 | GetResourcIndex(0xaf=0xaf,行为逐句一致) |
| 调用者/被调位序一致 | 很强 | GenerateSingleObject 一次定 5 项 |
| 被调元素计数一致 | 强 | 5:5 / 7:7 / 1:1 |
| 唯一调用者链 | 强 | 0x4DDCB0 → shared_ptr_emplace ctor |
| 尺寸精确 + 位序 | 中(需交叉核对) | 工厂 vtable 槽位 |
| 高置信推断 | 弱(要标注) | 位置关系推定的匿名辅助函数 |
| 未定 | 不命名 | 只记地址与线索 |
7. 踩过的坑
7.1 环境类
- 不要假设 IDA 的 Python 。我们用
idapyswitch -r -v+ 注册表Python3TargetDLL才确认是 uv 的 3.14.6。装错解释器 = 插件 import 失败,IDA 输出窗只会给一句No module named ...。 - PEP 668 :uv/py 管理的解释器拒绝
pip install,需要--break-system-packages。 - IDA 9.4 属未声明支持版本。官方徽章写 8.5--9.3;我们实测可用(加载器无版本硬校验),但遇到异常优先怀疑这一点。
- 内存 :两个大库(
.i64分别 556MB / 643MB)同开,机器剩余内存一度只有 ~2GB。别同时再开第三个大库。
7.2 工具行为类(这几个坑最费时间)
- 旧插件会让链路"假死" 。我们插件目录里还留着
mcp-plugin.py(旧 ida-pro-mcp)。重启后两个 IDA 进程只监听新插件的 8736 / 7461 ,没有进程监听旧端口,旧 MCP 调用直接WinError 10061 目标计算机积极拒绝。确认新链路可用后应把旧插件移掉。 xrefs_from取不到被调 。该工具在函数地址上只返回自引用条目,看起来像"空结果"。取被调集合要用callees------ 我们因此白跑了一轮。- IDA 拒绝含
<>的函数名 。模板实例化命名直接Rename failed,用下划线替代(如std::__shared_ptr_emplace_CPDF_GraphicStates__ctor)。 - 实例 id 会变 。
3u2h/reiw只对本次 IDA 会话有效,每次开工先list_instances()。
7.3 分析语义类
auto_is_ok()是快照不是闩锁 。queue_empty=true只说明"此刻没排队";可靠信号是functions_added连续为 0。- 改名未落盘会丢 。我们曾发现 IDB 里 38+ 处改名回退成
sub_(0x48E0/0x4EA0/0x4FF0/0x5440/0x4860/0x4DC0等),后来用整表幂等重放 修复,并改用idb_save+ 文件时间戳双重验证。幂等重放这个思路值得记住:命名操作可重复执行,重放一遍比逐个排查便宜得多。
8. 适用边界与一句话评价
适合:需要同时处理多个二进制 / 多版本 / 多架构的逆向工作(恶意样本 dropper-payload-C2、跨版本 patch diff、多模块联动分析),并且你已经在用 MCP 驱动的 AI 客户端。
不适合 / 要留意:
- 只监听 loopback、只支持 stdio ------ 没有远程/容器化接入;
- 并行是跨实例的,同一实例内仍串行(IDA 主线程单线程);
- 取消动作不会透传到 IDA,长调用靠工具超时兜底;
- 无头
idalib需要 IDA Pro 授权(Home/Free 没有); - 它自带的 BCSD 相似度是"指令级模糊哈希",若你有"不用哈希指纹"的方法论约束,只取路由/落盘/门控能力即可。
一句话 :它把"多实例逆向"从手工切窗口 变成了可编程的路由 。对我们这种一次会话上百次跨库交叉查询的任务,它是效率上的分水岭;而 idb_save 这类小工具解决的是"改了白改"的老毛病。
再一句话(逆向侧) :跨版本符号定位的胜负手,不在于"找到相似的函数",而在于把证据按强度分层 ------尺寸与位序负责缩小范围,调用关系与逐语句同构负责拍板,遇到冲突就老实标"未定"。我们在这次会话里靠这套分层,把 PageContentGenerator 整个类族从"完全未命名"推到收口,且没有留下一个错名字。
9. 回滚
powershell
# 1) 移除 IDA 插件
Remove-Item "$env:APPDATA\Hex-Rays\IDA Pro\plugins\ida_multi_mcp.py"
# 2) 卸载包(对安装过它的每个 Python 各执行一次)
& $py -m pip uninstall -y ida-multi-mcp
# 3) 从客户端 MCP 配置里删掉 ida-multi-mcp 条目
# (官方一键:ida-multi-mcp --uninstall,但它会一并改客户端配置)
环境:Windows | IDA Pro 9.4(Professional)| Python 3.14.6(IDA 侧,uv 管理)| 客户端:Trae CN
项目:https://github.com/MeroZemory/ida-multi-mcp(MIT)| 基础工具实现来自 https://github.com/mrexodia/ida-pro-mcp
本文所有地址、尺寸、耗时与体积数字均取自本次真实会话的 IDB 与终端输出。