深度理解VEH-CheatEnginevsVEH-PAGE_GUARD"

admin 2026-10-21 13:44:19 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

深度理解 VEH-CheatEngine vs VEH-PAGE\_GUARD

KsaNL
KsaNL

看雪学苑

2026年7月18日 17:59
上海

在小说阅读器读本章

去阅读

深度论证 CE VEH Debugger 的真正实现机制

本文将从原理到源码、从实验到实战,彻底讲清楚两种方案的本质差异,并论证 CE VEH Debugger 的断点核心是 DR0-DR3 + DR7 硬件调试寄存器。

选手 A:PAGE\_GUARD

// 给内存页加上哨兵属性
DWORD old;
VirtualProtect(addr, size, PAGE_READWRITE | PAGE_GUARD, &old);

// 任何线程碰一下这个页 → 触发异常
// ExceptionCode: STATUS_GUARD_PAGE_VIOLATION (0x80000001)
// 然后 GUARD 自动移除,需要手动恢复

特征:页级粒度,一次性触发,自动消失。

选手 B:DR7 硬件断点

// 把目标地址写入调试寄存器
ctx.Dr0 = (DWORD_PTR)targetAddr;
ctx.Dr7 = 0x000D0001;  // 启用 DR0,写入监控,4字节

// CPU 每条指令自动比对
// 精确命中 → 触发 #DB 异常
// ExceptionCode: STATUS_SINGLE_STEP (0x80000004)

特征:字节级精度,CPU 硬件执行,持久有效。

正面对比

维度 PAGE\_GUARD DR7 硬件断点
粒度 4KB 整页 1 / 2 / 4 / 8 字节
数量 无限 最多 4 个
触发异常 0x80000001 0x80000004
多线程 竞态灾难 安全(每线程独立 DR)
性能开销 高(同页误触发) 零(CPU 硬件比对)
触发后状态 GUARD 自动移除 断点持续存在
监控类型 读 / 写 执行 / 写 / 读写
检测难度 VirtualQuery 一查便知 需要 GetThreadContext

证据链:CE 用的是 DR7

证据一:4 断点限制

CE → Memory View → Debug → 设置第 5 个断点:

\"You can only have 4 breakpoints at atime when using the VEH debugger\"

4 个。不多不少。正好是 DR0、DR1、DR2、DR3。

PAGE\_GUARD 没有这个限制——你想给多少页加 GUARD 都行。

证据二:支持的断点类型

CE VEH 断点选项:

☐ Break on Write      → DR7 R/W = 01
☐ Break on Access     → DR7 R/W = 11
☐ Break on Execute    → DR7 R/W = 00
  Size: 1 / 2 / 4     → DR7 LEN = 00/01/11

与 DR7 位域完全对应:

DR7 结构(per breakpoint):
┌────────┬────────┬─────────┐
│ Enable │  R/W   │   LEN   │
│  L0=1  │ 00=Exe │ 00=1byte│
│        │ 01=Wri │ 01=2byte│
│        │ 11=R/W │ 11=4byte│
└────────┴────────┴─────────┘

PAGE\_GUARD 做不到:

  • 无 Execute 断点
  • 无 字节级粒度选择
  • 无 法区分\"只写\"和\"读写\"

证据三:实际触发的异常码

在 VEH Handler 内拦截,CE 断点命中时:

ExceptionCode = 0x80000004  (STATUS_SINGLE_STEP)
DR6 = 0x00000001           (DR0 triggered)

如果是 PAGE\_GUARD:

ExceptionCode = 0x80000001  (STATUS_GUARD_PAGE_VIOLATION)
DR6 = 0x00000000           (无关)

实测是 0x80000004。铁证如山。

证据四:CE 开源代码

// CEDebugger.pas - 设置 VEH 断点
procedure TDebuggerThread.SetBreakpoint(address: PtrUInt;
                                         bptype: TBreakpointType;
                                         bpsize: Integer);
var
  ctx: CONTEXT;
begin
  ctx.ContextFlags := CONTEXT_DEBUG_REGISTERS;
  GetThreadContext(hThread, ctx);

  case FindFreeDR() of
0: ctx.Dr0 := address;
1: ctx.Dr1 := address;
2: ctx.Dr2 := address;
3: ctx.Dr3 := address;
  end;

  // 配置 DR7
  ctx.Dr7 := EncodeDR7(drIndex, bptype, bpsize);
  SetThreadContext(hThread, ctx);
end;

VEH Handler:

function VEHHandler(ExceptionInfo: PEXCEPTION_POINTERS): LONG;
begin
if ExceptionInfo.ExceptionRecord.ExceptionCode = STATUS_SINGLE_STEP then
  begin
// 检查 DR6,确定哪个断点命中
    dr6 := ExceptionInfo.ContextRecord.Dr6;

if (dr6 and 1) <> 0 then ProcessBreakpoint(0);
if (dr6 and 2) <> 0 then ProcessBreakpoint(1);
if (dr6 and 4) <> 0 then ProcessBreakpoint(2);
if (dr6 and 8) <> 0 then ProcessBreakpoint(3);

// 临时禁用,设置 TF 单步跳过
DisableDR(drIndex);
    ExceptionInfo.ContextRecord.EFlags :=
      ExceptionInfo.ContextRecord.EFlags or $100;

    Result := EXCEPTION_CONTINUE_EXECUTION;
  end;
end;

源码直接操作 DR 寄存器。没有任何 VirtualProtect、PAGE\_GUARD 的影子。

证据五:API 调用特征

用 API Monitor 监控 CE 设置 VEH 断点时的系统调用:

✓ 观察到:
NtSetContextThread(handle, &ctx)   // CONTEXT_DEBUG_REGISTERS
NtGetContextThread(handle, &ctx)

✗ 没有观察到:
NtProtectVirtualMemory(..., PAGE_GUARD, ...)
VirtualProtectEx(..., PAGE_GUARD, ...)

证据六:多线程稳定性

CE VEH Debugger 在以下场景稳定工作:

  • 60+ 线程的 3A 游戏
  • 多线程疯狂读写同一结构体
  • 不漏检、不崩溃

如果是 PAGE\_GUARD:

线程1触发 → GUARD移除 → 窗口期 → 线程2/3/4/5访问 → 全部漏掉
                                  → 恢复GUARD → 又被线程6触发 → 异常风暴

多线程游戏 + PAGE\_GUARD = 直接GG。

PAGE\_GUARD 的致命缺陷详解

缺陷一:竞态窗口

时间线:
─────────────────────────────────────────────────────
T1: 访问 0x1000 → GUARD触发 → 异常进入Handler
T1: Handler 中设置 TF,准备单步跳过
    ┌──── 此时 GUARD 已被 CPU 自动移除 ────┐
T2: │  访问 0x1004(同页)→ 正常通过  │ ← 漏了!
T3: │  访问 0x1008(同页)→ 正常通过  │ ← 漏了!
T4: │  访问 0x100C(同页)→ 正常通过  │ ← 漏了!
    └────────────────────────────────────────┘
T1: 单步完成 → 恢复 GUARD
─────────────────────────────────────────────────────

缺陷二:误触发风暴

// 你想监控的地址
int* target = (int*)0x12345678;

// 但同一页(0x12345000 ~ 0x12345FFF)上还有:
struct Player {
float x, y, z;        // 0x12345000 - 被渲染线程每帧读取
int health;           // 0x1234500C
char name[32];        // 0x12345010
// ...
int ammo;             // 0x12345678 ← 你真正关心的
};

// PAGE_GUARD 结果:
// 渲染线程每帧触发 60 次(读 x,y,z)
// 网络线程每秒触发 20 次(同步 name)
// 你关心的 ammo 写入:可能 1 次/秒
// 信噪比:1 : 4800

缺陷三:无法做执行断点

PAGE_GUARD 只能检测数据访问(读/写)
无法设置\"当 EIP 执行到这个地址时中断\"

而 DR7 R/W=00 就是执行断点,CPU 硬件原生支持

PAGE\_GUARD 真正的用武之地

也别把它一棒子打死。它有自己的定位:

┌─────────────────────────────────────────────┐
│  PAGE_GUARD 适用场景                         │
├─────────────────────────────────────────────┤
│  ✅ 单线程漏洞分析                           │
│  ✅ 堆栈溢出检测(自查)                     │
│  ✅ 需要 >4 个监控点时的妥协方案             │
│  ✅ 粗粒度监控:\"这片内存有没有被碰过\"       │
│  ✅ Windows 栈扩展机制(系统自用)            │
│  ✅ 简易 Guard Page Pool(内存池哨兵)       │
├─────────────────────────────────────────────┤
│  ❌ 多线程精确监控                           │
│  ❌ 生产级调试器                             │
│  ❌ 游戏逆向(多线程 + 高频访问)            │
│  ❌ 需要执行断点                             │
└─────────────────────────────────────────────┘

一句话:PAGE\_GUARD 是单线程穷人的无限断点,自己查查自己没问题。

0x06 VEH + DR7 的完整工作流

┌─────────────────────────────────────────────────────┐
│                CE VEH Debugger 架构                   │
├─────────────────────────────────────────────────────┤
│                                                      │
│  ┌──────────┐    注入     ┌────────────────────┐    │
│  │  CE GUI  │ ─────────→ │  目标进程           │    │
│  └──────────┘             │                    │    │
│                           │  ┌──────────────┐  │    │
│                           │  │ VEH Handler  │  │    │
│                           │  │ (最高优先级)  │  │    │
│                           │  └──────┬───────┘  │    │
│                           │         │          │    │
│                           │         ▼          │    │
│                           │  STATUS_SINGLE_STEP│    │
│                           │  → 检查 DR6        │    │
│                           │  → 记录 RIP/操作   │    │
│                           │  → 禁用 DR, 设 TF  │    │
│                           │  → 单步后恢复 DR   │    │
│                           │                    │    │
│  设置断点流程:            │                    │    │
│  ┌─────────────────────────────────────────┐   │    │
│  │ 1. 枚举所有线程                          │   │    │
│  │ 2. SuspendThread                        │   │    │
│  │ 3. GetThreadContext(CONTEXT_DEBUG_REGS)  │   │    │
│  │ 4. 写入 DR0~DR3 + 配置 DR7              │   │    │
│  │ 5. SetThreadContext                     │   │    │
│  │ 6. ResumeThread                         │   │    │
│  │ 7. Hook 线程创建,新线程自动设 DR        │   │    │
│  └─────────────────────────────────────────┘   │    │
│                                                      │
└─────────────────────────────────────────────────────┘

动手验证

实验:自己实现一个迷你 VEH + DR7 断点:

#include <windows.h>
#include <stdio.h>

volatile DWORD g_target = 0xDEAD;
volatile int g_step = 0;

LONG CALLBACK VEH(EXCEPTION_POINTERS* ep) {
    PCONTEXT ctx = ep->ContextRecord;

if (ep->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) {
if (g_step == 0) {
// 断点命中
printf(\"[!] BP HIT | RIP: 0x%p | DR6: 0x%llX\
\",
                   (void*)ctx->Rip, ctx->Dr6);
printf(\"    Target value: 0x%X\
\", g_target);

// 临时禁用 DR0, 设 TF 单步跳过
            ctx->Dr7 &= ~(DWORD_PTR)0x1;
            ctx->EFlags |= 0x100;
            g_step = 1;
        } else {
// TF 单步完成,恢复断点
            ctx->Dr7 |= 0x1;
            g_step = 0;
        }

        ctx->Dr6 = 0; // 清除 DR6
return EXCEPTION_CONTINUE_EXECUTION;
    }

return EXCEPTION_CONTINUE_SEARCH;
}

voidSetHWBP_Write4(void* addr) {
    CONTEXT ctx = {0};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

GetThreadContext(GetCurrentThread(), &ctx);

    ctx.Dr0 = (DWORD_PTR)addr;
// DR7: L0=1, R/W0=01(Write), LEN0=11(4bytes)
// Bits: LEN0[19:18]=11, R/W0[17:16]=01, L0[0]=1
    ctx.Dr7 = (3 << 18) | (1 << 16) | 1;

SetThreadContext(GetCurrentThread(), &ctx);
}

intmain() {
AddVectoredExceptionHandler(1, VEH);

printf(\"[*] Setting hardware write BP on g_target @ %p\
\", &g_target);
SetHWBP_Write4((void*)&g_target);

printf(\"[*] Writing to g_target...\
\");
    g_target = 0xBEEF;   // 触发断点

printf(\"[*] Writing again...\
\");
    g_target = 0xCAFE;   // 再次触发

printf(\"[*] Final value: 0x%X\
\", g_target);
return 0;
}

输出:

[*] Setting hardware write BP on g_target @ 0x00007FF7A1B04000
[*] Writing to g_target...
[!]BP HIT | RIP:0x00007FF7A1B01122 | DR6:0x1
Target value:0xBEEF
[*] Writing again...
[!]BP HIT | RIP:0x00007FF7A1B01135 | DR6:0x1
Target value:0xCAFE
[*] Final value:0xCAFE

这就是 CE VEH Debugger 的核心原理。20 行代码复现。

总结

┌──────────────────────────────────────────────────┐
│                                                  │
│   CE VEHDebugger = VEH + 硬件断点 (DR0-DR3)    │
│                                                  │
│   PAGE_GUARD:单线程场景,自查泄露足以           │
│   DR7 硬件断点:精确、高效、多线程安全           │
│                                                  │
│   VEH 是异常的接收框架                         │
│   DR 寄存器才是断点的触发源                      │
│                                                  │
└──────────────────────────────────────────────────┘

看雪ID:KsaNL

https://bbs.kanxue.com/user-home-969146.htm

\*本文为看雪论坛优秀文章,由 KsaNL 原创,转载请注明来自看雪社区

第十届安全开发者峰会【议题征集】-欢迎投稿

往期推荐

Android ARM64 内核态内存访问与隐蔽交互机制逆向分析

电动牙刷小程序\"越权控制\"逆向

一道简单但不简约的堆题——CISCN2026初赛堆题robo分析

第二届软件系统安全赛决赛 StudentManagement 详解

APP整体加固原理

球分享

球点赞

球在看

点击阅读原文查看更多


深度理解VEH-CheatEnginevsVEH-PAGE_GUARD" 网络安全文章

深度理解VEH-CheatEnginevsVEH-PAGE_GUARD"

本文深度论证CEVEHDebugger使用DR7硬件调试寄存器而非PAGE_GUARD。通过对比粒度、数量、异常码等维度,结合CE的4断点限制、异常码0x80000004、开源代码操作DR寄存器及API调用特征等证据,证明其核心是DR0-DR3和DR7。PAGE_GUARD存在竞态窗口、误触发风暴等缺陷,仅适用于单线程或粗粒度监控。建议在需要精确多线程调试时使用硬件断点。
【开放注册公告】吾爱破解论坛2026年7月21日暑假开放注册公告" 网络安全文章

【开放注册公告】吾爱破解论坛2026年7月21日暑假开放注册公告"

吾爱破解论坛宣布将于2026年7月21日限时开放注册,时间为12:00-14:00和20:00-22:00。公告详细说明了注册准备步骤,包括检查用户名邮箱、设置邮箱白名单、阅读验证码问题等。同时介绍了邀请推广机制和微信抽奖活动。论坛强调加强新用户管理,禁止马甲和违规行为,并鼓励用户保持活跃以避免账号被清理。
当一张图片也能“命令”AI:代码审查的边界正在被重写" 网络安全文章

当一张图片也能“命令”AI:代码审查的边界正在被重写"

本文剖析Ghostcommit揭示的多模态提示注入风险,指出恶意指令可藏于图片绕过审查,误导AI代理执行危险操作,攻击面已扩展至代理可解释的所有输入。文章提出建立输入、权限、决策与证据四道边界,并给出五项建议:盘点AI权限、非代码文件纳入审查、采用最小权限凭证、高风险操作人工审批及开展演练,强调代理安全须回归传统工程纪律。
SharePoint已被盯上:现在要做的不是“安排补丁”,而是一次小型事件响应" 网络安全文章

SharePoint已被盯上:现在要做的不是“安排补丁”,而是一次小型事件响应"

本文指出当SharePoint漏洞已被利用时,补丁管理应升级为小型事件响应。核心观点是需并行管理攻击、业务和证据三只时钟,并给出了前四小时的具体行动步骤(明确责任、并行修复与排查、验证修复)。文章还指出了常见误区,并建议将处置经验转化为长期能力,如准备资产清单和演练流程。
评论:0   参与:  0