一个安全研究概念验证(POC),演示基于硬件断点(CPU 调试寄存器)的函数挂钩,作为传统内存代码补丁的替代方案。
目的与范围
本仓库的发布严格用于防御性安全研究、红队/紫队教育、检测工程以及 Windows 内部机制的学术研究。它演示了攻击者如何滥用处理器调试寄存器来使 user-mode 安全遥测失效——同样重要的是,防御者应监控什么以检测此类技术。作者不对任何滥用此代码的行为负责。在未经明确授权的情况下对系统使用此技术是非法的,并且在大多数司法管辖区违反相关的计算机欺诈和滥用法。请勿在您不拥有或未获得明确书面测试许可的任何环境中部署此代码。
mora_hwbp.c 实现了一个 DLL,一旦被加载/注入到目标进程(例如 PowerShell 宿主),便仅通过 CPU 硬件断点挂钩四个 user-mode 函数,这些断点存储于进程中每个线程的架构调试寄存器(DR0–DR7)中:
| 寄存器 | 挂钩的函数 | 模块 | 目的 |
|---|---|---|---|
DR0 | AmsiScanBuffer | amsi.dll | 使 AMSI 内容扫描失效 |
DR1 | WldpQueryDynamicCodeTrust | wldp.dll | 强制 WLDP 动态代码信任(Device Guard / WDAC) |
DR2 | WldpIsClassInApprovedList | wldp.dll | 强制 WLDP 类批准(Device Guard / WDAC) |
DR3 | NtTraceEvent | ntdll.dll | 切断整个 user-mode ETW 流 |
一个进程级的向量化异常处理器(VEH)接收由调试寄存器引发的 EXCEPTION_SINGLE_STEP(0x80000004)故障,通过重写异常上下文来模拟原函数的成功返回路径,并恢复执行——所有这些均未修改可执行内存的任何一个字节。
这使得该技术从攻击和防御两个角度来看都特别有趣:
.text 段的完整性检查(经典的内联挂钩、EAT/IAT 补丁或 Etwp* 桩替换)。GetThreadContext/SetThreadContext 系统调用模式),可用于检测。传统的 user-mode 挂钩方法——内联 detour(5–14 字节覆写)、导入地址表(IAT)挂钩和导出地址表(EAT)挂钩——都有一个共同的弱点:它们修改了完整性扫描器和 ETW 可以观察到的内存。
现代 AV/EDR 产品实现了:
pageguard/guard-page 技巧、VirtualProtect 向 PAGE_EXECUTE_READWRITE 的转换,以及节区哈希不匹配。硬件断点绕过了这一切:
.text 中没有任何可扫描的内容。SetThreadContext 以每线程为基础设置,这不会触发完整性扫描器所使用的经典“内存已修改”信号。本 POC 探讨了该技术针对 AMSI(反恶意软件扫描接口)、WLDP(Windows 锁定策略) 和 ETW(Windows 事件跟踪) 的有效性与可检测性——这三者是现代 Windows 安全栈中最广泛依赖的用户态安全原语。
AMSI 是 Windows 平台集成点,允许应用程序(PowerShell、Office、VBScript、.NET 宿主等)向已注册的反恶意软件提供程序请求内容扫描。主要关注的入口点是:
AmsiScanBuffer(HANDLE hamsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, HANDLE hamsiSession, AMSI_RESULT *pResult)通过强制返回的 AMSI_RESULT 为 AMSI_RESULT_CLEAN (0),脚本引擎会认为内容已被检查并判定为良性,因此执行会不间断地继续。
WLDP 为 Windows Defender 应用程序控制(WDAC / Device Guard)实现策略评估。本 POC 挂钩了两个 WLDP 入口点:
WldpIsClassInApprovedList 回答给定 COM 类(由 GUID 标识)在当前策略下是否被允许。AMSI 内部会咨询 WLDP,以决定某些脚本/内容类是否“受信任”(在批准列表中)。如果该函数报告该类已获批准,AMSI 可能会跳过对该内容类型的额外审查。WldpQueryDynamicCodeTrust 回答给定的一段动态生成的代码(例如 .NET JIT 输出或其他生成的可执行内存)在当前锁定策略下是否受信任。该 DLL 将 WldpIsClassInApprovedList 的输出参数 isApproved(RDX)设置为 TRUE 并返回 S_OK,同时强制 WldpQueryDynamicCodeTrust 返回 S_OK (0)——使被评估的内容类和被查询的动态代码都显得受信任。
ntdll.dll 中的 NtTraceEvent 是进入 ETW 子系统的最低层用户态入口点——几乎所有 ETW 事件发出都经由它汇聚(包括 EtwEventWrite 和更高层的 Etw* API)。因此挂钩它会从用户态切断整个 ETW 流,并对安全监控产生广泛的副作用:
Microsoft-Windows-DotNETRuntime)该 DLL 只是返回 STATUS_SUCCESS (0),而不执行真实函数。
┌──────────────────────────────────────────────────────────────────────────┐
│ Target Process (e.g. powershell.exe) │
│ │
│ ┌──────────────────────────┐ ┌──────────────────────────────────┐│
│ │ mora_hwbp.dll │ │ CPU / Windows ││
│ │ │ │ ││
│ │ DllMain / InstallHook │ │ Thread A Thread B ││
│ │ │ │ │ ┌────────┐ ┌────────┐ ││
│ │ ▼ │ │ │ DR0..3 │ │ DR0..3 │ ││
│ │ Resolve exports │ │ └────────┘ └────────┘ ││
│ │ (amsi/wldp/ntdll) │ │ ││
│ │ │ │ │ #DB (single-step) ││
│ │ ▼ │ │ exception ──► Windows Dispatch ││
│ │ AddVectoredException │ │ │ ││
│ │ Handler(VEH) │ │ ▼ ││
│ │ │ │ │ ┌─────────────────────────────┐ ││
│ │ ▼ │ │ │ VectoredHandler (ours) │ ││
│ │ SetHwbpOnThread(ALL) │ │ │ • match #DB address │ ││
│ │ │ │ │ │ • rewrite context (RIP/RSP)│ ││
│ │ ▼ │ │ │ • spoof return value (RAX) │ ││
│ │ MonitorThread ◄──┐ │ │ │ • continue execution │ ││
│ │ (re-hook every │ │ │ └─────────────────────────────┘ ││
│ │ 500ms) └──────┘ │ ││
│ └──────────────────────────┘ └──────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────────────┘
高层流程:
DLL_PROCESS_ATTACH 时(或通过导出的 InstallHook),使用 GetProcAddress 解析目标导出函数(可选地通过 LoadLibraryW 强制加载模块)。AddVectoredExceptionHandler(1, ...))。CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) 枚举进程中的 所有现有线程 并对其进行挂钩。#DB 单步异常;Windows 将其分派给 VEH,VEH 模拟一个无害的返回并继续执行。注入之前——AMSI 阻止测试向量:
PS> "AmsiTestSample:7e72c3ce-861b-4339-8740-0ac1484c1386"
This script contains malicious content and has been blocked by your antivirus software.
[HWBP] DR0: AmsiScanBuffer @ 0x... (16 hits)
[HWBP] DR1: WldpQueryDynamicCodeTrust @ 0x... (0 hits)
[HWBP] DR2: WldpIsClassInApprovedList @ 0x... (0 hits)
[HWBP] DR3: NtTraceEvent @ 0x... (28 hits)
[HWBP] VEH: REGISTERED
[HWBP] Monitor: RUNNING
PS> "AmsiTestSample:7e72c3ce-861b-4339-8740-0ac1484c1386"
AmsiTestSample:7e72c3ce-861b-4339-8740-0ac1484c1386

在 x86/x64 上,每个 CPU 提供 四个硬件调试地址寄存器(DR0–DR3)和一个控制寄存器(DR7)。任何线程只要在 DR0–DR3 中设置了非零断点,当指令指针到达该地址(或数据访问匹配所配置的条件)时就会触发异常。状态寄存器 DR6 记录哪个断点被触发。
硬件断点是上下文相关的:它们存储在线程的 CONTEXT 结构中,并且仅对设置它们的线程生效。这就是为什么一个健壮的实现必须在进程的每个线程上设置断点(并持续为新线程重新应用这些断点)。
DR7 是一个位域,控制断点的启用和行为: