反调试

windows api

IsDebuggerPresent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <windows.h>

bool isDebuggerPresent()
{
return IsDebuggerPresent();
}

int main()
{
if (isDebuggerPresent())
{
std::cout << "Debugger is present" << std::endl;
}
else
{
std::cout << "Debugger is not present" << std::endl;
}

return 0;
}

CheckRemoteDebuggerPresent

这个函数用于查询传入的句柄是否是处于调试状态的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <windows.h>

bool CheckRemoteDebuggerPresent()
{
BOOL isDebuggerPresent = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebuggerPresent);
return isDebuggerPresent;
}

int main()
{
if (CheckRemoteDebuggerPresent())
{
std::cout << "Debugger is present" << std::endl;
}
else
{
std::cout << "Debugger is not present" << std::endl;
}

return 0;
}

NtQueryInformationProcess

第二个参数是一个枚举类型,其中与反调试有关的成员有ProcessDebugPort(0x7)、ProcessDebugObjectHandle(0x1E)和ProcessDebugFlags(0x1F)。例如将该参数置为ProcessDebugPort,如果进程正在被调试,则返回调试端口,否则返回0。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <windows.h>
#include <iostream>

// 定义 PROCESSINFOCLASS 枚举类型
typedef enum _PROCESSINFOCLASS
{
ProcessDebugPort = 0x7,
ProcessDebugObjectHandle = 0x1E,
ProcessDebugFlags = 0x1F
} PROCESSINFOCLASS;

// 定义 NTSTATUS 类型
typedef LONG NTSTATUS;

// 定义 NT_SUCCESS 宏
#define NT_SUCCESS(status) (status >= 0)

// 声明 NtQueryInformationProcess 函数
typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength);

// 重命名函数以避免与 Windows API 冲突
bool CheckDebuggerPresence()
{
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll)
{
return false;
}

pfnNtQueryInformationProcess NtQueryInformationProcess =
(pfnNtQueryInformationProcess)GetProcAddress(hNtdll, "NtQueryInformationProcess");
if (!NtQueryInformationProcess)
{
return false;
}

// 检查 ProcessDebugPort
DWORD dwDebugPort = 0;
NTSTATUS status = NtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugPort,
&dwDebugPort,
sizeof(dwDebugPort),
nullptr);

if (NT_SUCCESS(status) && dwDebugPort != 0)
{
return true;
}

// 检查 ProcessDebugObjectHandle
HANDLE hDebugObject = nullptr;
status = NtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugObjectHandle,
&hDebugObject,
sizeof(hDebugObject),
nullptr);

if (NT_SUCCESS(status) && hDebugObject != nullptr)
{
return true;
}

// 检查 ProcessDebugFlags
DWORD dwDebugFlags = 0;
status = NtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugFlags,
&dwDebugFlags,
sizeof(dwDebugFlags),
nullptr);

if (NT_SUCCESS(status) && dwDebugFlags == 0)
{
return true;
}

return false;
}

int main()
{
if (CheckDebuggerPresence())
{
std::cout << "Debugger detected!" << std::endl;
}
else
{
std::cout << "No debugger detected." << std::endl;
}

return 0;
}

GetLastError

OutputDebugStringA 函数 (debugapi.h) - Win32 apps | Microsoft Learn

OutputDebugString 函数是将错误信息给调试程序,然后输出出来

如果美没有存在于调试器中的话,那么这个函数就会失效,errValue 就会被刷新

那么errvalue 就会被覆盖

errValue 就是随机的一个值

如果存在于调试器,那么这个值就不会被覆盖

GetLastError 用于获取上一次的err code

也就是说,只要errvalue 没有被改变,那么OutputDebugString 就是正常运行,存在于调试器中

反之亦然

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <windows.h>
#include <iostream>

bool CheckDebuggerPresence()
{
auto errValue = 123456;
SetLastError(errValue);
OutputDebugString("this is a debug info\n");
return !GetLastError() == errValue;
}
int main()
{
if (CheckDebuggerPresence())
{
std::cout << "Debugger detected!" << std::endl;
}
else
{
std::cout << "No debugger detected." << std::endl;
}

return 0;
}

同样的原理还可以使用以下的函数

DeleteFiber

对于DeleteFiber函数,如果给它传递一个无效的参数的话会抛出ERROR_INVALID_PARAMETER异常。如果进程正在被调试的话,异常会被调试器捕获。所以,同样可以通过验证LastError值来检测调试器的存在。如代码所示,0x57就是指ERROR_INVALID_PARAMETER。

手动检测数据结构

通过peb 进行检查

通过分析peb 的信息,进行反调试

1
2
3
4
5
6
7
8
9
10
11
BOOL CheckDebug()
{
int result = 0;
__asm
{
mov eax, fs:[30h]
mov al, BYTE PTR [eax + 2]
mov result, al
}
return result != 0;
}

进程运行时,位置fs:[30h]指向PEB的基地址。为了实现反调试技术,恶意代码通过这个位置检查BeingDebugged标志,这个标志标识进程是否正在被调试。

检测ProcessHeap属性

Reserved数组中一个未公开的位置叫作ProcessHeap,它被设置为加载器为进程分配的第一个堆的位置。ProcessHeap位于PEB结构的0x18处。第一个堆头部有一个属性字段,它告诉内核这个堆是否在调试器中创建。这些属性叫作ForceFlags和Flags。在Windows XP系统中,ForceFlags属性位于堆头部偏移量0x10处;在Windows 7系统中,对于32位的应用程序来说ForceFlags属性位于堆头部偏移量0x44处。

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
BOOL CheckDebug()
{
int result = 0;
DWORD dwVersion = GetVersion();
DWORD dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
//for xp
if (dwWindowsMajorVersion == 5)
{
__asm
{
mov eax, fs:[30h]
mov eax, [eax + 18h]
mov eax, [eax + 10h]
mov result, eax
}
}
else
{
__asm
{
mov eax, fs:[30h]
mov eax, [eax + 18h]
mov eax, [eax + 44h]
mov result, eax
}
}
return result != 0;
}

同样,恶意代码也可以检查Windows XP系统中偏移量0x0C处,或者Windows 7系统中偏移量0x40处的Flags属性。这个属性总与ForceFlags属性大致相同,但通常情况下Flags与值2进行比较。

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
BOOL CheckDebug()
{
int result = 0;
DWORD dwVersion = GetVersion();
DWORD dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
//for xp
if (dwWindowsMajorVersion == 5)
{
__asm
{
mov eax, fs:[30h]
mov eax, [eax + 18h]
mov eax, [eax + 0ch]
mov result, eax
}
}
else
{
__asm
{
mov eax, fs:[30h]
mov eax, [eax + 18h]
mov eax, [eax + 40h]
mov result, eax
}
}
return result != 2;
}

检测NTGlobalFlag

由于调试器中启动进程与正常模式下启动进程有些不同,所以它们创建内存堆的方式也不同。系统使用PEB结构偏移量0x68处的一个未公开位置,来决定如何创建堆结构。如果这个位置的值为0x70,我们就知道进程正运行在调试器中。

1
2
3
4
5
6
7
8
9
10
11
12
BOOL CheckDebug()
{
int result = 0;
__asm
{
mov eax, fs:[30h]
mov eax, [eax + 68h]
and eax, 0x70
mov result, eax
}
return result != 0;
}

操作系统创建堆时,值0x70是下列标志的一个组合。如果进程从调试器启动,那么进程的这些标志将被设置。
(FLG_HEAP_ENABLE_TAIL_CHECK|FLG_HEAP_ENABLE_FREE_CHECK|FLG_HEAP_VALIDATE_PARAMETERS)
避免这种问题方法和前面的差不多。如果用OllyDbg的命令行插件修改,输入的命令为dump fs:[30]+0x68。如果用PhantOm插件,它会逃避使用NTGlobalFlag的反调试技术而不需要手动设置。


反调试
https://tsy244.github.io/2025/02/25/逆向/反调试/
Author
August Rosenberg
Posted on
February 25, 2025
Licensed under