
项目链接:https://github.com/kiki1e/binary-mcp (将在毕业答辩之后开源,欢迎师傅们来交流与指导)
BinaryMCP 技术详解:
在二进制安全研究中,大语言模型(LLM)具备较强的代码理解与逻辑分析能力,但由于无法直接与逆向分析环境交互,其在动态验证和实际操作方面存在局限性。
BinaryMCP 项目旨在解决这一问题。通过引入 Model Context Protocol (MCP),项目将 LLM 作为决策中心,同时封装 IDA Pro 和 GDB 等专业工具作为执行模块,使模型能够直接调用底层工具进行分析。
本文将介绍 BinaryMCP 的架构设计及其在静态与动态分析中的具体实现。
1. 设计理念与架构分层
为了适应不同逆向工具的运行环境差异,项目采用了模块化设计,将系统拆分为两个独立的部分:
idamcp(静态分析):定位:专注于控制流图分析、伪代码生成等静态任务。
架构:考虑到 IDA Pro 通常运行在 Windows GUI 环境下,该模块采用了 Client-Server 架构,通过 HTTP 桥接 LLM 与 IDA 内部环境。
pwnmcp(动态分析):定位:专注于运行时状态监控与漏洞利用。
架构:部署于 Linux/WSL 环境,直接封装 GDB 和 pwndbg,负责与系统底层进行交互。
2. 静态分析模块:idamcp 的实现
在开发 idamcp 时,最大的技术挑战在于 IDA Pro 的线程模型。由于 IDA 的大部分 API 是非线程安全的,如果外部 HTTP 请求线程直接调用 API,极易导致宿主程序(IDA)直接崩溃。
2.1 核心难点:线程同步机制
为了解决这一问题,项目在 idamcp/scripts/ida_server.py 中实现了一个基于 BaseHTTPRequestHandler 的轻量级服务器,并引入了 idaapi.execute_sync 装饰器模式来进行线程调度。
代码实现:
# ida_server.py 中的核心同步逻辑
def do_POST(self):
# ... 解析请求 ...
# 1. 定义闭包,封装所有的 IDA API 调用逻辑
def safe_execution():
nonlocal response, status
try:
if path == "/decompile":
response = self.handle_decompile(payload)
# ... 分发其他路由 ...
except Exception as e:
# ... 异常处理 ...
pass
# 2. 关键机制:将 safe_execution 调度到 IDA 主 UI 线程执行
# MFF_READ 标志表示这是一个读取操作,不涉及数据库修改,效率更高
idaapi.execute_sync(safe_execution, idaapi.MFF_READ)
# 3. 返回结果
self._send_response(response, status)
机制解析:该设计保证了无论 HTTP 请求由何种后台线程发起,实际的数据访问操作均被强制串行化至 IDA 的主 UI 线程执行,从而完美解决了插件的稳定性问题。
2.2 数据清洗:伪代码预处理
Hex-Rays 反编译器的原始输出中包含大量用于语法高亮的隐藏控制字符(Color Tags)。这些字符对 LLM 来说不仅是噪音,还会显著增加 Token 消耗并引发模型幻觉。
代码在 handle_decompile 中引入了清洗步骤:
cfunc = idaapi.decompile(f)
sv = cfunc.get_pseudocode()
# 使用 idaapi.tag_remove 彻底移除颜色标签,只保留纯文本代码
code_lines = [idaapi.tag_remove(s.line) for s in sv]
3. 动态分析模块:pwnmcp 的实现
pwnmcp 的核心目标是将交互式的 GDB 命令行操作,抽象为 LLM 可理解、可调用的无状态原子操作。
3.1 策略规划 (StrategyPlanner)
LLM 有时会陷入“不知从何下手”的困境。在 pwnmcp/pwnmcp/strategy/__init__.py 中,模块内置了一个确定性的规则引擎。它不依赖生成式模型的概率,而是基于经典的漏洞利用判据进行硬编码引导。
def _determine_approach(self, protections, dangerous_funcs, suspicions):
# 规则 1: 栈溢出判定 (存在溢出函数 且 未开启 Canary)
has_overflow_funcs = any(f in ['strcpy', 'gets', ...] for f in dangerous_funcs)
if has_overflow_funcs and not protections.get("Canary"):
# 规则 2: Ret2Libc 判定 (存在 system 函数 且 未开启 PIE)
if has_system and not protections.get("PIE"):
return "ret2libc"
# 规则 3: Shellcode 注入判定 (未开启 NX)
elif not protections.get("NX"):
return "shellcode injection"
通过联合分析 checksec 的安全防护状态(NX, PIE, Canary)与导入表中的危险函数,该逻辑能自动输出最符合当前上下文的利用路径(Exploit Path),为 LLM 提供准确的初始引导。
3.2 自动化:一键计算溢出偏移
为了赋予 LLM 高效的“调试”能力,我们将繁琐的人工调试步骤封装为原子函数。以栈溢出偏移计算为例,run_with_gdb_pattern 函数实现了全自动化:
序列生成:自动生成指定长度的 De Bruijn 循环序列(Cyclic Pattern)。
执行注入:通过 GDB Python API 启动目标程序并将序列注入输入流。
异常捕获:监听
SIGSEGV信号,精准捕获崩溃现场。偏移演算:读取崩溃时的 RIP/EIP 寄存器值,反向计算其在原序列中的索引。
这一封装将原本需要数分钟的人工操作(生成->运行->查看->计算)压缩为单次工具调用,大幅提升了分析效率。
4. MCP 协议集成与状态管理
在协议适配层,项目使用 fastmcp 库暴露功能接口。
鉴于 MCP 协议本身是无状态的,而调试任务具有极强的上下文依赖(例如:需要记住上一步分析出的基地址),server.py 中引入了 SessionState 机制。
@mcp.tool()
def analyze_binary(path: str, deep: Optional[bool] = None) -> str:
"""静态分析二进制文件,并保存上下文"""
# 1. 调用 StaticAnalyzer 获取信息
facts = analyzer.analyze_binary(path)
# 2. 持久化事实数据至会话状态
# 这使得后续的策略规划工具可以直接读取本次分析的结果
session_state.save_facts(facts.to_dict())
return _json_ok(facts.to_dict())
通过文件系统对分析事实(Facts)、策略(Strategy)和调试偏移(Offsets)进行持久化存储,系统成功在无状态的 RPC 调用间维持了完整的调试会话上下文。