NtGlobalFlag 字段
一、通过检测 NtGlobalFlag 字段
我们在进行调试的过程中,可以通过两种方法来调试目标程序。第一种方法就是直接附加到一个已经运行的进程,第二种方法就是通过打开一个新的进程来进行调试。
在进程的 PEB
结构中有一个 NtGlobalFlag
字段,该字段是一个 Uint4B
类型的值,包含一系列的标志位,默认值为 0.
在 32 位系统上,NtGlobalFlag
字段位于 PEB
偏移 0x68 处,而在 64 位系统上则在偏移 0xbc 的位置。当我们通过打开一个新的进程来进行调试的时候,由调试器创建的进程会设置以下三个标志位:
FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
FLG_HEAP_ENABLE_FREE_CHECK (0x20)
FLG_HEAP_VALIDATE_PARAMETERS (0x40)
剔除掉 NtGlobalFlag
字段的其他标志位后,其值为 0x70
,因此我们可以通过检测该标志位来判断是否目标进程由调试器创建并处于调试状态。
注:这三个标志位被设置只是大概率处于被调试状态,并不包含所有情况,但这个字段常常因为这个目的而被使用。
1 通过汇编程序来检测 NtGlobalFlag 字段
因为 Windows 并没有直接提供检测该字段的函数,因此我们只能通过自己编写汇编代码来对该字段进行检测。
针对 32 位程序的汇编代码:
.386
.model flat, c
.code
Asm_IsDebugged proc
push ebp ; 保存 x86 的非易失性寄存器(ebp, ebx, esi, edi)
mov ebp, esp ; 在开辟临时栈空间之前需要保存栈顶指针(没有开辟可省略)
sub esp, 100h ; 开辟临时栈空间(没有开辟可省略)
push ebx
push esi
push edi
assume fs:nothing ; 使用 fs 寄存器
mov eax, dword ptr fs:[30h] ; 取得 PEB
mov eax, dword ptr [eax+68h] ; 得到 NtGlobalFlag 字段(Uint4B)
and eax, 70h ; 掩盖其他位
cmp eax, 70h ; 检查 NtGlobalFlag 字段的三个位是否被设置
setz al ; 取 ZF 标志位的值给 al
movzx eax, al ; 返回值
pop edi ; 恢复 x86 的非易失性寄存器(ebp, ebx, esi, edi)
pop esi
pop ebx
add esp, 100h ; 释放临时栈空间
mov esp, ebp ; 恢复栈顶指针
pop ebp
ret
Asm_IsDebugged endp
end
针对 64 位程序的汇编代码:
.code
Asm_IsDebugged proc
push rbp ; 保存 x64 的非易失性寄存器
push rbx
push rsi
push rdi
push r12
push r13
push r14
push r15
sub rsp, 100h ; 开辟临时栈空间(没有开辟可省略)
mov rax, qword ptr gs:[60h] ; 取得 PEB
mov eax, dword ptr [rax+0bch] ; 得到 NtGlobalFlag 字段(Uint4B)
and eax, 70h ; 掩盖其他位
cmp eax, 70h ; 检查 NtGlobalFlag 字段的三个位是否被设置
setz al ; 取 ZF 标志位的值给 al
movzx rax, al ; 返回值
add rsp, 100h
pop r15 ; 恢复 x64 的非易失性寄存器
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
pop rbp
ret
Asm_IsDebugged endp
end
上面的汇编代码中定义了一个函数,用于检测该字段的三个标志位是否被设置,接下来我们只需要在主程序中声明并调用即可:
#include <stdio.h>
#include <Windows.h>
extern "C" bool Asm_IsDebugged();
int main()
{
if (Asm_IsDebugged())
{
printf("可能被调试\r\n");
}
else
{
printf("没有被调试\r\n");
}
system("pause");
}
通过调试器打开程序,结果如下: