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; }
|
第二个参数是一个枚举类型,其中与反调试有关的成员有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>
typedef enum _PROCESSINFOCLASS { ProcessDebugPort = 0x7, ProcessDebugObjectHandle = 0x1E, ProcessDebugFlags = 0x1F } PROCESSINFOCLASS;
typedef LONG NTSTATUS;
#define NT_SUCCESS(status) (status >= 0)
typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)( HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength);
bool CheckDebuggerPresence() { HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); if (!hNtdll) { return false; }
pfnNtQueryInformationProcess NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtdll, "NtQueryInformationProcess"); if (!NtQueryInformationProcess) { return false; }
DWORD dwDebugPort = 0; NTSTATUS status = NtQueryInformationProcess( GetCurrentProcess(), ProcessDebugPort, &dwDebugPort, sizeof(dwDebugPort), nullptr);
if (NT_SUCCESS(status) && dwDebugPort != 0) { return true; }
HANDLE hDebugObject = nullptr; status = NtQueryInformationProcess( GetCurrentProcess(), ProcessDebugObjectHandle, &hDebugObject, sizeof(hDebugObject), nullptr);
if (NT_SUCCESS(status) && hDebugObject != nullptr) { return true; }
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))); 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))); 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的反调试技术而不需要手动设置。