调试标志寄存器
系统表中的特殊标志寄存器,即停留在进程内存中的、由操作系统设置的标志寄存器,可以用来指示进程正在被调试。这些标志寄存器的状态可以通过使用特定的API函数或检查内存中的系统表来验证。
这些技术是恶意软件最常使用的。
1. 使用Win32 API
以下技术使用现有的API函数(WinAPI或NativeAPI),这些函数检查进程内存中的系统结构,以寻找表明进程现在正在被调试的特定标志寄存器。
1.1. IsDebuggerPresent()
函数kernel32!IsDebuggerPresent()确定当前进程是否被用户模式的调试器如OllyDbg或x64dbg调试。一般来说,该函数只检查进程环境块(PEB)的 BeingDebugged 标志寄存器。
如果正在调试进程,可以使用以下代码终止进程:
汇编代码:
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); |
1.2. CheckRemoteDebuggerPresent()
函数kernel32!CheckRemoteDebuggerPresent()检查一个调试器(在同一台机器的不同进程中)是否连接到当前进程。
C/C++ 代码:
1 2 3 4 | BOOL bDebuggerPresent; if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) && TRUE == bDebuggerPresent) ExitProcess(-1); |
x86 汇编:
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 |
x86-64 汇编:
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 |
1.3. NtQueryInformationProcess()
函数ntdll!NtQueryInformationProcess()可以从一个进程中检索不同种类的信息。它接受一个ProcessInformationClass参数,该参数指定了你想得到的信息,并定义了ProcessInformation参数的输出类型。
1.3.1. 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 28 | typedef NTSTATUS (NTAPI *TNtQueryInformationProcess)( 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 汇编:
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 |
x86-64 汇编:
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 |
1.3.2. 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 汇编:
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 1Fh ; ProcessInformationClass push -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [dwProcessDebugPort], 0 jz being_debugged ... being_debugged: push -1 call ExitProcess |
x86-64 汇编:
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, 1Fh ; ProcessInformationClass mov rcx, -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [dwProcessDebugPort], 0 jz being_debugged ... being_debugged: mov ecx, -1 call ExitProcess |
1.3.3. 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 汇编:
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, [hProcessDebugObject] push ecx ; ProcessInformation push 1Eh ; ProcessInformationClass push -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [hProcessDebugObject], 0 jnz being_debugged ... being_debugged: push -1 call ExitProcess |
x86-64 汇编:
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, 1Fh ; ProcessInformationClass mov rcx, -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [hProcessDebugObject], 0 jnz being_debugged ... being_debugged: mov ecx, -1 call ExitProcess |
1.4. RtlQueryProcessHeapInformation()
ntdll!RtlQueryProcessHeapInformation()函数可以用来从当前进程的进程内存中读取堆标志寄存器。
C/C++ 代码:
1 2 3 4 5 6 7 8 9 | bool Check() { ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); if (!SUCCEEDED(ntdll::RtlQueryProcessHeapInformation((ntdll::PRTL_DEBUG_INFORMATION)pDebugBuffer))) return false; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; return dwFlags & ~HEAP_GROWABLE; } |
1.5. RtlQueryProcessDebugInformation()
ntdll!RtlQueryProcessDebugInformation()函数可用于从被请求进程的进程内存中读取某些字段,包括堆标志寄存器。
C/C++ 代码:
1 2 3 4 5 6 7 8 9 | bool Check() { ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); if (!SUCCEEDED(ntdll::RtlQueryProcessDebugInformation(GetCurrentProcessId(), ntdll::PDI_HEAPS | ntdll::PDI_HEAP_BLOCKS, pDebugBuffer))) return false; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; return dwFlags & ~HEAP_GROWABLE; } |
1.6. NtQuerySystemInformation()
ntdll!NtQuerySystemInformation()函数接受一个参数,即要查询的信息类别。大多数类都没有被记录下来。这包括SystemKernelDebuggerInformation(0x23)类,它从Windows NT开始就存在了。SystemKernelDebuggerInformation类返回两个标志寄存器的值。al中的KdDebuggerEnabled,和ah中的KdDebuggerNotPresent。因此,如果内核调试器存在,ah中的返回值为零。
C/C++ 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | enum { SystemKernelDebuggerInformation = 0x23 }; typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION { BOOLEAN DebuggerEnabled; BOOLEAN DebuggerNotPresent; } SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION; bool Check() { NTSTATUS status; SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInfo;
status = NtQuerySystemInformation( (SYSTEM_INFORMATION_CLASS)SystemKernelDebuggerInformation, &SystemInfo, sizeof(SystemInfo), NULL); return SUCCEEDED(status) ? (SystemInfo.DebuggerEnabled && !SystemInfo.DebuggerNotPresent) : false; } |
反制措施
-
对于IsDebuggerPresent()。将进程环境块(PEB)的BeingDebugged标志设置为0。 更多信息请参见BeingDebugged 标志寄存器反制措施。
-
对于CheckRemoteDebuggerPresent()和NtQueryInformationProcess():
由于CheckRemoteDebuggerPresent()调用NtQueryInformationProcess(),唯一的方法是拦截NtQueryInformationProcess()并在返回缓冲区设置以下值。-
如果是ProcessDebugPort查询,则为0(或除-1外的任何值)。
-
如果是ProcessDebugFlags查询,则为非零值。
-
在ProcessDebugObjectHandle查询的情况下为0。
-
-
用RtlQueryProcessHeapInformation()、RtlQueryProcessDebugInformation()和NtQuerySystemInformation()函数反制这些检查的唯一方法是拦截它们并修改返回值:
-
RTL_PROCESS_HEAPS::HeapInformation::Heaps[0]:标志寄存器改为HEAP_GROWABLE,用于RtlQueryProcessHeapInformation()和RtlQueryProcessDebugInformation()。
-
SYSTEM_KERNEL_DEBUGGER_INFORMATION::DebuggerEnabled为0和系统_KERNEL_DEBUGGER_INFORMATION::DebuggerNotPresent为1。NtQuerySystemInformation()函数在查询SystemKernelDebuggerInformation的情况下。
2. 手工检查
以下方法用于验证系统结构中的调试标志。他们手工检查进程的内存,而不使用特殊的调试API函数。
2.1. PEB!正在调试标志寄存器
这个方法只是检查PEB的BeingDebugged标志寄存器[的另一种方法,而不需要调用IsDebuggerPresent()。
32位进程:
1 2 3 | mov eax, fs:[30h] cmp byte ptr [eax+2], 0 jne being_debugged |
64位进程:
1 2 3 | mov rax, gs:[60h] cmp byte ptr [rax+2], 0 jne being_debugged |
WOW64 进程:
1 2 | mov eax, fs:[30h] cmp byte ptr [eax+1002h], 0 |
C/C++ 代码:
1 2 3 4 5 6 7 8 | #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); #else PPEB pPeb = (PPEB)__readgsqword(0x60); #endif // _WIN64
if (pPeb->BeingDebugged) goto being_debugged; |
2.2. NtGlobalFlag
进程环境块的NtGlobalFlag字段(32位Windows的0x68偏移,64位Windows的0xBC)默认为0。附加一个调试器并不改变NtGlobalFlag的值。但是,如果进程是由调试器创建的,则将设置以下标志寄存器:
-
FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
-
FLG_HEAP_ENABLE_FREE_CHECK (0x20)
-
FLG_HEAP_VALIDATE_PARAMETERS (0x40)
调试器的存在可以通过检查这些标志寄存器的组合来检测。
32位进程:
1 2 3 4 5 | mov eax, fs:[30h] mov al, [eax+68h] and al, 70h cmp al, 70h jz being_debugged |
64位进程:
1 2 3 4 5 | mov rax, gs:[60h] mov al, [rax+BCh] and al, 70h cmp al, 70h jz being_debugged |
WOW64 进程:
1 2 3 4 5 | mov eax, fs:[30h] mov al, [eax+10BCh] and al, 70h cmp al, 70h jz being_debugged |
C/C++ 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #define FLG_HEAP_ENABLE_TAIL_CHECK 0x10 #define FLG_HEAP_ENABLE_FREE_CHECK 0x20 #define FLG_HEAP_VALIDATE_PARAMETERS 0x40 #define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS) #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68); #else PPEB pPeb = (PPEB)__readgsqword(0x60); DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC); #endif // _WIN64
if (dwNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED) goto being_debugged; |
2.3. 堆标志寄存器
堆包含两个字段,它们会受到调试器存在的影响。具体如何影响,取决于Windows的版本。这些字段是标志寄存器(Flags)和强标志寄存器(ForceFlags)。
标志寄存器和强标志寄存器的值通常分别设置为HEAP_GROWABLE和0。
当调试器出现时,在Windows NT、Windows 2000和32位Windows XP上,标志寄存器字段被设置为这些标志寄存器的组合。
-
HEAP_GROWABLE (2)
-
HEAP_TAIL_CHECKING_ENABLED (0x20)
-
HEAP_FREE_CHECKING_ENABLED (0x40)
-
HEAP_SKIP_VALIDATION_CHECKS (0x10000000)
-
HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)
在64位的Windows XP和Windows Vista及更高版本中,如果有调试器存在,Flags字段会被设置为这些标志寄存器组合:
-
HEAP_GROWABLE (2)
-
HEAP_TAIL_CHECKING_ENABLED (0x20)
-
HEAP_FREE_CHECKING_ENABLED (0x40)
-
HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)
当调试器出现时,ForceFlags字段被设置为这些标志寄存器的组合:
-
HEAP_TAIL_CHECKING_ENABLED (0x20)
-
HEAP_FREE_CHECKING_ENABLED (0x40)
-
HEAP_VALIDATE_PARAMETERS_ENABLED (0x40000000)
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 | bool Check() { #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); PVOID pHeapBase = !m_bIsWow64 ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x40 : 0x0C; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x44 : 0x10; #else PPEB pPeb = (PPEB)__readgsqword(0x60); PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x70 : 0x14; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x74 : 0x18; #endif // _WIN64 PDWORD pdwHeapFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset); PDWORD pdwHeapForceFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset); return (*pdwHeapFlags & ~HEAP_GROWABLE) || (*pdwHeapForceFlags != 0); } |
2.3.堆保护
如果在NtGlobalFlag中设置了HEAP_TAIL_CHECKING_ENABLED标志寄存器,序列0xABABABAB将被附加在所分配的堆块的末端(在32位Windows中为2次,在64位Windows中为4次)。
如果在NtGlobalFlag中设置了HEAP_FREE_CHECKING_ENABLED标志寄存器,如果需要额外的字节来填充空的空间,直到下一个内存块,那么将附加序列0xFEEEFEEE。
C/C++ 代码:
1 2 3 4 5 6 7 8 9 10 11 12 | bool Check() { PROCESS_HEAP_ENTRY HeapEntry = { 0 }; do { if (!HeapWalk(GetProcessHeap(), &HeapEntry)) return false; } while (HeapEntry.wFlags != PROCESS_HEAP_ENTRY_BUSY); PVOID pOverlapped = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; return ((DWORD)(*(PDWORD)pOverlapped) == 0xABABABAB); } |
反制措施
对于PEB!BeingDebugged标志寄存器:
将BeingDebugged标志设置为0,这可以通过DLL注入完成。如果你使用OllyDbg或x32/64dbg作为调试器,你可以选择各种反调试插件,如ScyllaHide。
1 2 3 4 5 6 | #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); #else PPEB pPeb = (PPEB)__readgsqword(0x60); #endif // _WIN64 pPeb->BeingDebugged = 0; |
对于NtGlobalFlag:
将NtGlobalFlag设置为0,这可以通过DLL注入完成。如果你使用OllyDbg或x32/64dbg作为调试器,你可以选择各种反调试插件,如ScyllaHide。
1 2 3 4 5 6 7 | #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); *(PDWORD)((PBYTE)pPeb + 0x68) = 0; #else PPEB pPeb = (PPEB)__readgsqword(0x60); *(PDWORD)((PBYTE)pPeb + 0xBC); = 0; #endif // _WIN64 |
对于堆标志寄存器:
设置标志寄存器值为HEAP_GROWABLE,ForceFlags值为0,这可以通过DLL注入完成。如果你使用OllyDbg或x32/64dbg作为调试器,你可以选择各种反调试插件,如ScyllaHide。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); PVOID pHeapBase = !m_bIsWow64 ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x40 : 0x0C; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x44 : 0x10; #else PPEB pPeb = (PPEB)__readgsqword(0x60); PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x70 : 0x14; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x74 : 0x18; #endif // _WIN64 *(PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset) = HEAP_GROWABLE; *(PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset) = 0; |
对于堆保护:
手工修补32位的12字节和64位环境下的20字节的堆。拦截kernel32!HeapAlloc()并在其分配后修补堆。
1 2 3 4 5 6 7 8 9 | #ifndef _WIN64 SIZE_T nBytesToPatch = 12; #else SIZE_T nBytesToPatch = 20; #endif // _WIN64 SIZE_T nDwordsToPatch = nBytesToPatch / sizeof(DWORD); PVOID pHeapEnd = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; for (SIZE_T offset = 0; offset < nDwordsToPatch; offset++) *((PDWORD)pHeapEnd + offset) = 0; |
备注:
本文投稿来自:作者王建达
原文地址:https://anti-debug.checkpoint.com/techniques/debug-flags.html
原文标题:Anti-Debug: DebugFlags