调试标志
系统表中的特殊标志(驻留在进程内存中并由操作系统设置)可以用来指示进程正在调试。可以通过使用特定的API函数或检查内存中的系统表来验证这些标志的状态。
这些技术是恶意软件最常用的技术。
1.使用Win32 API
下列技术使用现有的API函数(WinAPI或NativeAPI),它们检查进程内存中的系统结构是否有特定的标志,这些标志指示进程正在立即调试。
1.1. IsDebuggerPresent()
函数kernel32!IsDebuggerPresent()
确定当前进程是否正在由用户模式调试器(例如OllyDbg或x64dbg)调试。通常,该函数仅检查进程环境块(PEB)的BeingDebugged
标志。
如果正在调试,以下代码可用于终止进程:
汇编代码
call IsDebuggerPresent
test al, al
jne being_debugged
...
being_debugged:
push 1
call 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 eax
push -1 ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
push -1
call ExitProcess
x86-64汇编
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 ++代码
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 ; 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汇编
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 ++代码
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 ; 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汇编
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
调试开始时,将创建一个称为“调试对象”的内核对象。可以通过使用未记录的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 ; 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汇编
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.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], 0
jne being_debugged
64位进程
mov rax, gs:[60h]
cmp byte ptr [rax+2], 0
jne being_debugged
WOW64进程
mov eax, fs:[30h]
cmp byte ptr [eax+1002h], 0
C / C ++代码
#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
进程环境块(在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, 70h
cmp al, 70h
jz being_debugged
64位进程
mov rax, gs:[60h]
mov al, [rax+BCh]
and al, 70h
cmp al, 70h
jz being_debugged
WOW64进程
mov eax, fs:[30h]
mov al, [eax+10BCh]
and al, 70h
cmp al, 70h
jz 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 _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
。
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 _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位为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 _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
#endif // _WIN64
pPeb->BeingDebugged = 0;
对于NtGlobalFlag:
将NtGlobalFlag
设置为0。这可以通过DLL注入来完成。如果使用OllyDbg或x32 / 64dbg作为调试器,则可以选择各种Anti-Debug插件,例如ScyllaHide。
#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
对于堆标志:
将Flags
值设置为HEAP_GROWABLE
,并将ForceFlags
值设置为0。这可以通过DLL注入来完成。如果使用OllyDbg或x32 / 64dbg作为调试器,则可以选择各种Anti-Debug插件,例如ScyllaHide。
#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;
对于堆保护:
在堆之后手动修补12个字节(32位)或20个字节(64位)。hookkernel32!HeapAlloc()
,在分配堆后并修补堆。
#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;