初识反调试

IsDebuggerPresent()函数

包含在debugapi.h头文件中,函数原型:

1
BOOL IsDebuggerPresent();   //未在调试器中运行时返回值为零,否则为非零值

该函数允许程序确定是否正在由用户模式调试器(如 OllyDbg 或 x64dbg)调试它,以便可以修改其行为。通常,该函数只检查进程环境块 (PEB) 的 BeingDebugged 标志。

Assembly:

1
2
3
4
5
6
7
    call IsDebuggerPresent    
test al, al
jne being_debugged
...
being_debugged:
push 1
call ExitProcess

C/C++:

1
2
if (IsDebuggerPresent())
ExitProcess(-1);

CheckRemoteDebuggerPresent()函数

包含在kernel32中的CheckRemoteDebuggerPresent()检查调试器(在同一台计算机上的不同进程中)是否连接到当前进程。

X86 Assembly:

1
2
3
4
5
6
7
8
9
10
    lea eax, [bDebuggerPresent]
push eax
push -1 ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
push -1
call ExitProcess

X64 Assembly:

1
2
3
4
5
6
7
8
9
    lea rdx, [bDebuggerPresent]
mov rcx, -1 ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

C/C++:

1
2
3
4
BOOL bDebuggerPresent;
if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProces(), &bDebuggerPresent) &&
TRUE == bDebuggerPresent)
ExitProcess(-1);

PEB->BeingDebugged

FS标志段寄存器总是指向TEB(当前的线程环境块),其中包含一个指针指向当前PEB(进程环境块),该结构体包含一个成员BeingDebugged,它是一个标志位,用于标识当前进程是否正在被调试

NtQueryInformationProcess()函数

该函数包含在ntdll.h头文件中,可以从进程中检索不同类型的信息。它接受一个ProcessInformationClass参数,该参数指定要获取的信息并定义ProcessInformation参数的输出类型。

ProcessDebugPort

可以使用ntdll检索进程的调试器的端口号!NtQueryInformationProcess()的有一个记录在案的类ProcessDebugPort,如果正在调试进程,则该类将检索等于 0xFFFFFFFF(十进制 -1) 的 DWORD 值,C/C++语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__kernel_entry NTSTATUS NtQueryInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength
);
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");

if (pfnNtQueryInformationProcess)
{
DWORD dwProcessDebugPort, dwReturned;
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugPort,
&dwProcessDebugPort,
sizeof(DWORD),
&dwReturned);

if (NT_SUCCESS(status) && (-1 == dwProcessDebugPort))
ExitProcess(-1);
}
}

X86 Assembly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    lea eax, [dwReturned]
push eax ; ReturnLength
push 4 ; ProcessInformationLength
lea ecx, [dwProcessDebugPort]
push ecx ; ProcessInformation
push 7 ; ProcessInformationClass
push -1 ; ProcessHandle
call NtQueryInformationProcess
inc dword ptr [dwProcessDebugPort]
jz being_debugged
...
being_debugged:
push -1
call ExitProcess

X64 Assembly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    lea rcx, [dwReturned]
push rcx ; ReturnLength
mov r9d, 4 ; ProcessInformationLength
lea r8, [dwProcessDebugPort]
; ProcessInformation
mov edx, 7 ; ProcessInformationClass
mov rcx, -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugPort], -1
jz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

ProcessDebugFlags

名为EPROCESS的内核结构(表示进程对象)包含字段 NoDebugInherit。可以使用未记录的类 ProcessDebugFlags(0x1f)检索此字段的反向值。因此,如果返回值为 0,则存在调试器。

C/C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
typedef NTSTATUS(NTAPI *TNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN DWORD ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength
);

HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");

if (pfnNtQueryInformationProcess)
{
DWORD dwProcessDebugFlags, dwReturned;
const DWORD ProcessDebugFlags = 0x1f;
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugFlags,
&dwProcessDebugFlags,
sizeof(DWORD),
&dwReturned);

if (NT_SUCCESS(status) && (0 == dwProcessDebugFlags))
ExitProcess(-1);
}
}

X86 Assembly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    lea eax, [dwReturned]
push eax ; ReturnLength
push 4 ; ProcessInformationLength
lea ecx, [dwProcessDebugFlags]
push ecx ; ProcessInformation
push 1Fh ; ProcessInformationClass
push -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugFlags], 0
jz being_debugged
...
being_debugged:
push -1
call ExitProcess

X64 Assembly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    lea rcx, [dwReturned]
push rcx ; ReturnLength
mov r9d, 4 ; ProcessInformationLength
lea r8, [dwProcessDebugFlags]
; ProcessInformation
mov edx, 1Fh ; ProcessInformationClass
mov rcx, -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugFlags], 0
jz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

ProcessDebugObjectHandle

调试开始时,将创建一个名为debug object的内核对象。可以使用未记录的ProcessDebugObjectHandle(0x1e)类来查询此句柄的值。

C/C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef NTSTATUS(NTAPI * TNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN DWORD ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength
);

HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");

if (pfnNtQueryInformationProcess)
{
DWORD dwReturned;
HANDLE hProcessDebugObject = 0;
const DWORD ProcessDebugObjectHandle = 0x1e;
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugObjectHandle,
&hProcessDebugObject,
sizeof(HANDLE),
&dwReturned);

if (NT_SUCCESS(status) && (0 != hProcessDebugObject))
ExitProcess(-1);
}
}

X86 Assembly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    lea eax, [dwReturned]
push eax ; ReturnLength
push 4 ; ProcessInformationLength
lea ecx, [dwProcessDebugFlags]
push ecx ; ProcessInformation
push 1Fh ; ProcessInformationClass
push -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugFlags], 0
jz being_debugged
...
being_debugged:
push -1
call ExitProcess

X64 Assembly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    lea rcx, [dwReturned]
push rcx ; ReturnLength
mov r9d, 4 ; ProcessInformationLength
lea r8, [hProcessDebugObject]
; ProcessInformation
mov edx, 1Eh ; ProcessInformationClass
mov rcx, -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [hProcessDebugObject], 0
jnz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess

其检索一个WORD_PTR值,该值是进程的调试器端口号。非零值指示进程正在环3调试器的控制下运行,如果进程没有调试器,则返回零

EPROCESS_DebugPort

获取系统内核中标记进程信息的结构体EPROCESS,通过EPROCESS结构体中的DebugPort成员判断进程是否正在被调试,一般无法使用普通方法实现,需要内核调试

5. 异常处理检测

处理异常时,正常运行过程会将信息发给Windows的SEH异常捕获流程,而进行调试时则会发给调试器

6. 断点检测

函数断点体现为0xCC,可通过对比内存数据中的指令数据与磁盘内文件的数据进行对比,如发现存在不同,则说明有断点,即程序被调试

7. 检测调试器进程

通过枚举进程,判断进程名是否为调试器进程名,如果是,则说明程序正在被调试

还可通过查看是否存在调试器的窗口,判断程序是否正在被调试。可使用VC++中附带的Spy++查找窗口名

8. 时间检测

程序运行时,如果被调试,则由于会出现单步运行等过程,程序运行速度会变慢,因此可通过检测程序运行时间,判断程序是否正在被调试