8.4 ProcessHeap

ProcessHeap 是Windows进程的默认堆,每个进程都有一个默认的堆,用于在进程地址空间中分配内存空间。默认情况下ProcessHeap由内核进行初始化,该堆中存在一个未公开的属性,它被设置为加载器为进程分配的第一个堆的位置(进程堆标志),ProcessHeap标志位于PEB结构中偏移为0x18处,第一个堆头部有一个属性字段,这个属性叫做ForceFlags属性偏移为0x44,该属性为0说明程序没有被调试,非0说明被调试,另外的Flags属性为2说明被调试,不为2则说明没有被调试。

0:000> dt !_peb
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x018 ProcessHeap : Ptr32 Void // 找到Process偏移地址

0:000> !heap // 找出堆区首地址
Heap Address NT/Segment Heap
1270000 NT Heap

0:000> !heap -a 1270000 // 查询heep的内存
Index Address Name Debugging options enabled
1: 01270000
Segment at 01270000 to 0136f000 (00006000 bytes committed)
Flags: 40000062
ForceFlags: 40000060
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000

0:000> dt _HEAP 1270000 // 找到ForceFlags标志的偏移地址
ntdll!_HEAP
+0x000 Segment : _HEAP_SEGMENT
+0x000 Entry : _HEAP_ENTRY
+0x040 Flags : 0x40000062
+0x044 ForceFlags : 0x40000060

这里需要注意一点,堆区在不同系统中偏移值是不同的,在Windows10系统中ForceFlags属性位于堆头部偏移量为0x44处,而默认情况如果被调试则ForceFlags属性为0x40000060,而Flags标志为0x40000062,有了这些参考那么通过汇编语言实现将变得很容易,如下代码则是通过汇编分别读取这两个堆头参数;

#include <stdio.h>
#include <windows.h>

// 两种方式输出
int IsDebug(DWORD x)
{
DWORD Debug = 0;

if (x == 1)
{
__asm
{
mov eax, fs:[0x18] // TED基地址
mov eax, [eax + 0x30] // PEB基地址
mov eax, [eax + 0x18] // 定位 ProcessHeap
mov eax, [eax + 0x44] // 定位到 ForceFlags
mov Debug, eax
}
}
if (x == 2)
{
__asm
{
mov eax, fs:[0x18] // TED基地址
mov eax, [eax + 0x30] // PEB基地址
mov eax, [eax + 0x18] // 定位 ProcessHeap
mov eax, [eax + 0x40] // 定位到 Flags
mov Debug, eax
}
}

return Debug;
}

int main(int argc, char * argv[])
{
if (IsDebug(1) && IsDebug(2))
{
printf("[-] 进程正在被调试 \n");
}
else
{
printf("[*] 进程正常 \n");
}

system("pause");
return 0;
}

另一种通过C语言实现的反调试版本,其反调试原理与上方相同,只不过此处我们使用了系统的API来完成检测标志位的。

#include <stdio.h>
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS(NTAPI *typedef_ZwQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);

BOOL IsDebug()
{
HANDLE hProcess = NULL;
DWORD ProcessId = 0;
PROCESS_BASIC_INFORMATION Pbi;
typedef_ZwQueryInformationProcess pZwQueryInformationProcess = NULL;
ProcessId = GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if (hProcess != NULL)
{
HMODULE hModule = LoadLibrary("ntdll.dll");
pZwQueryInformationProcess = (typedef_ZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess");

NTSTATUS Status = pZwQueryInformationProcess(hProcess, ProcessBasicInformation, &Pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL);
if (NT_SUCCESS(Status))
{
DWORD ByteRead = 0;
DWORD ProcessHeap = 0;
ULONG PebBase = (ULONG)Pbi.PebBaseAddress;
DWORD ForceFlagsValue = 1;

ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x18), &ProcessHeap, 2, &ByteRead);
ReadProcessMemory(hProcess, (LPCVOID)(ProcessHeap + 0x40), &ForceFlagsValue, 4, &ByteRead);

if (ForceFlagsValue != 0)
{
return TRUE;
}
}
CloseHandle(hProcess);
}
return FALSE;
}

int main(int argc, char * argv[])
{
if (IsDebug())
{
printf("[-] 正在被调试 \n");
}

system("pause");
return 0;
}