C# - PEB.ProcessHeap을 이용해 디버깅 중인지 확인하는 방법
아래의 글에 재미있는 내용이 있군요. ^^
Anti-Reversing Technique: PEB.ProcessHeap
; https://t0rchwo0d.github.io/windows/Windows-Anti-Reversing-Technique-PEB.ProcessHeap/
디버거가 연결된 경우 ProcessHeap의,
0:007> dt _PEB @$peb
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 '' // IsDebuggerPresent function
+0x003 BitField : 0x84 ''
...[생략]...
+0x028 SubSystemData : 0x00007ffd`b5cfe120 Void
+0x030 ProcessHeap : 0x000001b0`39300000 Void
...[생략]...
Flags와 ForceFlags 값이 달라진다는 것입니다.
0:007> dt _HEAP
ntdll!_HEAP
+0x000 Segment : _HEAP_SEGMENT
+0x000 Entry : _HEAP_ENTRY
+0x010 SegmentSignature : Uint4B
+0x014 SegmentFlags : Uint4B
+0x018 SegmentListEntry : _LIST_ENTRY
+0x028 Heap : Ptr64 _HEAP
+0x030 BaseAddress : Ptr64 Void
+0x038 NumberOfPages : Uint4B
+0x040 FirstEntry : Ptr64 _HEAP_ENTRY
+0x048 LastValidEntry : Ptr64 _HEAP_ENTRY
+0x050 NumberOfUnCommittedPages : Uint4B
+0x054 NumberOfUnCommittedRanges : Uint4B
+0x058 SegmentAllocatorBackTraceIndex : Uint2B
+0x05a Reserved : Uint2B
+0x060 UCRSegmentList : _LIST_ENTRY
+0x070 Flags : Uint4B
+0x074 ForceFlags : Uint4B
...[생략]...
사실 ProcessHeap이 가리키는 것은 Default Heap이기 때문에 전체 힙 목록은 예전에 설명한 대로,
windbg와 Win32 API로 알아보는 Windows Heap 정보 분석
; https://www.sysnet.pe.kr/2/0/12068
NumberOfHeaps, ProcessHeaps 필드의 값으로 알 수 있습니다.
0:000> dt _PEB @$peb ProcessHeap
ntdll!_PEB
+0x030 ProcessHeap : 0x00000255`54510000 Void
0:000> dt _PEB @$peb NumberOfHeaps, ProcessHeaps
ntdll!_PEB
+0x0e8 NumberOfHeaps : 2
+0x0f0 ProcessHeaps : 0x00007ffb`64a43c40 -> 0x00000255`54510000 Void
0:000> dq /c1 0x00007ffb`64a43c40 L2
00007ffb`64a43c40 00000255`54510000
00007ffb`64a43c48 00000255`54410000
보는 바와 같이 ProcessHeaps의 첫 번째 엔트리가 PEB.ProcessHeap 값입니다. 위와 같은 조작을
KernelStructOffset 라이브러리를 이용해 코딩해 보면 다음과 같습니다.
IntPtr pebAddress = EnvironmentBlockInfo.GetPebAddress(out IntPtr tebAddress);
var pebOffset = DbgOffset.Get("_PEB");
var heapOffset = DbgOffset.Get("_HEAP");
if (pebOffset.TryRead<IntPtr>(pebAddress, "ProcessHeap", out IntPtr processHeapPtr) == false)
{
return;
}
Console.WriteLine($"Default ProcessHeap: {processHeapPtr.ToInt64():x}");
IntPtr processHeapsPtr = pebOffset.GetPointer(pebAddress, "ProcessHeaps").ReadPtr();
if (pebOffset.TryRead<int>(pebAddress, "NumberOfHeaps", out int numberOfHeaps) == false)
{
return;
}
Console.WriteLine($"Ptr of ProcessHeaps: {processHeapsPtr.ToInt64():x}");
Console.WriteLine($"Number of Heaps: {numberOfHeaps}");
for (int i = 0; i < numberOfHeaps; i++)
{
IntPtr entryPtr = processHeapsPtr + (IntPtr.Size * i);
IntPtr heapAddress = entryPtr.ReadPtr();
Console.WriteLine($"[{i}] Heap: {heapAddress.ToInt64():x}");
}
/* 출력 결과
Default ProcessHeap: 13c0000
Ptr of ProcessHeaps: 7ffdba963c40
Number of Heaps: 6
[0] Heap: 13c0000
[1] Heap: fa0000
[2] Heap: 1730000
[3] Heap: 19b0000
[4] Heap: 3250000
[5] Heap: 3410000
*/
그리고, 각각의 heap에 대한 Flags, ForceFlags는 이렇게 구하면 됩니다.
for (int i = 0; i < numberOfHeaps; i++)
{
IntPtr entryPtr = processHeapsPtr + (IntPtr.Size * i);
IntPtr heapAddress = entryPtr.ReadPtr();
Console.WriteLine($"[{i}] Heap: {heapAddress.ToInt64():x}");
if (heapOffset.TryRead<int>(heapAddress, "Flags", out int flagsValue) == true)
{
Console.WriteLine($"\tFlags: {flagsValue:x}");
}
if (heapOffset.TryRead<int>(heapAddress, "ForceFlags", out int forceFlagsValue) == true)
{
Console.WriteLine($"\tForceFlags: {forceFlagsValue:x}");
}
}
코딩이 완료되었으니 이제 테스트를 해봐야겠지요. ^^
우선, Visual Studio에서 F5 키를 눌러 "Start Debugging..."으로 시작하면 다음과 같이 결과가 출력됩니다.
// Visual Studio - F5 실행 시
Default ProcessHeap: d50000
Ptr of ProcessHeaps: 7ffdba963c40
Number of Heaps: 6
[0] Heap: d50000
Flags: 2
ForceFlags: 0
[1] Heap: 9d0000
Flags: 8000
ForceFlags: 0
[2] Heap: 1060000
Flags: 1002
ForceFlags: 0
[3] Heap: 1370000
Flags: 1002
ForceFlags: 0
[4] Heap: 2c70000
Flags: 1002
ForceFlags: 0
[5] Heap: 2b70000
Flags: 41002
ForceFlags: 0
/*
0x2 == HEAP_GROWABLE
0x1000 == (?)
0x8000 == HEAP_PSEUDO_TAG_FLAG
0x40000 == HEAP_CREATE_ENABLE_EXECUTE
*/
저 결과는 디버거 없이 실행했을 때와 동일한 것인데 따라서 Visual Studio는 Heap 구성에 별다른 영향을 끼치지 않고 있습니다. 반면, windbg는 다릅니다. "Open Executable..." 메뉴로 exe를 지정한 뒤 "g" 키를 눌러 실행하면 다음과 같이 플래그들이 구성됩니다.
Default ProcessHeap: 13c0000
Ptr of ProcessHeaps: 7ffdba963c40
Number of Heaps: 6
[0] Heap: 13c0000
Flags: 40000062
ForceFlags: 40000060
[1] Heap: fa0000
Flags: 40008060
ForceFlags: 40000060
[2] Heap: 1730000
Flags: 40001062
ForceFlags: 40000060
[3] Heap: 19b0000
Flags: 40001062
ForceFlags: 40000060
[4] Heap: 3250000
Flags: 40001062
ForceFlags: 40000060
[5] Heap: 3410000
Flags: 40001062
ForceFlags: 40000060
/*
0x2 == HEAP_GROWABLE
0x1000 == (?)
0x60 ==
0x20 HEAP_TAIL_CHECKING_ENABLED
0x40 HEAP_FREE_CHECKING_ENABLED
0x40000000 == HEAP_VALIDATE_PARAMETERS_ENABLED
*/
의도한 값이 나오는군요. (참고로, 중간에 attach시키면 소용 없습니다.) 재미있는 것은, 해당 프로세스 명(예제에서는 ConsoleApp1.exe)을
Image File Execution Options 레지스트리에 등록하면,
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ConsoleApp1.exe
windbg에서 실행했을지라도 디버거가 없는 것처럼 Flags들의 값이 구성됩니다. 정리하면... windbg를 상당히 제한적으로 감지할 수 있는 방법입니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]