__try/__except无法捕获硬件断点异常,因其触发的调试异常(0x80000003)由内核调试子系统直接处理,不进入用户态SEH链;检测DR0–DR3需用GetThreadContext配合挂起线程与CONTEXT_DEBUG_REGISTERS标志。

DR0-DR3 硬件断点在 Windows 用户态下根本不可用
Windows 内核禁止普通进程直接写入 DR0–DR3 寄存器,哪怕开了调试权限(SeDebugPrivilege)也不行。调用 SetThreadContext() 传入含 Dr0 非零值的 CONTEXT 结构,API 会静默忽略这些字段——不是报错,是彻底丢弃。你查 GetThreadContext() 返回的 Dr0,永远是 0,哪怕你刚“设”过。
真正能用硬件断点的,只有内核驱动(通过 KeSetHardwareBreakpoint())或调试器自身(利用系统预留的调试寄存器上下文切换机制)。用户态程序想靠自己往 DR0 写地址来检测 OllyDbg / x64dbg 下的硬件断点?这条路从 WinXP SP2 起就被堵死了。
检测逆向工具更现实的路径是调试器存在性判断
硬件断点检测不成立,但你可以间接推断:如果当前进程正被调试,且调试器设置了硬件断点,那大概率是逆向分析行为。重点不是“有没有 DRx 被设”,而是“有没有调试器在控制流程”。
-
IsDebuggerPresent():最基础,但易被绕过(如清空Peb->BeingDebugged) -
CheckRemoteDebuggerPresent():比前者多查一层,仍可被隐藏调试器欺骗 - 尝试
OutputDebugStringA("")并捕获STATUS_ACCESS_VIOLATION:未启用调试时会触发异常,被调试时则静默成功——这个信号更难伪造 - 用
NtQueryInformationProcess查询ProcessBasicInformation,检查Peb->NtGlobalFlag是否含FLG_HEAP_ENABLE_TAIL_CHECK等调试器常设标志
DRx 相关异常(INT1)无法被用户态代码可靠捕获
即使你设法绕过系统限制(比如通过驱动配合),硬件断点触发的是 #DB 异常(向量 1),它由内核直接分发给调试器。用户态的 SetUnhandledExceptionFilter() 或 __try/__except 捕获不到这个异常——它根本不会到达你的 SEH 链。你看到的 EXCEPTION_SINGLE_STEP 是调试器转发的模拟信号,不是原始硬件中断。
立即学习“C++免费学习笔记(深入)”;
所以别试图在 EXCEPTION_SINGLE_STEP 处理器里读 DR6 判断哪个地址被击中;那个 EXCEPTION_SINGLE_STEP 很可能来自软件单步(EFLAGS.TF),和 DR0–DR3 完全无关。
真要监控内存访问,得换思路:ETW + 页面保护 + 异常回调
如果你的目标是“发现有人对某块内存下断点或读写”,可行组合是:
- 用
VirtualProtect()将目标页设为PAGE_NOACCESS,触发EXCEPTION_ACCESS_VIOLATION - 在异常处理器里检查
ExceptionRecord->ExceptionAddress和ExceptionRecord->ExceptionInformation[1](访问地址),确认是否命中目标区域 - 结合 ETW 的
Microsoft-Windows-Kernel-Memory提供的PageProtectionChanged事件,监听是否有外部进程调用VirtualProtectEx()修改你的页面属性(这往往是调试器下断点的前置动作)
这比硬啃 DRx 寄存器靠谱得多,而且所有操作都在用户态合法范围内。真正的难点不在技术实现,而在于 ETW 会话创建权限、事件过滤粒度,以及如何避免被调试器提前禁用 ETW provider。


















