调试标志
系统表中的特殊标志(驻留在进程内存中并由操作系统设置)可以用来指示进程正在调试。可以通过使用特定的API函数或检查内存中的系统表来验证这些标志的状态。
这些技术是恶意软件最常用的技术。
1.使用Win32 API
下列技术使用现有的API函数(WinAPI或NativeAPI),它们检查进程内存中的系统结构是否有特定的标志,这些标志指示进程正在立即调试。
1.1. IsDebuggerPresent()
函数kernel32!IsDebuggerPresent()确定当前进程是否正在由用户模式调试器(例如OllyDbg或x64dbg)调试。通常,该函数仅检查进程环境块(PEB)的BeingDebugged标志。
如果正在调试,以下代码可用于终止进程:
汇编代码
call IsDebuggerPresenttest al, aljne being_debugged...being_debugged:push 1call ExitProcess
C / C ++代码
if (IsDebuggerPresent())ExitProcess(-1);
1.2. CheckRemoteDebuggerPresent()
函数kernel32!CheckRemoteDebuggerPresent()检查调试器(在同一计算机上的不同进程中)是否已附加到当前进程。
C / C ++代码
BOOL bDebuggerPresent;if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) &&TRUE == bDebuggerPresent)ExitProcess(-1);
x86汇编
lea eax, bDebuggerPresent]push eaxpush -1 ; GetCurrentProcess()call CheckRemoteDebuggerPresentcmp [bDebuggerPresent], 1jz being_debugged...being_debugged:push -1call ExitProcess
x86-64汇编
lea rdx, [bDebuggerPresent]mov rcx, -1 ; GetCurrentProcess()call CheckRemoteDebuggerPresentcmp [bDebuggerPresent], 1jz being_debugged...being_debugged:mov ecx, -1call ExitProcess
1.3. NtQueryInformationProcess()
函数ntdll!NtQueryInformationProcess()可以从进程中检索不同类型的信息。它接受ProcessInformationClass参数,该参数指定要获取的信息,并定义ProcessInformation参数的输出类型。
1.3.1. ProcessDebugPort
可以使用ntdll!NtQueryInformationProcess()来检索该进程的调试器的端口号。有一个已记录的类ProcessDebugPort,如果正在调试该进程,它将检索等于0xFFFFFFFF(十进制-1)的DWORD值。
C / C ++代码
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汇编
lea eax, [dwReturned]push eax ; ReturnLengthpush 4 ; ProcessInformationLengthlea ecx, [dwProcessDebugPort]push ecx ; ProcessInformationpush 7 ; ProcessInformationClasspush -1 ; ProcessHandlecall NtQueryInformationProcessinc dword ptr [dwProcessDebugPort]jz being_debugged...being_debugged:push -1call ExitProcess
x86-64汇编
lea rcx, [dwReturned]push rcx ; ReturnLengthmov r9d, 4 ; ProcessInformationLengthlea r8, [dwProcessDebugPort]; ProcessInformationmov edx, 7 ; ProcessInformationClassmov rcx, -1 ; ProcessHandlecall NtQueryInformationProcesscmp dword ptr [dwProcessDebugPort], -1jz being_debugged...being_debugged:mov ecx, -1call ExitProcess
1.3.2. ProcessDebugFlags
代表进程对象的名为EPROCESS的内核结构包含字段NoDebugInherit。可以使用未记录的类ProcessDebugFlags(0x1f)检索此字段的逆值。因此,如果返回值为0,则存在调试器。
C / C ++代码
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汇编
lea eax, [dwReturned]push eax ; ReturnLengthpush 4 ; ProcessInformationLengthlea ecx, [dwProcessDebugPort]push ecx ; ProcessInformationpush 1Fh ; ProcessInformationClasspush -1 ; ProcessHandlecall NtQueryInformationProcesscmp dword ptr [dwProcessDebugPort], 0jz being_debugged...being_debugged:push -1call ExitProcess
x86-64汇编
lea rcx, [dwReturned]push rcx ; ReturnLengthmov r9d, 4 ; ProcessInformationLengthlea r8, [dwProcessDebugPort]; ProcessInformationmov edx, 1Fh ; ProcessInformationClassmov rcx, -1 ; ProcessHandlecall NtQueryInformationProcesscmp dword ptr [dwProcessDebugPort], 0jz being_debugged...being_debugged:mov ecx, -1call ExitProcess
1.3.3. ProcessDebugObjectHandle
调试开始时,将创建一个称为“调试对象”的内核对象。可以通过使用未记录的ProcessDebugObjectHandle(0x1e)类来查询此句柄的值。
C / C ++代码
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汇编
lea eax, [dwReturned]push eax ; ReturnLengthpush 4 ; ProcessInformationLengthlea ecx, [hProcessDebugObject]push ecx ; ProcessInformationpush 1Eh ; ProcessInformationClasspush -1 ; ProcessHandlecall NtQueryInformationProcesscmp dword ptr [hProcessDebugObject], 0jnz being_debugged...being_debugged:push -1call ExitProcess
x86-64汇编
lea rcx, [dwReturned]push rcx ; ReturnLengthmov r9d, 4 ; ProcessInformationLengthlea r8, [hProcessDebugObject]; ProcessInformationmov edx, 1Fh ; ProcessInformationClassmov rcx, -1 ; ProcessHandlecall NtQueryInformationProcesscmp dword ptr [hProcessDebugObject], 0jnz being_debugged...being_debugged:mov ecx, -1call ExitProcess
1.3.4 ProcessBasicInformation
ProcessInformationClass值为0,检索指向可用于确定指定进程是否正在调试的PEB结构的指针,以及系统用于标识指定进程的唯一值。
使用CheckRemoteDebuggerPresent和GetProcessId 函数来获取此信息。
1.4. RtlQueryProcessHeapInformation()
所述NTDLL!RtlQueryProcessHeapInformation()函数可以被用来从当前进程的进程存储器读出的堆标志。
C / C ++代码
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 ++代码
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类返回两个标志的值:KdDebuggerEnabled的人,和KdDebuggerNotPresent的啊。因此,如果存在内核调试器,则ah中的返回值为零。
C / C ++代码
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)的正在调试标志设置为0。 - 对于
CheckRemoteDebuggerPresent()和NtQueryInformationProcess():
由于CheckRemoteDebuggerPresent()调用NtQueryInformationProcess(),唯一的方法是挂接NtQueryInformationProcess()并在返回缓冲区中设置以下值: - 如果是
ProcessDebugPort查询,则为0(或-1以外的任何值)。 - 如果是
ProcessDebugFlags查询,则为非零值。 - 如果是
ProcessDebugObjectHandle查询,则为0 。
- 如果是
- 使用
RtlQueryProcessHeapInformation(),RtlQueryProcessDebugInformation()和NtQuerySystemInformation()函数减轻这些检查的唯一方法是钩住它们并修改返回的值: RTL_PROCESS_HEAPS :: HeapInformation :: Heaps [0] :: Flags到HEAP_GROWABLE的RtlQueryProcessHeapInformation()和RtlQueryProcessDebugInformation()。- 如果是
SystemKernelDebuggerInformation查询,则NtQuerySystemInformation()函数的SYSTEM_KERNEL_DEBUGGER_INFORMATION :: DebuggerEnabled设置为0,SYSTEM_KERNEL_DEBUGGER_INFORMATION :: DebuggerNotPresent设置为1 。
2.手动检查
以下方法用于验证系统结构中的调试标志。他们无需使用特殊的调试API函数即可手动检查进程内存。
2.1. PEB!BeingDebugged标志
此方法只是不调用IsDebuggerPresent()即可检查PEB的BeingDebugged标志的另一种方法。
32位进程
mov eax, fs:[30h]cmp byte ptr [eax+2], 0jne being_debugged
64位进程
mov rax, gs:[60h]cmp byte ptr [rax+2], 0jne being_debugged
WOW64进程
mov eax, fs:[30h]cmp byte ptr [eax+1002h], 0
C / C ++代码
#ifndef _WIN64PPEB pPeb = (PPEB)__readfsdword(0x30);#elsePPEB pPeb = (PPEB)__readgsqword(0x60);#endif // _WIN64if (pPeb->BeingDebugged)goto being_debugged;
2.2. NtGlobalFlag
所述NtGlobalFlag进程环境块(在Windows32位偏移0x68、64位偏移0xBC)的默认值为0。附加调试器不会更改NtGlobalFlag的值。但是,如果该进程是由调试器创建的,则将设置以下标志:
FLG_HEAP_ENABLE_TAIL_CHECK(0x10)FLG_HEAP_ENABLE_FREE_CHECK(0x20)FLG_HEAP_VALIDATE_PARAMETERS(0x40)
可以通过检查这些标志的组合来检测调试器的存在。
32位进程
mov eax, fs:[30h]mov al, [eax+68h]and al, 70hcmp al, 70hjz being_debugged
64位进程
mov rax, gs:[60h]mov al, [rax+BCh]and al, 70hcmp al, 70hjz being_debugged
WOW64进程
mov eax, fs:[30h]mov al, [eax+10BCh]and al, 70hcmp al, 70hjz being_debugged
C / C ++代码
#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 _WIN64PPEB pPeb = (PPEB)__readfsdword(0x30);DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68);#elsePPEB pPeb = (PPEB)__readgsqword(0x60);DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC);#endif // _WIN64if (dwNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED)goto being_debugged;
2.3. 堆标志
堆包含两个字段,这些字段受调试器的存在影响。它们具体值取决于Windows版本。这些字段是Flags和ForceFlags。
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及更高版本上,如果存在调试器,则“标志”字段将设置为以下标志的组合:
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 ++代码
bool Check(){#ifndef _WIN64PPEB 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;#elsePPEB pPeb = (PPEB)__readgsqword(0x60);PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30));DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater()? 0x70: 0x14;DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater()? 0x74: 0x18;#endif // _WIN64PDWORD 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位为2次,64位Windows 为4次)在分配的堆块的末端。
如果NtGlobalFlag设置了HEAP_FREE_CHECKING_ENABLED标志,如果需要额外的字节来填充空白空间,序列0xFEEEFEEE将会被追加,直到下一个存储块。
C / C ++代码
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作为调试器,则可以选择各种Anti-Debug插件,例如ScyllaHide。
#ifndef _WIN64PPEB pPeb = (PPEB)__readfsdword(0x30);#elsePPEB pPeb = (PPEB)__readgsqword(0x60);#endif // _WIN64pPeb->BeingDebugged = 0;
对于NtGlobalFlag:
将NtGlobalFlag设置为0。这可以通过DLL注入来完成。如果使用OllyDbg或x32 / 64dbg作为调试器,则可以选择各种Anti-Debug插件,例如ScyllaHide。
#ifndef _WIN64PPEB pPeb = (PPEB)__readfsdword(0x30);*(PDWORD)((PBYTE)pPeb + 0x68) = 0;#elsePPEB pPeb = (PPEB)__readgsqword(0x60);*(PDWORD)((PBYTE)pPeb + 0xBC); = 0;#endif // _WIN64
对于堆标志:
将Flags值设置为HEAP_GROWABLE,并将ForceFlags值设置为0。这可以通过DLL注入来完成。如果使用OllyDbg或x32 / 64dbg作为调试器,则可以选择各种Anti-Debug插件,例如ScyllaHide。
#ifndef _WIN64PPEB 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;#elsePPEB 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;
对于堆保护:
在堆之后手动修补12个字节(32位)或20个字节(64位)。hookkernel32!HeapAlloc(),在分配堆后并修补堆。
#ifndef _WIN64SIZE_T nBytesToPatch = 12;#elseSIZE_T nBytesToPatch = 20;#endif // _WIN64SIZE_T nDwordsToPatch = nBytesToPatch / sizeof(DWORD);PVOID pHeapEnd = (PBYTE)HeapEntry.lpData + HeapEntry.cbData;for (SIZE_T offset = 0; offset < nDwordsToPatch; offset++)*((PDWORD)pHeapEnd + offset) = 0;
