文章总结: 本文探讨了使用MCP进行Windows内核调试的尝试与挑战。作者基于SvenScharmentke的工作,试图将MCP从用户空间调试扩展到内核调试,但遇到了无法程序化发送CTRL-C信号中断目标系统的障碍。经过多种尝试失败后,作者决定转向使用DbgEngCOM接口作为解决方案,这是专业调试工具采用的方法。文章展示了AI辅助内核调试的潜力和当前限制,为后续研究提供了方向。 综合评分: 78 文章分类: 二进制安全,内核调试,漏洞分析,安全工具,AI安全
使用 MCP 进行 Windows 内核调试:从 Vibe Coding 到 DbgEng COM 接口的探索之路
Alan Sguigna
securitainment
2025年12月19日 13:37 中国香港
在本系列文章的 第 1 部分 中,我展示了使用自然语言处理来分析 Windows 崩溃转储的配置过程。本文将更深入地探讨如何通过”氛围编码” (vibe coding) 扩展 MCP 在 Windows 内核调试中的应用。
本系列 第 1 部分 是在 Sven Scharmentke 工作的基础上发展而来,他撰写了一篇引人入胜的文章 《崩溃分析的未来:AI 遇见 WinDBG》。他的 GitHub 仓库 mcp-debug 包含了利用 AI 分析 Windows 崩溃转储的代码,并能通过 Microsoft 的 “CDB” 工具进行用户空间调试。该项目使用模型上下文协议 (Model Context Protocol, MCP) 作为与大语言模型 (LLM) 及 GitHub Copilot 的接口,实现了令人惊叹的功能:将调试技术带入 21 世纪,打破了 WinDbg 工具那套只有少数资深工程师才掌握的神秘命令体系。这是一项突破性的成果:它让原本如同魔法般高深的先进技术,从少数人的专属领域变得对大众可及。
在研究 Sven 的代码时,我开始思考:能否将其扩展到深度 Windows 内核交互式调试?在此前的工作中,我曾大量使用 JTAG,通过 ASSET 公司的 SourcePoint 产品在远程 AAEON UP Xtreme i11 Tiger Lake 开发板上探索内核。这是一套非常强大的组合。但 SourcePoint 同样存在学习曲线,虽然它具备诸多优势,却缺少 Microsoft WinDbg 内核调试工具的某些功能。如果能将 WinDbg 的强大能力与基于 LLM 的自然语言处理相结合,更深入地挖掘内核,会怎么样呢?下图展示了我试图实现的架构:
主机 PC 在 VS Code 中运行 GitHub Copilot,连接到 Claude Sonnet 4.5;MCP 将自然语言转换为特定的 WinDbg/KD 命令并发送至目标机,从而扩展了我们在内核研究方面的调试能力。
你可能仍会追问:这有什么意义?这将为研究人员在内核调试和漏洞研究方面提供巨大的能力提升。想象一下,能够用自然语言来探索我们博客文章中记录的恶意驱动程序代码,比如 Ivan Cabrera 的 《逆向易受攻击的杀手驱动程序方法论》 和 Jay Pandya 的 《理解 Windows 内核驱动程序中的越界问题》。这其中的可能性无穷无尽。
当然,这是一项艰巨的工程 (否则早就有人做成了)。加上 Sven 使用的是 Python 编程语言,而我目前对它并不太熟悉。但我决定全力以赴;况且 Python 也是 AI 的主流语言,正是绝佳的学习机会。于是”氛围编码”就派上了用场。没有什么比全身心投入、挑战自己的能力极限更能促进学习了!就这样,我开始了这段旅程。
首先,理解 Sven 的 mcp-windbg 仓库中源代码的整体结构至关重要。代码主体围绕两个文件展开:server.py负责设置和销毁调试会话及崩溃转储的资源、运行 WinDbg 命令等;cdb_session.py负责管理 CDB 会话、发送命令、等待命令完成并触发提示符等。我很快意识到 CDB 和 KD (我将使用的内核调试器) 在运行机制上差异显著。我必须扩展 server.py的功能来适配 KD 会话的建立方式,这与 CDB 大相径庭;同时还需要创建一个新的 kd_session.py来持续读取 KD 调试器的输出 (这是其独特之处)、等待提示符、发送命令等。听起来简单?实则不然,正如您将看到的。
从服务器端着手,我创建了一个额外的函数来对应现有的 get_or_create_session(),命名为 get_or_create_kd_session,专门用于管理内核调试会话,并假设目标是远程的且可通过 TCP/IP 访问:
defget_or_create_kd_session(kernel_connection: str,
symbols_path: Optional[str] =None,
timeout: int=30,
kd_flags: Optional[str] =None) -> KDSession:
sid = f"kernel:{kernel_connection}"
sess = active_kernel_sessions.get(sid)
if sess isNone:
sess = KDSession(kernel_connection=kernel_connection,
symbols_path=symbols_path,
timeout=float(timeout),
kd_flags=kd_flags)
sess.start()
active_kernel_sessions[sid] = sess
return sess
我还添加了几个工具:
Tool(
name="open_windbg_kernel",
description="""
使用 KD (kd.exe) 打开内核调试会话。
接受 '-kl' (本地内核) 或传输协议如 'net:port=...,key=...', 'usb3:targetname=...', 'com:port=COM1,baud=115200'。
可选择在连接时返回线程、堆栈和模块信息。
""",
inputSchema=OpenWindbgKernel.model_json_schema(),
),
Tool(
name="close_windbg_kernel",
description="""
关闭之前通过 open_windbg_kernel 打开的内核调试会话。
""",
inputSchema=CloseWindbgKernelParams.model_json_schema(),
),
Tool(
name="send_break",
description="""
向正在运行的内核调试会话发送 CTRL-C 信号以中断目标。
这会中断目标系统并进入内核调试器。
""",
inputSchema=SendBreakParams.model_json_schema(),
),
看到上面的 send_break工具了吗?这是我早期尝试解决内核调试与用户态调试会话之间根本差异的一个方案。KD 应用程序首先与目标机上运行的远程 KDNET 代理建立连接;然后必须重置目标才能中断进入调试器。
实际情况是这样的:当目标处于运行状态,你执行 open_windbg_kernel时,会通过标准输入输出 (stdio) 得到以下文本:
PS C:\Users\alans> kd -k net:port=50000,key=cja5yc9a64kf.2hmf45lejxq8z.3or47kcoz7uc4.3a6e8x9lpigeo
************* Preparing the environment for Debugger Extensions Gallery repositories **************
ExtensionRepository : Implicit
UseExperimentalFeatureForNugetShare : true
AllowNugetExeUpdate : true
NonInteractiveNuget : true
AllowNugetMSCredentialProviderInstall : true
AllowParallelInitializationOfLocalRepositories : true
EnableRedirectToV8JsProvider : false
-- Configuring repositories
----> Repository : LocalInstalled, Enabled: true
----> Repository : UserExtensions, Enabled: true
>>>>>>>>>>>>> Preparing the environment for Debugger Extensions Gallery repositories completed, duration 0.015 seconds
************* Waiting for Debugger Extensions Gallery to Initialize **************
>>>>>>>>>>>>> Waiting for Debugger Extensions Gallery to Initialize completed, duration 0.360 seconds
----> Repository : UserExtensions, Enabled: true, Packages count: 0
----> Repository : LocalInstalled, Enabled: true, Packages count: 29
Microsoft (R) Windows Debugger Version 10.0.26100.6584 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
Using NET for debugging
Opened WinSock 2.0
Kernel Debug Target Status: [no_debuggee]; Retries: [0] times in last [7] seconds.
Waiting to reconnect...
Connected to target 192.168.68.55 on port 50000 on local IP 192.168.68.81.
You can get the target MAC address by running .kdtargetmac command.
然后你需要到目标机上手动重置,通常从命令提示符窗口执行 “shutdown -r -t 0“。接着会立即看到大量文本输出:
Connected to Windows 10 26100 x64 target at (Tue Nov 11 14:34:36.979 2025 (UTC - 6:00)), ptr64 TRUE
Kernel Debugger connection established.
Symbol search path is: srv*
Executable search path is:
Windows 10 Kernel Version 26100 MP (4 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Edition build lab: 26100.1.amd64fre.ge_release.240331-1435
Kernel base = 0xfffff800`7f200000 PsLoadedModuleList = 0xfffff800`800f4f10
Debug session time: Tue Nov 11 14:34:54.996 2025 (UTC - 6:00)
System Uptime: 0 days 0:14:04.732
Shutdown occurred at (Tue Nov 11 14:34:39.868 2025 (UTC - 6:00))...unloading all symbol tables.
Using NET for debugging
Opened WinSock 2.0
Waiting to reconnect...
Connected to target 192.168.68.55 on port 50000 on local IP 192.168.68.81.
You can get the target MAC address by running .kdtargetmac command.
Connected to Windows 10 26100 x64 target at (Tue Nov 11 14:34:52.789 2025 (UTC - 6:00)), ptr64 TRUE
Kernel Debugger connection established.
************* Path validation summary **************
Response Time (ms) Location
Deferred srv*
Symbol search path is: srv*
Executable search path is:
Windows 10 Kernel Version 26100 MP (1 procs) Free x64
Edition build lab: 26100.1.amd64fre.ge_release.240331-1435
Kernel base = 0xfffff802`afc00000 PsLoadedModuleList = 0xfffff802`b0af4f10
System Uptime: 0 days 0:00:01.377
KDTARGET: Refreshing KD connection
DriverEntry failed 0xc00000bb for driver \REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\NetworkPrivacyPolicy
最后,需要按 CTRL-C 才能中断目标并使其进入挂起状态。然后调试器横幅显示出来,你会看到以下内容:
Break instruction exception - code 80000003 (first chance)
*******************************************************************************
* *
* You are seeing this message because you pressed either *
* CTRL+C (if you run console kernel debugger) or, *
* CTRL+BREAK (if you run GUI kernel debugger), *
* on your debugger machine's keyboard. *
* *
* THIS IS NOT A BUG OR A SYSTEM CRASH *
* *
* If you did not intend to break into the debugger, press the "g" key, then *
* press the "Enter" key now. This message might immediately reappear. If it *
* does, press "g" and "Enter" again. *
* *
*******************************************************************************
nt!DbgBreakPointWithStatus:
fffff802`b00f7240 cc int 3
2: kd>
这个过程非常笨拙,而且很难编写相应的代码!
不过,在继续之前,我还必须创建一个新的 kd_session.py,其中包含 _reader、_collect_until_prompt、sync 等例程。尽管这是一个非常简短的程序,但它最终成为一个主要障碍,并最终阻止我成功实现该解决方案。虽然我能成功连接到目标,例如使用:
kd -k net:port=50000,key=cja5yc9a64kf.2hmf45lejxq8z.3or47kcoz7uc4.3a6e8x9lpigeo
但我最初无法让代码发送 CTRL-BREAK 或 CTRL-C,因为这种键盘驱动的中断无法通过接口发送。CTRL-C 键盘序列无法通过 MCP WinDbg 工具发送;这些工具仅支持向调试器发送文本命令,不支持原始键盘输入或控制字符。而且在 KD 命令序列中使用 “-b” 参数在连接时中断也不起作用——该参数已被弃用。所以即使克服了这一点,我仍然只能间歇性地连接到目标,MCP 会与目标状态失去同步;有时我可以执行一两个命令,然后主机突然认为目标处于运行状态,从而阻止进一步的调试操作。
想看看我在这个项目上工作时一些”氛围编码”和调试的过程吗?如果感兴趣,请查看我们的视频:
使用 AI 进行底层 Windows 内核调试
在这个项目上工作了几天后,即使 AI 也准备认输了:用 GitHub Copilot 自己的话说:
不幸的是,CTRL-BREAK 也无法挂起目标。三种方法全部失败:
❌ os.kill(signal.CTRL_C_EVENT)– 无法挂起目标
❌ stdin.write(b'\x03')– 无法挂起目标
❌ os.kill(signal.CTRL_BREAK_EVENT)– 无法挂起目标
根本原因是基础性的:kd.exe 是为交互式控制台使用而设计的,但当通过 subprocess.Popen 以重定向 I/O (stdin=PIPE, stdout=PIPE) 方式运行时,它失去了使 CTRL-C/CTRL-BREAK 生效的控制台控制信号处理机制。
架构问题:
- CREATE_NEW_PROCESS_GROUP 标志 (第 62 行) 是通过 os.kill() 发送信号所必需的
- 但同一标志将进程与正确接收控制台控制信号隔离开来
- kd.exe 期望直接控制台连接,而非重定向管道
建议:接受在当前子进程架构下,程序化中断是不可行的。用户必须在控制台窗口手动按 CTRL-C 进行交互式调试。
我应该:
- 更新文档以明确说明需要手动 CTRL-C 才能中断运行中的目标
- 保留 send_break() 作为”尽力而为”并附带已添加的警告
- 完全删除 send_break(),因为它不能可靠工作
还是你想探索替代方法,比如使用 winpty (增加依赖) 或 Windows API 调用来附加控制台?
我尝试了许多其他方法。例如,我创建了一个单独的 CMD 窗口来启动 KD,然后手动发出 CTRL-C 中断目标,接着退出,让 Copilot/MCP 接管。但仍然没有成功。经过多个小时的实验,我也准备放弃了。
但随后我决定换个角度,从 30,000 英尺的高度俯瞰,询问 GitHub Copilot 是否有更好的整体方法。经过一番交流后,它提出了一个引人注目的新选项:
选项 2:MCP 通过 COM/DbgEng 控制 WinDbg (最佳长期方案)
不使用 kd.exe 的子进程方式,而是使用 DbgEng.dll COM 接口:
- MCP 将使用与 WinDbg 相同的调试引擎
- 可以程序化地发送中断命令
- 没有 stdio 重定向问题
- 这是严肃调试工具的构建方式
代码草图:
import comtypes
from comtypes.client import CreateObject
# 使用 DbgEng COM 接口
client = CreateObject("DbgEng.DebugClient")
control = client.QueryInterface(IDebugControl)
control.SetInterrupt(DEBUG_INTERRUPT_ACTIVE) # 发送中断
用于生产环境:
实现选项 2 (DbgEng COM)。这将:
- ✓ 允许程序化的中断命令
- ✓ 消除 stdio 缓冲问题
- ✓ 提供可靠的状态跟踪
- ✓ 匹配 WinDbg 内部的工作方式
- ✗ 实现更复杂
DbgEng 方法是 WinDbgX、Visual Studio 调试器和商业工具等专业调试工具所使用的。这是在 Windows 上构建程序化调试接口的”正确”方式。
您想让我探索 DbgEng COM 方法吗?它将解决我们一直在与 kd.exe stdio 斗争的中断问题和缓冲问题。
这听起来很棒!所以,这就是我接下来要尝试的。敬请关注关于这个主题的下一篇文章。
Using MCP for Debugging, Reversing, and Threat Analysis Part 2
免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。
查看原文:《使用 MCP 进行 Windows 内核调试:从 Vibe Coding 到 DbgEng COM 接口的探索之路》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论