FirmVulnFlow:AI 驱动的 IoT 固件漏洞挖掘管线
0x00 背景
IoT 固件安全研究是一个高度重复的流程:下载固件、binwalk 解包、找到关键二进制、strings 看一眼、IDA 打开逆向、发现漏洞点、QEMU 模拟验证、写报告。每个步骤都有大量的命令行操作和人工判断。
这些步骤虽然流程化,但细节极多。FirmVulnFlow 的设计目标是:把整个固件漏洞研究流程封装成 MCP 工具,让 AI Agent 能自主完成从固件下载到 PoC 验证的全流程。
替代研究员做的那些重复性操作——解包、字符串提取、xref 分析、QEMU 配置、NVRAM 编译部署这些。研究员只需要在关键决策点介入:选择分析目标、确认 IDA 反编译结果、判断漏洞是否可利用。
0x01 架构总览
整个系统基于 MCP(Model Context Protocol) 构建。
项目结构:
FirmVulnFlow/
├── src/firmvulnflow/
│ ├── common/ # 共享配置、数据库、日志
│ ├── firmware_manager/ # MCP Server: 固件下载与管理
│ ├── firmware_extractor/ # MCP Server: 固件解包与分析
│ ├── vuln_database/ # MCP Server: CVE/PoC 查询
│ ├── binary_diff/ # MCP Server: 二进制比对
│ └── firmware_emulator/ # MCP Server: QEMU 全系统模拟
├── .claude/commands/ # Skill 文件(工作流编排)
│ ├── vuln-discovery.md
│ ├── emulate-firmware.md
│ ├── cross-model-vuln-hunt.md
│ └── patch-bypass-analysis.md
├── data/
│ ├── firmwares/ # 下载的固件文件
│ └── extracted/ # 解包后的文件系统
└── tools/ # 辅助工具、sysroot 缓存
核心设计:5 个 MCP Server 提供 34 个原子工具,4 个 Skill 文件编排完整工作流。
MCP Server 负责”怎么做”——每个工具做一件具体的事。Skill 文件负责定义完整的多步骤研究流程,指导 Agent 按什么顺序调用哪些工具、如何处理异常、何时请求人工介入。
0x02 五个 MCP Server
firmware-manager:固件生命周期管理
4 个工具。处理固件的下载、注册、搜索和版本比对。
# .mcp.json 配置
"firmware-manager": {
"command": ".venv/bin/python",
"args": ["-m", "firmvulnflow.firmware_manager.server"]
}
| 工具 | 用途 |
|---|---|
download_firmware | 下载固件文件,注册到本地 SQLite manifest |
list_local_firmwares | 列出所有已下载的固件 |
search_firmware | 按品牌/关键词搜索本地 manifest |
diff_firmware_versions | 同型号不同版本的文件级 diff |
数据模型很简单——一个 SQLite 表存 model、version、brand、firmware_path、extracted_path。所有路径遵循统一约定:
data/firmwares/{model}_{version}/ # 原始固件
data/extracted/{brand}/{model}_{version}/ # 解包产物
├── rootfs/ # 文件系统根
└── kernel/ # 内核镜像
路径标准化看起来不起眼,但在整个流程中至关重要——Agent 不需要猜路径,每个工具都能通过 model+version 自动定位文件。
firmware-extractor:解包与静态分析
5 个工具。封装 binwalk 做递归解包,提供 ELF 分析能力。
| 工具 | 用途 |
|---|---|
extract_firmware | 端到端解包:支持多层嵌套(zip→.web→.bin→squashfs),自动定位文件系统根并复制到标准路径 |
list_binaries | 列出所有 ELF 可执行文件,含架构信息 |
list_shared_libraries | 列出所有 .so 共享库 |
find_cgi_handlers | 定位 CGI 处理程序(cstecgi.cgi、apply.cgi 等) |
extract_strings | 提取二进制中的可打印字符串 |
extract_firmware 是最复杂的一个。很多路由器固件的打包格式层层嵌套——外层 zip,里面一个 .web 容器,再里面才是 squashfs 镜像。binwalk 递归解包后产出的目录名也五花八门(squashfs-root、jffs2-root、cpio-root……)。这个工具自动处理这些差异,输出统一的 rootfs/ 目录。
vuln-database:漏洞情报
4 个工具。对接 NVD API 和 GitHub,提供 CVE 查询和 PoC 获取。
| 工具 | 用途 |
|---|---|
search_cve | 通过 CPE 或关键词搜索 CVE,本地缓存 |
get_cve_detail | 获取 CVE 完整详情、CVSS 评分、分类引用 |
fetch_poc | 从 GitHub 仓库和 exploit-db 聚合 PoC 代码 |
list_known_vuln_patterns | 列出厂商的常见漏洞模式 |
fetch_poc 不只是返回链接——它会直接读取 GitHub 仓库的 README 和脚本内容、解析 exploit-db 页面的代码。Agent 拿到的是可以直接参考的完整 PoC 源码,而不是一个需要手动打开的 URL。
search_cve 支持两种模式(CPE 和关键词),NVD 的 CPE 条目经常不全或命名不一致,只用 CPE 搜索会漏掉大量已知 CVE。两种模式并行搜索、结果合并,才能接近完整覆盖。
binary-diff:二进制比对
4 个工具。基于 radare2 做函数级和指令级 diff。
| 工具 | 用途 |
|---|---|
diff_binaries | 两个 ELF 的函数级对比(函数列表、大小差异) |
diff_functions | 单个函数的指令级 diff |
identify_patched_functions | 识别两个版本之间被修补的函数 |
find_vuln_handlers | 找到调用危险函数(system、popen)的所有 caller,附带引用字符串 |
find_vuln_handlers 是漏洞挖掘中最常用的工具。它做的事情很直接:用 radare2 的交叉引用找到所有调用 system()、popen() 的函数,然后提取每个 caller 引用的字符串。
使用 radare2 只能做精度比较低的识别,后续的详细分析需要借助IDA Pro MCP。
firmware-emulator:QEMU 全系统模拟
17 个工具,是整个项目最复杂的部分。通过 SSH 在远程 Linux 服务器上管理 QEMU 全系统模拟。
"firmware-emulator": {
"command": ".venv/bin/python",
"args": ["-m", "firmvulnflow.firmware_emulator.server"],
"env": {"SUSER": "", "SERVER": ""}
}
工具按功能分四组:
生命周期管理:
| 工具 | 用途 |
|---|---|
init_workspace | 初始化远程服务器目录结构 |
start_emulation | 启动 QEMU,创建 tmux session |
stop_emulation | 停止模拟 |
check_emulation_status | 检查 session 状态 |
list_emulation_sessions | 列出所有活跃 session |
wait_for_boot | 轮询等待 guest 启动完成 |
Guest 交互:
| 工具 | 用途 |
|---|---|
send_console_command | 向 QEMU 控制台发送命令 |
capture_console_output | 捕获控制台输出 |
clear_console | 清空 tmux 缓冲区 |
exec_in_chroot | 在固件 chroot 环境内执行命令 |
Rootfs 注入:
| 工具 | 用途 |
|---|---|
transfer_rootfs | 打包 rootfs 为 tarball,启动临时 HTTP 服务 |
inject_firmware_rootfs | Guest 内下载、解压、挂载 /dev /proc /sys 等 |
环境配置:
| 工具 | 用途 |
|---|---|
analyze_init_scripts | 解析 rcS/inittab,确定 web 服务类型 |
check_nvram_dependencies | 扫描二进制的 NVRAM 符号依赖 |
detect_nvram_config | 检测架构、libc、NVRAM API 类型 |
setup_nvram_emulation | 全自动 NVRAM 模拟:检测→编译→部署→验证 |
auto_setup_web_env | 一键配置 web 运行环境 |
每个固件型号对应一个独立的 tmux session。用 user-mode 网络做端口转发(host:8080 → guest:80),-snapshot 模式保护磁盘镜像不被修改。
0x03 四个 Skill 工作流
MCP 工具是砖块,Skill 是蓝图。每个 Skill 是一个详细的多步骤工作流程文档(Markdown),通过 Claude 的 /command 机制加载,指导 Agent 完成一个完整的研究任务。
/vuln-discovery — 独立漏洞挖掘
从零开始对一个固件进行系统性漏洞挖掘。7 步:
解包固件 → 攻击面识别 → radare2 xref 初筛(候选列表)
→ IDA 反编译验证(确认/证伪) → CVE 去重 → QEMU 模拟验证 → 报告
四层递进验证:
- radare2 自动扫描——产出候选列表
- IDA Pro 反编译——确认或证伪每个候选的数据流
- CVE 去重——确保是新漏洞
- QEMU 动态验证——运行时证据
/emulate-firmware — 全系统模拟
把一个固件跑起来,让 web 服务可访问。9 步:
初始化工作区 → 查询固件 → 解包 → 分析启动配置
→ 启动 QEMU → 等待启动登录 → 传输 rootfs
→ NVRAM 配置 + 启动服务 → 验证可访问性
这个 Skill 最难的部分是 Step 7(NVRAM 模拟),后面单独展开讲。
/cross-model-vuln-hunt — 跨型号漏洞传播
已知某型号有漏洞,检查同品牌其他型号是否也有。7 步:
漏洞情报收集 → 获取多型号固件 → 解包
→ 分析已知漏洞特征 → 跨型号比对 → 模拟验证 → 报告
同一厂家的不同产品可能用的同一套底层库,所以跨型号漏洞传播还挺常见的
/patch-bypass-analysis — 补丁绕过分析
分析厂商的漏洞修复是否可以绕过。8 步:
漏洞情报 → 获取新旧版本固件 → 解包 → radare2 diff 定位变化
→ IDA 逐函数对比新旧伪代码 → 绕过可行性评估 → QEMU 验证 → 报告
有些漏洞修复的比较敷衍,比如只加一部分黑名单、只修一个参数、只修一个入口。
0x04 模拟系统设计
总体架构
固件模拟的核心思路是用一个通用的 Debian MIPSEL/ARM 虚拟机当壳,把目标固件的文件系统塞进去,在 chroot 里跑固件的服务进程。
不是模拟整块路由器硬件,只模拟到能让 web 服务跑起来接收请求的程度。
┌─ macOS Host ─────────────────────────────────────────────┐
│ │
│ Claude Code + MCP Servers │
│ │ │
│ │ SSH │
│ ▼ │
│ ┌─ Linux Server ──────────────────────────────────────┐ │
│ │ │ │
│ │ tmux session: "A950RG" │ │
│ │ ┌─ QEMU (qemu-system-mipsel) ───────────────────┐ │ │
│ │ │ │ │ │
│ │ │ Debian MIPSEL Guest (malta) │ │ │
│ │ │ ┌─ chroot /rootfs ─────────────────────────┐ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ 固件文件系统 │ │ │ │
│ │ │ │ ├─ /usr/sbin/httpd │ │ │ │
│ │ │ │ ├─ /usr/sbin/cstecgi.cgi │ │ │ │
│ │ │ │ ├─ /lib/libapmib.so ← NVRAM hook 替换 │ │ │ │
│ │ │ │ └─ /tmp/nvram/ ← 文件 KV 存储 │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ mount: /dev /proc /sys /tmp │ │ │ │
│ │ │ └──────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ user-mode network: host:8080 → guest:80 │ │ │
│ │ │ -snapshot: 磁盘镜像只读 │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
整个流程分几步:
-
启动 QEMU:在远程 Linux 服务器上创建 tmux session,用 Debian MIPSEL 内核 + 磁盘镜像启动一个完整的 Linux guest。
-snapshot模式保护磁盘镜像不被修改,每次重启都是干净状态。 -
注入 rootfs:把固件文件系统打包成 tarball,通过 QEMU user-mode 网络的 HTTP 传输到 guest 内部,解压到
/rootfs。然后 mount bind/dev、/proc、/sys,挂载 tmpfs 到/tmp和/var/run——构造一个最小可用的 chroot 环境。 -
处理硬件依赖:chroot 环境虽然有了文件系统,但固件二进制依赖的硬件接口全都不存在。最关键的是 NVRAM——几乎所有路由器服务启动时都会读取 NVRAM 配置。需要编译一个 hook 库来模拟 NVRAM 读写。
-
启动服务:在 chroot 内启动 web 服务,通过端口转发从外部访问。
同一个 Debian 镜像可以跑所有 MIPSEL 架构的固件,不需要为每个型号适配不同的虚拟机。代价是模拟精度不高,guest 内核是标准 Debian 而非厂商定制内核,硬件外设全部缺失。但对安全研究来说够用了,能验证漏洞。
接下来讲几个落地时最难处理的部分。
NVRAM Hook
IoT 固件模拟的最大障碍不是 QEMU 本身,而是固件对硬件的依赖。路由器固件几乎都依赖 NVRAM(非易失性 RAM)存储配置。没有 NVRAM 支持,web 服务启动就会 crash。
多种实现
不同厂商用不同的配置存储 API,部分例子:
| API 类型 | 厂商 | 芯片平台 | 特点 |
|---|---|---|---|
nvram_get/set | Broadcom 系 | BCM47xx/53xx | 字符串 KV,最常见 |
apmib_get/set | Realtek 系 | RTL8196E/8197D | 数字 MIB ID,值可以是整数或字符串 |
cfm_get/set | Broadcom DSL 系 | — | 本质是 nvram 的别名 |
cdb_get/set | Realtek RTL9600 系 | — | 本质是 nvram 的别名 |
detect_nvram_config 会自动检测固件用的是哪种 API,setup_nvram_emulation 生成对应的 hook 库。
拦截策略
根据固件的 libc 类型选择不同策略:
LD_PRELOAD 策略:
编译一个 .so,在启动服务时用 LD_PRELOAD 注入,拦截 nvram_get/nvram_set 等符号。优点是不修改原始文件系统。
LD_PRELOAD=/tmp/libnvram_hook.so /usr/sbin/httpd
Library Replacement 策略:
当 LD_PRELOAD 不可靠时,直接替换导出 NVRAM 符号的原始 .so。备份原库 → 编译替代库 → 覆盖原库位置 → 替代库内部用 dlopen 加载备份库转发非 NVRAM 符号。
// library_replacement 模板中的转发逻辑
static void *_orig_lib = NULL;
static void _load_orig(void) {
if (!_orig_lib)
_orig_lib = dlopen("/lib/libapmib.so.bak", RTLD_LAZY);
}
// 非 NVRAM 符号通过 dlsym 转发到原库
交叉编译的两个维度
编译 NVRAM hook 库需要同时匹配两个维度:
架构 (architecture) × libc 实现 (C runtime)
mipsel glibc
mips uClibc
arm musl
aarch64 ...
这两个维度相互独立。mipsel + glibc 的 .so 放到 mipsel + uClibc 的固件里,动态链接器会静默拒绝加载——不报错,服务直接 crash。
detect_nvram_config 同时检测这两个维度:通过 ELF header 确定架构,通过动态链接器字符串确定 libc 类型和版本。
构建级联
build_nvram_library 按优先级尝试编译:
Priority 1: tools/sysroots/{arch}-{libc}/ 缓存有匹配的 sysroot
→ 用系统交叉编译器 + --sysroot 编译 LD_PRELOAD 模板
Priority 2: glibc 固件
→ 系统交叉编译器直接编译(自带 sysroot)
Priority 3: 返回 need_libc_source
→ 附带说明:交叉编译器已有,缺 libc 的 sysroot
→ 三个选项:下载预编译 sysroot / 编译 libc 源码 / -nostdlib fallback
Priority 3 不会静默降级,Agent 会拿到 need_libc_source 状态,然后询问用户如何获取匹配的 libc源码。这是刻意的设计:让人来做这个需要判断的决策,而不是让程序猜。
Library Replacement 的测试与回滚
替换原库是个危险操作——如果替代库 ABI 不兼容,整个 chroot 环境就废了。所以部署时有完整的安全网:
# 部署流程
1. 备份原库:cp libapmib.so libapmib.so.bak
2. 替换:cp hook.so libapmib.so
3. 烟雾测试:chroot /rootfs sh -c "ls /tmp"
4. 测试失败 → 自动回滚:cp libapmib.so.bak libapmib.so
烟雾测试的逻辑很简单——跑一个依赖 libc 的命令(ls),如果 chroot 环境崩了(SEGV、ABRT、链接器报错),说明替代库有问题,立刻回滚。
数据符号
NVRAM 相关的共享库不只导出函数,还导出全局变量。比如 Realtek 的 libapmib.so 导出 pMib(指向 MIB 数据库的指针)、mib_table(MIB 配置表)、wlan_idx(无线网卡索引)等。
如果替代库没有导出这些数据符号,依赖它们的二进制在运行时会因为符号解析失败而 crash。
detect_nvram_config 用 readelf --dyn-syms 采集 OBJECT 类型的符号(不只是 nm -D 的函数符号),然后 _generate_data_stubs 自动生成 C 声明:
// 自动生成的数据符号桩
static char _pMib_buffer[262144]; // 256KB 静态缓冲区
void *pMib = _pMib_buffer; // 指针指向缓冲区,避免 NULL 解引用
int wlan_idx = 0; // 整数符号,零初始化
int mib_table[4] = {0}; // 数组符号,零初始化
指针类型分配足够大的静态缓冲区(pMib 这种通常指向几百 KB 的配置数据库),整数和数组零初始化。这些桩不提供真实功能,但足够让依赖这些符号的二进制正常启动——而对于安全研究来说,只要 web 服务能跑起来接收请求就够了。
零 libc 依赖的模板
TEMPLATE_LIBRARY_REPLACEMENT 是一个完全不依赖 libc 的 C 模板。文件 I/O 全部用内联汇编做 raw syscall:
// MIPSEL 架构的 raw syscall 内联汇编
static inline long _raw_syscall3(long n, long a, long b, long c) {
register long r4 __asm__("$4") = a;
register long r5 __asm__("$5") = b;
register long r6 __asm__("$6") = c;
register long r2 __asm__("$2") = n;
__asm__ volatile("syscall" : "+r"(r2) : "r"(r4),"r"(r5),"r"(r6)
: "$7","$8","$9","$10","$11","$12","$13","$14","$15",
"$24","$25","memory");
return r2;
}
为什么这么做?因为 library_replacement 策略是替换原库——如果替代库本身又依赖 libc,而原库也是 libc 链接的一部分,就会形成循环依赖。用 raw syscall 的字符串比较、文件读写完全绕开 libc。
NVRAM 值存储在 /tmp/nvram/<key> 文件中,每个 key 一个文件。nvram_get 先查内存缓存(64 slot 的 key-value 数组),miss 时读文件。nvram_set 写文件 + 更新缓存。
不同的 NVRAM API 类型有不同的处理:
// nvram_get/set — 字符串 KV
char *nvram_get(const char *key) {
// 查缓存 → miss 则读 /tmp/nvram/{key}
}
// apmib_get/set — 数字 MIB ID,第三个参数是目标缓冲区
int apmib_get(int id, void *value, int len) {
// 读 /tmp/apmib/{id},memcpy 到 value
}
/proc 下的特殊文件依赖绕过
NVRAM 不是唯一的硬件依赖。很多路由器固件的守护进程在启动时会读取 /proc 下的硬件特有文件——这些文件由厂商内核模块注册,QEMU 的通用 Debian 内核里根本不存在。
例如某路由器 的 sysconf 在执行 init gw wan(初始化 WAN 网关)时会访问:
/proc/rtl_dnstrap— Realtek DNS trap 控制/proc/br_igmpProxy— IGMP 代理配置/proc/fast_pppoe— PPPoE 快速路径
这三个都是 Realtek RTL8196E 内核模块注册的 procfs 接口。QEMU guest 里没有这些文件,sysconf 直接 SIGSEGV(RC=139)。
inject_firmware_rootfs 在 chroot 里挂载的是 QEMU guest 的真实 /proc:
f"mount -t proc /proc /{rootfs_basename}/proc"
这个 /proc 来自 Debian MIPSEL 内核,只有标准 Linux proc 条目,没有任何 Realtek 专有的硬件接口。
AI Agent 发现某个二进制因为缺失 /proc 文件而崩溃时,会自行调用 IDA Pro MCP进行判断,并进行 patch 尝试。
NVRAM API 碎片化、/proc 下的文件每个厂商完全不同、libc 不兼容导致 .so 静默加载失败——这些问题没有通用的自动化方案,但 AI Agent 可以看到报错信息(SIGSEGV、dlopen 失败、符号找不到),结合 IDA 反编译理解缺了什么,然后决定是创建 fake 文件、patch 二进制、还是绕过这个组件走其他验证路径。传统的固件模拟框架碰到这类问题就直接失败。而 AI Agent 的优势在于它能阅读错误理解上下文并做出判断——这恰恰是硬件碎片化场景下最需要的能力。
0x05 工具之外的智能
在真实的固件模拟过程中,大量问题是没有标准答案的。把一个固件的 web 服务完整跑起来,中间会碰到各种意想不到的阻塞,每一个都需要判断:这个问题值不值得修?怎么修?还是绕过去?这些判断全部由 AI Agent完成。 模型的基础推理能力是整个系统能正确运行的关键前提,工具只是手脚,AI Agent 才是大脑。
理解完整的服务启动链
例如某路由器的 web 服务不是简单的 httpd + cstecgi.cgi,而是一个基于 MQTT 消息分发的 CSTE 框架:
lighttpd → cstecgi.cgi → mosquitto_pub(topic)
↓
cs_broker (mosquitto 1.4.8) → 消息路由
↓
cste_sub → dlopen("cste_modules/*.so") → handler 函数
↓
sysconf → 实际执行配置变更
这个架构完全没有文档。模型需要通过 IDA 反编译一步步逆向出来:cstecgi.cgi 的 web_getData() 发布到什么 topic、cste_sub 订阅什么 topic、load_modules() 怎么加载 handler、get_action() 怎么从 topic 里提取函数名。
整个过程开了 8 个 IDA session,在多个二进制之间交叉分析才理清了这条链路,模型必须自己读懂反编译代码,理解组件间的依赖关系,然后决定启动顺序。
模拟中的取舍
模拟过程中碰到的障碍,不是每个都值得花时间解决。模型需要对每个阻塞点做成本收益判断:
值得修的例子——MQTT topic 不匹配:
cste_sub 默认只订阅 "cste" topic,但 cstecgi.cgi 发布到的是 "totolink/router/{topicurl}"。消息根本到不了 handler。
模型通过 IDA 分析 web_getData() 发现了正确的 topic 格式,fix 很简单——启动 cste_sub 时加 -t "totolink/router/#" 参数。成本低,收益高(不修就完全不通),必须修。
不值得修、直接用 MQTT 替代 HTTP 的例子——lighttpd + cstecgi.cgi 联调:
让 lighttpd 正确路由请求到 cstecgi.cgi,涉及配置文件调整、CGI 环境变量设置、模块加载等一系列问题。模型判断:漏洞验证不一定要走 HTTP。 CSTE 架构的核心是 MQTT 消息分发——直接用 mosquitto_pub 发消息到对应 topic,效果等价于通过 HTTP + cstecgi.cgi 的完整链路:
mosquitto_pub -h 127.0.0.1 -t "totolink/router/setting/setDiagnosisCfg" \
-m '{"topicurl":"setting/setDiagnosisCfg",
"ipDoamin":"127.0.0.1 -w 1\necho PWNED > /tmp/diag_rce\n#"}'
这个决策省了大量的 lighttpd 调试时间,而且 MQTT 层的验证同样可以证明漏洞存在。
工具与模型
MCP 工具提供的是可靠的原子操作:解包不会出错、radare2 xref 不会漏、NVRAM 编译部署流程是确定性的。
模型提供的是不确定环境中的判断力:理解未知架构、决定修什么绕什么、在多种验证方式中选择最高效的、从错误中修正。
两者缺一不可。只有工具没有模型,面对 非标准架构束手无策。只有模型没有工具,每次都要手动敲 binwalk、radare2、qemu 命令,效率极低。
Skill 文档的不是脚本,而是经验。 它不是告诉模型机械地执行 step 1→2→3,而是告诉模型:这类任务通常怎么做、常见的坑是什么、什么时候需要人工介入。模型在执行过程中有充分的自由度来调整策略——跳过某些步骤、切换验证方式、甚至推翻自己之前的结论。
0x06 数据流管线
一次完整的漏洞挖掘,数据流是这样的:
[firmware-manager] 下载固件
↓
[firmware-extractor] binwalk 解包 → rootfs/
↓
[firmware-extractor] find_cgi_handlers → 定位关键二进制
[firmware-extractor] extract_strings → 提取字符串特征
↓
[binary-diff] find_vuln_handlers → 危险函数 xref 初筛
↓ 候选列表
[IDA Pro MCP] decompile/xrefs_to → 反编译验证每个候选
↓ 确认/证伪
[vuln-database] search_cve + fetch_poc → CVE 去重
↓ 新漏洞
[firmware-emulator] QEMU 模拟 → 动态验证
↓
输出:结构化漏洞报告 + PoC
每个工具只做一件事,Skill 文件把它们串起来。工具之间通过标准化路径(data/extracted/{brand}/{model}_{version}/rootfs/)和数据库(SQLite manifest)共享状态。
0x07 IDA Pro MCP 集成
单纯依赖 radare2 做静态分析是不够的。IDA Pro 通过 MCP 集成,提供反编译和深度分析能力。
在 Skill 工作流中,IDA 的角色是验证者而不是发现者:
- radare2 的
find_vuln_handlers找到候选 idalib_open加载目标二进制(支持同时打开多个文件,用idalib_switch切换)decompile获取伪代码,逐一回答 radare2 无法回答的问题- 对每个候选给出 CONFIRMED / DISPROVED / WEAKENED / NEEDS_DYNAMIC 结论
idalib_close关闭 session
这个二级验证机制在实践中非常有效。某路由器的研究中,radare2 初筛出了 20+ 个 system() caller,IDA 验证后只有 5 个真正可利用——其他的要么实际走 execve(不经 shell,无法注入),要么输入经过 gethostbyname → inet_ntoa 转换后已经不可控。
0x08 当前局限性
无法识别内存破坏漏洞
当前的分析能力集中在逻辑漏洞——命令注入、认证绕过、信息泄露这些。对于缓冲区溢出、堆溢出、格式化字符串漏洞等内存破坏类漏洞,目前的工具链力不从心。
原因很简单:
- radare2 xref 只能找到
strcpy/sprintf的 caller,但判断是否存在溢出需要理解缓冲区大小和输入长度的关系——这需要更精细的数据流分析 - IDA 伪代码可以看到栈变量的大小,但自动化判断”这个 sprintf 是否能溢出这个 128 字节的缓冲区”仍然困难
- 最关键的是:即使静态分析认为可能溢出,没有运行时验证就不能确认——stack canary、ASLR、编译选项都会影响可利用性,需要用调试工具进行更详细的调试
find_vuln_handlers 目前对 sprintf/strcpy/strcat 的分析精度远低于对 system()/popen() 的分析。后者只需要确认”用户输入到达了 system 参数”,前者还需要额外计算缓冲区边界。
经验积累不足
目前实际完整跑通的固件样本太少。Skill 文档中的经验主要来自几个特定品牌的型号,覆盖的芯片平台只有 Broadcom 和 Realtek,架构只有 MIPSEL。这意味着模型在面对新厂商、新平台的固件时,缺乏足够的先验知识来指导 AI Agent 高效决策。直接后果是大量 token 消耗在重复试错上——每次模拟失败都要重新分析报错、调整编译参数、重新部署测试。比如碰到非标准的 web 服务架构(不是 httpd/lighttpd + CGI 的常见模式)时,模型需要花费大量上下文窗口通过 IDA 逆向来理解组件间的依赖关系,才能找到正确的启动方式。
0x09 后续计划:GDB Server 集成
下一步是集成 gdbserver 到 QEMU 模拟环境中,补上内存破坏漏洞的验证能力。
计划的架构:
[QEMU Guest] [Host]
┌──────────────────┐ ┌──────────────────┐
│ chroot /rootfs │ │ │
│ ├─ gdbserver │←─ SSH ──→│ GDB MCP Server │
│ │ :1234 │ tunnel │ (新 MCP) │
│ └─ target_bin │ │ │
└──────────────────┘ └──────────────────┘
有了 gdbserver 支持,验证流程可以扩展为:
radare2 初筛 sprintf/strcpy caller
→ IDA 确认缓冲区大小 vs 输入长度
→ GDB 设断点在 sprintf 处
→ 发送超长输入
→ GDB 检查是否覆盖了返回地址 / canary
→ 确认可利用性
另外还考虑加入 strace 的系统化集成——目前在 Skill 文档中只是作为辅助手段提及,后续可以作为 MCP 工具正式封装,用于二阶注入的数据流追踪。
0x0A 总结
FirmVulnFlow 的核心思路是:把 IoT 安全研究中的重复性操作封装成 MCP 工具,让 AI Agent 自主编排完成全流程,人只在关键决策点介入。
但工具只是一半。另一半是模型本身的推理能力——理解未知固件架构、判断执行阻塞的修复策略、在多种验证方式中选择最高效的路径、从自身的判断错误中修正。工具提供可靠的原子操作,模型提供不确定环境中的决策力,两者结合才是完整的能力。
5 个 MCP Server 覆盖从固件获取到动态验证的完整链路。4 个 Skill 工作流把 34 个原子工具组合成可执行的研究方案。NVRAM 模拟子系统解决了固件模拟中最棘手的硬件依赖问题。Skill 文档沉淀了实战经验,让模型在面对新固件时不需要从零摸索。
目前对逻辑漏洞(命令注入、认证绕过)的发现效率已经很高。下一步通过 gdbserver 集成补齐内存破坏漏洞的能力,让整个系统覆盖更完整的漏洞类型。