Skip to main content
Glama

WinDbg / cdb 会话式调试 MCP

一个把系统自带的 cdb.exe(Debugging Tools for Windows 里的命令行调试器)暴露为 MCP 工具的服务端。零三方调试库——不依赖任何 Python 调试绑定,直接驱动你装 WinDbg 时就已经有的 cdb。

设计思想

调试本质上是一个持久的、有状态的交互会话,不是一堆孤立命令。因此本 MCP 采用 token_key 会话生命周期架构:

   open(target)      ──►  token_key        打开 dump / 启动 exe / attach 进程 / 连内核
   run(token_key, 命令)  ──►  output        在会话里执行任意 cdb 命令(上下文跨命令保留)
   interrupt(token_key)                   中断当前命令(阻塞的 g)
   close(token_key)                       关闭会话
   sessions()                             列出活跃会话

run 下的是真正的 cdb/WinDbg 命令!analyzekvr~Ns.frame Ndvbpgp!heap!process ……),会话上下文(符号 / 当前线程 / 栈帧 / 断点 / 当前进程)在同一个 token_key 内跨命令保留——和坐在 cdb 前操作完全一致。WinDbg/cdb 的全部命令都可直接用,远比预先封装的几十个结构化工具完整。

前置

  • Windows

  • Debugging Tools for Windows(提供 cdb.exe;装 WinDbg 时一并就有)。本服务会自动在 常见位置(Windows Kits、Store 版 WinDbg、PATH)查找;找不到时设环境变量 WINDBGMCP_CDB 指向 cdb.exe 全路径。

  • Python 3.10+

  • 符号路径(强烈建议):设环境变量 _NT_SYMBOL_PATH=srv*C:\Symbols*https://msdl.microsoft.com/download/symbols, cdb 子进程会继承,否则符号解析多半失败。

安装

cd windbg_mcp
pip install -r requirements.txt      # 只装 mcp SDK

接入客户端

启动方式统一:python <本目录>/server.py

Claude Code

claude mcp add windbg-mcp -- py -3.13 C:\path\to\windbg_mcp\server.py

手写 .mcp.json

{
  "mcpServers": {
    "windbg-mcp": {
      "command": "py",
      "args": ["-3.13", "C:/path/to/windbg_mcp/server.py"],
      "env": {
        "_NT_SYMBOL_PATH": "srv*C:\\Symbols*https://msdl.microsoft.com/download/symbols"
      }
    }
  }
}

改完配置重启客户端并批准 windbg-mcp

工具总览(44 个)

分两层:会话生命周期(7) + 基础命令封装(37)。封装是便捷,run 是统一原始接口。

会话生命周期(7)

工具

作用

open(target, args?, kind?, initial_commands?)

打开会话 → token_key

run(token_key, command, timeout_ms?)

统一原始接口:执行任意 cdb 命令

interrupt(token_key)

中断当前命令(阻塞的 g 等)

close(token_key)

关闭会话(终止被调试进程)

detach(token_key)

分离会话(被调试进程继续运行,cdb qd)

sessions()

列出活跃会话(自动清理死会话,含 kind/target)

list_dumps(directory, pattern?, limit?)

列出目录下转储文件(不需会话)

openkinddump / launch / attach / kernel / remote / auto(默认,按 target 形态判断)。

基础命令封装(37)

封装内部都走会话的 run,参数结构化、不用记 cdb 语法。没有封装的命令仍用 run 直接发。

分组

工具(对应 cdb 命令)

执行

go(g) · step(into?)(p/t) · step_out(gu) · goto(expr)(g expr) · trace(count)(t N) · analyze(!analyze -v)

寄存器/内存

regs(r) · set_reg(r name=val) · read_mem(db) · write_mem(eb) · read_str(da/du) · read_ptr(dps) · poi(dps L1) · disasm(u $ip) · mem_info(!vprot) · mem_list(!address)

符号/模块

resolve(?) · find_symbols(x) · addr_to_symbol(ln) · modules(lm) · module_info(lm vm) · get_exports(x mod!*)

栈/线程/帧/局部

stack(kv) · threads(~) · select_thread(~Ns) · frame(.frame N) · locals(dv) · get_teb(r $teb) · get_peb(r $peb) · get_handles(!handle 0 f)

断点

bp(bp) · hw_bp(ba) · breakpoints(bl) · enable_bp(be) · disable_bp(bd) · remove_bp(bc)

状态捕获

capture_state(手动快照:寄存器 + 调用栈 + 栈顶内存 + 当前 ip 反汇编)

regs / read_mem / resolve 会把输出解析成结构化字段(registers / hex+ascii / addr);其余返回干净文本(提示符已去掉)。

典型工作流

崩溃 dump 分析(交互式,!analyze 只是起点)

1. open("C:/crashes/x.dmp")            -> t
2. analyze(t)                          起点:故障模块 / 异常 / 故障栈
3. threads(t)                          列出所有线程
4. select_thread(t, 3)                 切到故障线程 3
5. stack(t)                            该线程带参数的调用栈
6. frame(t, 2)                         切到第 2 栈帧
7. locals(t)                           当前帧的局部变量
8. regs(t)                             寄存器(结构化 registers)
9. disasm(t, count=8)                  反汇编崩溃点附近
10. run(t, "!heap -stat")              没封装的命令,走统一 run 接口
11. read_mem(t, "@rsp", 64)            读栈内存(结构化 hex/ascii)
12. close(t)

第 4 步切线程、第 6 步切栈帧——后续 stack/locals/regs 都作用在切完后的状态上,这才是真实调试。封装之外的命令(如 !heap)随时用 run 兜底。

活进程调试 / 漏洞利用验证

1. open("C:/target/vuln.exe", args="input.bin")   -> t
2. bp(t, "vuln!processInput+0x2A")
3. go(t)                            继续;命中断点返回,或超时则 interrupt(t)
4. regs(t)                          看 rip(registers.rip=0x4141...=可控)
5. stack(t)                         看栈
6. step(t)                          单步越过
7. close(t)

attach 到运行中的进程

1. open("1234", kind="attach")        -> t
2. run(t, "~"); run(t, "kv"); ...
3. close(t)                           detach 后进程继续运行

内核 / 驱动调试

1. open("net:port=50000,key=1.2.3.4", kind="kernel") -> t
2. run(t, "!process 0 0")
3. run(t, "lm m nt")
4. close(t)

重要注意事项

  • Windows 专属:cdb.exe 仅 Windows 可用。

  • run 的 timeout 与中断gp、等待事件类命令会阻塞到事件发生;到 timeout_ms 仍未返回时结果里 timed_out=true,调用 interrupt(token_key)(发 CTRL+BREAK)中断。

  • 命令完成判定:服务通过识别 cdb 提示符(0:000> / kd> / lkd> 等)判断命令结束。 极少数情况(命令输出含疑似提示符的行)可能提前返回,重发命令即可。

  • cdb 伪寄存器(实测)$ip(当前指令指针)、$teb$peb$exentry 可用;$sp 不存在—— 读栈指针用 regs() 取 esp/rsp,或命令里用 @esp/@rsp(寄存器引用需 @ 前缀)。封装已规避 (disasm$ipcapture_state 用解析出的栈指针)。

  • mem_list(!address):首次会建立内存映射(输出 'Building memory map...' 进度),耗时且输出 可能延迟到下一条命令;建议单独调用或会话开始预热一次。mem_info(!vprot)轻量、无此问题。

  • 编码:cdb 输出默认按系统首选编码解码(中文系统=gbk/cp936);若乱码设 WINDBGMCP_ENCODING=gbkutf-8

  • cdb 路径:设 WINDBGMCP_CDB=<cdb.exe 全路径或其目录> 可强制指定。

  • 日志:写 stderr;WINDBGMCP_LOG_LEVEL=DEBUG 调高 verbosity。

  • 安全(.shell 拦截)run / initial_commands 默认拦截 .shell / .pcan(cdb 可 借此执行系统命令);确需放行设 WINDBGMCP_ALLOW_DANGEROUS=1

  • 会话数上限:默认最多 8 个并发会话(open 超限报错);改 WINDBGMCP_MAX_SESSIONS(0=不限)。 死会话由 sessions() 自动清理,server 退出时 atexit 关闭全部。

  • 并发:同一 token_key 的命令自动串行化(不会交错);不同 token_key 并发。

  • 权限与授权:能 attach 任意进程、写内存、内核调试,权限极大。仅用于安全研究与授权测试。

测试

tests/selftest.py 是自测脚本,验证 cdb 交互机制 + 调试流程(不经 MCP 协议,直接驱动 CdbSession,同步快速)。换机器 / 升级 cdb / 改 server 后跑一遍即可回归。 完整说明见 tests/README.md

windbg_mcp/ 目录下快速跑:

py tests/selftest.py --exe "<被调试程序>" --cdb-path "<cdb.exe 或目录>" --symbol-path "<符号路径>"

末尾 PASS=x WARN=y FAIL=z,FAIL=0 即核心机制可用。

声明

仅供安全研究、学习与授权测试。使用者需自行确保对分析对象拥有合法授权。