PEB中BeingDebugged字段位于x86偏移0x2、x64偏移0xB,通过__readfsdword(0x30)或__readgsqword(0x60)获取PEB基址后读取,需用volatile防止优化,且应配合NTGlobalFlag等其他检测手段避免误判。

PEB结构体中BeingDebugged字段的读取方式
Windows进程的PEB(Process Environment Block)里,BeingDebugged是一个单字节标志位,位于偏移0x2处(x86)或0xB处(x64),值为1表示当前进程正被调试器附加。直接读取它需要先获取PEB地址——在用户态,__readgsbyte(x86)或__readgsqword(x64)配合固定偏移是最常用且无需API调用的方式。
注意:该字段仅反映“是否被调试器附加”,不区分是Visual Studio、x64dbg还是其他调试器;也不代表是否处于断点暂停状态。
- x86下:PEB基址 =
__readfsdword(0x30),BeingDebugged在+0x2 - x64下:PEB基址 =
__readgsqword(0x60),BeingDebugged在+0xB - 必须用
volatile指针读取,防止编译器优化掉访问(尤其Release模式下)
跨平台编译时如何正确处理x86/x64差异
硬编码偏移容易出错,尤其是混用平台或启用LTCG时。推荐用预处理器判断架构,而非运行时检测:
#ifdef _M_X64
PEB_BASE = (BYTE*)__readgsqword(0x60);
BEING_DEBUGGED_OFFSET = 0xB;
#else
PEB_BASE = (BYTE*)__readfsdword(0x30);
BEING_DEBUGGED_OFFSET = 0x2;
#endif
bool is_debugged = *(volatile BYTE*)(PEB_BASE + BEING_DEBUGGED_OFFSET) != 0;
别用sizeof(void*)或IsWow64Process来动态判断——前者在编译期不可知,后者开销大且无法用于判断当前进程架构。
- MSVC中
_M_X64和_M_IX86是可靠宏,Clang/LLVM也兼容 - 不要试图用
GetModuleHandleA("ntdll.dll")再查NTDLL!NtQueryInformationProcess——绕远路且易被沙箱拦截 - 静态链接时(/MT),
__readgs*内建函数仍可用,无需额外库
为什么BeingDebugged可能被调试器主动清零
很多现代调试器(如x64dbg、Cheat Engine)启动后会立即把BeingDebugged设为0,以绕过基础反调试检查。这意味着该标志位单独使用极易失效。
它只适合做第一层快速筛查,不能作为唯一依据。常见误判场景包括:
- 进程由IDE启动但未打任何断点(
BeingDebugged仍为1) - 调试器已加载但尚未完成初始化(短暂为
0) - 恶意软件利用
NtSetInformationThread伪造调试状态(罕见但存在)
若业务逻辑依赖此检查,务必叠加至少一种补充手段,比如检查NTGlobalFlag(偏移0x68)、调用IsDebuggerPresent(),或验证父进程是否为vsjitdebugger.exe等。
Release模式下被优化导致检测失效的典型表现
最常被忽略的问题:MSVC在/O2下可能将整个检测逻辑优化掉,尤其当返回值未被后续代码使用时。即使加了volatile,若编译器判定该字节读取“无副作用”,仍可能跳过。
安全写法必须满足两个条件:
- 读取结果要参与实际分支逻辑(比如
if (is_debugged) exit(1);) - 禁用对该段代码的内联与删除:用
#pragma optimize("", off)包裹,或加__declspec(noinline)
另外,部分AV/EDR会在LoadLibrary阶段hook ntdll.dll导出函数,但__readgs*是CPU指令级操作,不受影响——这点正是它比IsDebuggerPresent更底层、更难被拦截的原因。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











