BugChecker
简介
BugChecker 是一个类似于 SoftICE 的内核和用户调试器,支持 Windows 11(也支持 Windows XP:它支持从 XP 到 11 的 Windows 版本,包括 x86 和 x64)。BugChecker 不需要像 WinDbg 和 KD 那样连接第二台机器到被调试的系统。此版本的 BugChecker(与 20 年前开发的原始版本不同)利用了 NTOSKRNL 中内部且未文档化的 KD API。KD API 允许 WinDbg/KD 执行诸如读/写虚拟内存、读/写寄存器、在地址处设置断点等调用。
相比之下,原始版 BugChecker 和 SoftICE 一样,通过挂钩多个内核 API(包括导出和私有 API)、控制 APIC、发送 IPI 等方式“接管”系统。这种方法复杂性呈指数增长(并降低系统稳定性),因为实现必须与所有支持的 Windows 版本和子版本(在函数签名级别)以及所有可能支持的硬件配置兼容。此外,20 年后,PatchGuard 使这种解决方案变得不可能。
相比之下,此版本的 BugChecker 通过拦截内核中的 KdSendPacket 和 KdReceivePacket 调用,向被调试的机器呈现为另一个运行外部内核调试器的系统,但实际上所有操作都发生在同一台机器上。通常,这是通过替换 KDCOM.DLL(该模块在 Windows 中实现 KD API 的串行电缆通信)并以内核调试模式启动系统来实现的。这种方法(灵感来自 VirtualKD)降低了复杂性,提高了稳定性和兼容性(以及可移植性,例如可以移植到 ARM——以及模块化,因为低级调试器功能在 KdXxxPacket 背后实现,可以用自定义实现替换)。此外,引导时存在内核调试器(尽管是“假的”)会使 Windows 禁用 PatchGuard。
目前,BugChecker 需要 PS/2 键盘输入和线性帧缓冲来输出。请注意,许多现代笔记本电脑的内置键盘仍然是 PS/2 的。
特性
- 支持 Windows XP 到 Windows 11,x86 和 x64,以及 SMP 内核。支持 x64 上的 WOW64 进程。
- 集成了 QuickJSPP,这是 QuickJS 到 MSVC++ 的移植。在调用 QuickJS 之前,BugChecker 保存 FPU 状态(在 x86 上)并切换到 128KB 的扩展堆栈。
- 命令接受 JS 表达式。例如,“U rip+rax*4”和“U MyJsFn(rax+2)”都是有效的命令。可以在脚本窗口中定义自定义函数。CPU 寄存器由 BugChecker 自动声明为全局作用域变量。
- 支持 PDB 符号文件。可以手动指定 PDB 文件,或者符号加载器可以从符号服务器下载它们。
- JavaScript 代码可以调用以下异步函数:WriteReg、ReadMem、WriteMem。
- 断点可以带有 JS 条件:如果条件计算结果为 0,则不会发生“断入”。这允许设置“日志点”以及可以改变执行流程的断点。
- 日志窗口显示发送给内核调试器的消息(例如 DbgPrint 消息)。
- JavaScript 窗口支持语法高亮。
- 按 Tab 键可以在给定少量数字时循环浏览屏幕上的所有十六进制数字,或者在给定少量字符时循环浏览包含这些字符的所有符号。
- EASTL 和 C++20 协程使创建新命令变得轻而易举。欢迎提交你的拉取请求!
视频(YouTube)
在 VirtualBox 7.0.4 中的 Windows 11 22H2 上演示 BugChecker。编写了一个 JavaScript 断点条件来改变用户模式线程的执行流程。

BugChecker 在非常受限的环境中运行:树莓派 4(4GB 内存),通过 QEMU 在 Windows XP(512MB 内存)上运行。使用断点记录所有从用户模式到内核的 SYSENTER 调用。服务索引存储在 JavaScript 数组中。

直接在裸机上运行 BugChecker,在一台配备 PS/2 键盘的旧电脑 HP Pavilion Dv2000 上。操作系统是 Windows 7 Home 32 位。

安装说明
简介
在安装和使用 BugChecker 时,请确保安全启动已禁用。通常之后可以重新启用。如果你使用 VMware 或 VirtualBox,可以在虚拟机设置中禁用安全启动。
如果使用 Windows 8、10 或 11,还可以考虑使用命令启用传统启动菜单: bcdedit /set "{current}" bootmenupolicy legacy。它允许在启动时选择 BugChecker 启动选项并同时禁用驱动程序签名强制,从而获得更流畅的体验。
说明
第一步是启动符号加载器:

如有必要,通过单击“Disable Display Drvs(禁用显示驱动程序)”按钮来禁用显示驱动程序。同样的操作也可以在 Windows 设备管理器中完成。禁用显示驱动程序后,它们即使在系统重启后也保持禁用状态。以后不使用时可以随时重新启用。
关键点是:BugChecker 需要一个 32 位每像素格式的线性帧缓冲来绘制其界面。禁用显示驱动程序时,Windows 会放弃加速 UI 绘图的硬件加速,回退到 VGA 兼容模式。如果在裸机或 VMware 上运行,应该禁用显示驱动程序。如果在 VirtualBox 上运行,应该禁用显示驱动程序,或者按如下所述设置 BugChecker.dat 中的 vm_screen 设置。如果在 QEMU 上运行,则无需禁用显示驱动程序,但需要指定“-vga std”显示设备。
请注意,VGA 兼容模式可能限制最大屏幕分辨率。VMware 限制最大分辨率为 1152x864。使用“-vga std”显示设备的 QEMU 不受此限制。
有趣的是,如果 BugChecker 安装在具有多个显卡的系统上,可以只禁用其中一个显卡的显示驱动程序,该显卡将连接到显示 BugChecker UI 的屏幕。第二个显卡(设为主显示器)将保留其所有 2D 和 3D 加速功能,包括 OpenGL 和 DirectX 支持(注意:已在 VMware 上使用 Windows 11 和 DisplayLink 显示器测试过)。
然后点击“Start Driver(启动驱动程序)”,然后是“Auto Detect(自动检测)”,最后点击“Save(保存)”。“自动检测”应该能够自动确定帧缓冲的宽度、高度、物理地址和步幅。但是,你也可以手动指定这些设置(完成后不要忘记点击“保存”)。如果“步幅”为 0,则启动驱动程序时会自动计算为“宽度”* 4。“地址”(即帧缓冲的物理地址)可以在 Windows 设备管理器中获取,方法是点击显示设备的“属性”,然后选择“资源”选项卡。
然后在“KDCOM Hook Method(KDCOM 挂钩方法)”部分点击“Callback(回调)”,然后点击“Copy/Replace Kdcom(复制/替换 Kdcom)”,最后可以重启系统。
此设置过程只需执行一次,并且如有必要可以重新启用显示驱动程序。但在使用 BugChecker 时,如果配置需要,必须再次禁用显示驱动程序。
VirtualBox 的 vm_screen 设置(实验性)
BugChecker.dat 中的 vm_screen 设置允许在 VirtualBox 中打开 BugChecker 调试器 UI,而无需预先在符号加载器中指定屏幕分辨率,也无需禁用显示驱动程序。
其思路是直接向虚拟显示设备的 I/O 端口和命令缓冲区写入数据,从而获取当前屏幕分辨率并通知虚拟机监视器帧缓冲的任何更新。
此解决方案的灵感来自 X.org xf86-video-vmware 驱动程序。
此解决方案仅适用于 VirtualBox VM,并且需要手动编辑 BugChecker.dat 文件:

- 在符号加载器中,手动将帧缓冲的宽度和高度设置为最大可能分辨率(即你电脑屏幕的尺寸)。将步幅设置为 0。
- BugChecker.dat 文件由符号加载器创建在“C:\Windows\BugChecker”中。
- vm_screen 设置应添加到“settings->framebuffer”下。
- 此文件中设置的分层结构由制表符(而非空格)决定。
- 设置的格式为:Command_Buffer_Start_Address(逗号)Command_Buffer_End_Address(逗号)I/O_Port_Base
- 重要:在 VM 设置的“Display(显示)”下,选择“VBoxSVGA”作为图形控制器,并取消勾选“Enable 3D Acceleration(启用 3D 加速)”。
这是一个实验性功能。将来,此设置将由符号加载器自动添加。
已实现的命令
命令名称和语法尽可能接近原始 SoftICE for NT:
- ? javascript-expression: 计算一个 JavaScript 表达式。
- ADDR eprocess: 切换到进程上下文(将控制权返回给操作系统)。
- BC list|*: 清除一个或多个断点。
- BD list|*: 禁用一个或多个断点。
- BE list|*: 启用一个或多个断点。
- BL (无参数): 列出所有断点。
- BPX address [-t|-p|-kt thread|-kp process] [WHEN js-expression]: 设置执行断点。
- CLS (无参数): 清除日志窗口。
- COLOR [normal bold reverse help line]|[reset]: 显示、设置或重置屏幕颜色。
- DB/DW/DD/DQ [address] [-l len-in-bytes]: 以 8/16/32/64 位值显示内存。
- EB/EW/ED/EQ address -v space-separated-values: 以 8/16/32/64 位值编辑内存。
- KL EN|IT: 设置键盘布局。
- LINES [rows-num]: 显示或设置当前显示行数。
- MOD [-u|-s] [search-string]: 显示模块信息。
- P [RET]: 执行一个程序步。
- PAGEIN address: 强制分页调入一页内存(将控制权返回给操作系统)。
- PROC [search-string]: 显示进程信息。
- R register-name -v value: 更改寄存器值。
- STACK [stack-ptr]: 扫描堆栈搜索返回地址。
- T (无参数): 跟踪一条指令。
- THREAD [-kt thread|-kp process]: 显示线程信息。
- U address|DEST: 反汇编指令。
- VER (无参数): 显示版本信息。
- WD [window-size]: 切换反汇编器窗口或设置其大小。
- WIDTH [columns-num]: 显示或设置当前显示列数。
- WR (无参数): 切换寄存器窗口。
- WS [window-size]: 切换脚本窗口或设置其大小。
- X (无参数): 退出 BugChecker 屏幕。
构建说明
先决条件
- Visual Studio 2019
- Windows Driver Kit 7.1.0
注意:WDK 应安装在其默认位置,即 X:\WinDDK,其中 X 是保存 BugChecker 源代码的驱动器。
构建内核驱动程序的逐步指南可在此处找到。
Visual Studio 项目说明
- BugChecker: 这是 BugChecker 内核驱动程序,整个调试器在此实现。“Release|x86”和“Release|x64”输出文件包含在最终包中。在初始化期间,驱动程序在“\SystemRoot\BugChecker\BugChecker.dat”加载其配置文件(所有符号文件也存储在此目录中),然后尝试在内核空间中找到“KDCOM.dll”。如果找到,它会尝试调用其导出的“KdSetBugCheckerCallbacks”函数,从而挂钩 KdSendPacket 和 KdReceivePacket。
- SymLoader: 这是符号加载器。只有“Release|x86”输出文件包含在最终包中。符号加载器用于更改 BugChecker 配置(配置写入“\SystemRoot\BugChecker\BugChecker.dat”)、下载 PDB 文件以及安装自定义的 KDCOM.dll 模块。
- KDCOM: 这是 NTOSKRNL 在系统启动时加载的自定义 KDCOM.dll 模块。它导出了驱动程序调用以挂钩 KdSendPacket 和 KdReceivePacket 的“KdSetBugCheckerCallbacks”函数。
- pdb: 这是 Ghidra 的“pdb”项目。原始版本将 PDB 文件的内容以 XML 格式输出到标准输出。代码已修改为生成 BCS 文件。
- NativeUtil: 由于符号加载器是 Windows x64 上的 WOW64 应用程序,因此必须从架构原生映像进行的 API 调用(例如设备和驱动程序安装 API 的调用)被移到了此处。
- HttpToHttpsProxy: 这是一个 ASP.NET Core 应用程序,其功能是在 Windows XP 中运行时充当符号加载器的互联网代理。由于 XP 的 TLS 支持已过时,符号加载器无法从任意符号服务器下载文件。将该应用程序部署在同一网络的 IIS 上后,可以在符号加载器中将服务器 URL 前面加上“http://<YOUR_IIS_SERVER_IP>/HttpToHttpsProxy/”来从符号服务器下载文件。
致谢
- VirtualKD:BugChecker 的第一个概念验证是通过修改 VirtualKD 构建的。
- BazisLib:符号加载器中“Copy/Replace Kdcom + Add Boot Entry(复制/替换 Kdcom + 添加启动项)”按钮背后的代码来自 VirtualKD 并使用 BazisLib。
- EASTL:这里无法使用 MSVC++ STL。EASTL 是一个优秀的替代品。
- Ghidra:BugChecker 中的“pdb”项目来自 Ghidra。它已被修改以生成 BCS 文件。
- Zydis:用于 BugChecker 中的反汇编窗口。
- QuickJSPP,这是 QuickJS 到 MSVC++ 的移植:用于集成在内核驱动程序中的 JavaScript 引擎。
- ReactOS:用于 Windows KD 内部类型定义。
- SerenityOS:用于 BugChecker 内存分配器使用的低级位图操作函数。因为在看到 Andreas 的一个视频(在远离 C/C++ 和任何低级编程 10 年后)后开始了 BugChecker 项目,我想在 BugChecker 中包含一小段 SerenityOS 的代码。