Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석 [링크 복사], [링크+제목 복사],
조회: 28330
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 6개 있습니다.)
디버깅 기술: 130. windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례
; https://www.sysnet.pe.kr/2/0/12058

디버깅 기술: 131. windbg/Visual Studio - HeapFree x86의 동작 분석
; https://www.sysnet.pe.kr/2/0/12059

디버깅 기술: 132. windbg/Visual Studio - HeapFree x64의 동작 분석
; https://www.sysnet.pe.kr/2/0/12060

디버깅 기술: 133. windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12062

디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석
; https://www.sysnet.pe.kr/2/0/12068

디버깅 기술: 144.  windbg - Marshal.FreeHGlobal에서 발생한 덤프 분석 사례
; https://www.sysnet.pe.kr/2/0/12086




windbg와 Win32 API로 알아보는 Windows Heap 정보 분석

지난 글에서,

windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12062

엎어진 김에 쉬어간다고 ^^ 기왕 이렇게 되었으니 힙 관련 정보를 얻기 위한 방법을 이참에 짚고 넘어가 보겠습니다. 우선, HeapWalk Win32 API를 이용해 프로그램 스스로 HeapCreate 및 HeapAlloc으로 할당받은 메모리를 알아낼 수 있습니다.

HeapWalk function
; https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapwalk

Enumerating a Heap
; https://learn.microsoft.com/en-us/windows/win32/memory/enumerating-a-heap

위의 함수를 이용해 지난 글에 다룬 예제 코드에 살을 붙여 보면,

#include <iostream>
#include <stdio.h>
#include <wchar.h>
#include <combaseapi.h>
#include <tchar.h>

void ShowHeapInfo(HANDLE hHandle)
{
    PROCESS_HEAP_ENTRY entry;

    entry.lpData = nullptr;

    while (HeapWalk(hHandle, &entry) != false)
    {
        if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
            _tprintf(TEXT("Allocated block"));

            if ((entry.wFlags & PROCESS_HEAP_ENTRY_MOVEABLE) != 0) {
                _tprintf(TEXT(", movable with HANDLE %#p"), entry.Block.hMem);
            }

            if ((entry.wFlags & PROCESS_HEAP_ENTRY_DDESHARE) != 0) {
                _tprintf(TEXT(", DDESHARE"));
            }
        }
        else if ((entry.wFlags & PROCESS_HEAP_REGION) != 0) {
            _tprintf(TEXT("Region\n  %d bytes committed\n") \
                TEXT("  %d bytes uncommitted\n  First block address: %#p\n") \
                TEXT("  Last block address: %#p\n"),
                entry.Region.dwCommittedSize,
                entry.Region.dwUnCommittedSize,
                entry.Region.lpFirstBlock,
                entry.Region.lpLastBlock);
        }
        else if ((entry.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE) != 0) {
            _tprintf(TEXT("Uncommitted range\n"));
        }
        else {
            _tprintf(TEXT("Block\n"));
        }

        _tprintf(TEXT("  Data portion begins at: %#p\n  Size: %d bytes\n") \
            TEXT("  Overhead: %d bytes\n  Region index: %d\n\n"),
            entry.lpData,
            entry.cbData,
            entry.cbOverhead,
            entry.iRegionIndex);
    }
}

int main()
{
    int size = 20;

    HANDLE hHandle = HeapCreate(0, 0, 8192);
    printf("Handle == 0x%I64x\n", hHandle);

    LPVOID pVoid1 = HeapAlloc(hHandle, 0, size);
    printf("pVoid1 == 0x%I64x\n", pVoid1);
    memset(pVoid1, 0xff, size);

    LPVOID pVoid2 = HeapAlloc(hHandle, 0, size + 1);
    printf("pVoid2 == 0x%I64x\n", pVoid2);
    memset(pVoid2, 0xee, size + 1);

    LPVOID pVoid3 = HeapAlloc(hHandle, 0, size + 2);
    printf("pVoid3 == 0x%I64x\n", pVoid3);
    memset(pVoid3, 0xcc, size + 2);

    LPVOID pVoid4 = HeapAlloc(hHandle, 0, 1);
    printf("pVoid4 == 0x%I64x\n", pVoid4);
    memset(pVoid4, 0xaa, 1);

    ShowHeapInfo(hHandle);

    printf("Wait...\n");
    getchar();

    HeapFree(hHandle, 0, pVoid4);
    HeapFree(hHandle, 0, pVoid3);
    HeapFree(hHandle, 0, pVoid2);
    HeapFree(hHandle, 0, pVoid1);

    HeapDestroy(hHandle);

    printf("Exited!");
    return 0;
}

실행 후 HeapAlloc으로 할당받은 메모리의 주소를 printf로 출력한 내용이,

Handle == 0x1614c0f0000
pVoid1 == 0x1614c0f0750
pVoid2 == 0x1614c0f0770
pVoid3 == 0x1614c0f0790
pVoid4 == 0x1614c0f07b0

그대로 HeapWalk에서도 나오고 심지어 HeapAlloc 호출 시 요청한 Size까지 구할 수 있습니다.

Region
  8192 bytes committed
  0 bytes uncommitted
  First block address: 000001614C0F0750
  Last block address: 000001614C0F2000
  Data portion begins at: 000001614C0F0000
  Size: 1856 bytes
  Overhead: 0 bytes
  Region index: 0

Allocated block  Data portion begins at: 000001614C0F0750
  Size: 20 bytes
  Overhead: 12 bytes
  Region index: 0

Allocated block  Data portion begins at: 000001614C0F0770
  Size: 21 bytes
  Overhead: 11 bytes
  Region index: 0

Allocated block  Data portion begins at: 000001614C0F0790
  Size: 22 bytes
  Overhead: 10 bytes
  Region index: 0

Allocated block  Data portion begins at: 000001614C0F07B0
  Size: 1 bytes
  Overhead: 31 bytes
  Region index: 0

Block
  Data portion begins at: 000001614C0F07E0
  Size: 6112 bytes
  Overhead: 32 bytes
  Region index: 0

Uncommitted range
  Data portion begins at: 000001614C0F2000
  Size: 0 bytes
  Overhead: 0 bytes
  Region index: 0

만약 CoTaskMemAlloc으로 할당한 메모리 내역을 보고 싶다면 Default Heap handle을 구한 다음 그것을 전달하면 됩니다.

HANDLE hDefaultHeap = GetProcessHeap();
printf("hDefaultHeap == 0x%I64x\n", hDefaultHeap); // hDefaultHeap == 0x1614bf20000
ShowHeapInfo(hDefaultHeap);

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




코드를 통해 알아봤으니, 이제 메모리 덤프를 다루기 위해 windbg에서 heap 정보를 알아내는 것도 보겠습니다.

Common WinDbg Commands (Thematically Grouped) - 20) Memory: Heap
; http://windbg.info/doc/1-common-cmds.html#20_memory_heap

!heap
; https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/-heap

우선, 프로그램에서 생성한 Heap 목록을 "-s" 옵션으로 구할 수 있습니다.

0:000> !heap -s
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
LFH Key                   : 0x0363460674ab5132
Termination on corruption : ENABLED
          Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                            (k)     (k)    (k)     (k) length      blocks cont. heap 
-------------------------------------------------------------------------------------
000001614bf20000 00000002    1220    104   1020      2     6     1    0      0   LFH
000001614bcf0000 00008000      64      4     64      2     1     1    0      0      
000001614c0f0000 00001000       8      8      8      6     1     1    0      0      
-------------------------------------------------------------------------------------

보는 바와 같이 000001614bf20000 == GetProcessHeap이고, 000001614c0f0000 항목이 코드에서 HeapCreate로 생성한 것입니다. (중간의 000001614bcf0000 항목은 아마도 CRT에서 생성했을 것입니다.) 또한, "-s" 옵션 말고 "-m" 옵션으로도 유사한 정보를 볼 수 있습니다.

0:000> !heap -m
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
HEAPEXT: Unable to get address of ntdll!RtlpHeapInvalidBadAddress.
Index   Address  Name      Debugging options enabled
  1:   1614bf20000 
    Segment at 000001614bf20000 to 000001614c01f000 (00018000 bytes committed)
  2:   1614bcf0000 
    Segment at 000001614bcf0000 to 000001614bd00000 (00001000 bytes committed)
  3:   1614c0f0000 
    Segment at 000001614c0f0000 to 000001614c0f2000 (00002000 bytes committed)

그리고 HeapWalk처럼 개별 힙 내부에 할당된 메모리 조각을 열람하려면 "-stat -h" 옵션을 적용하거나,

0:000> !heap -stat -h 000001614c0f0000 
 heap @ 000001614c0f0000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    16 1 - 16  (34.38)
    15 1 - 15  (32.81)
    14 1 - 14  (31.25)
    1 1 - 1  (1.56)

"-stat" 옵션을 빼면 더욱 자세한 정보를 얻을 수 있습니다.

0:000> !heap -h 000001614c0f0000
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
Index   Address  Name      Debugging options enabled
  3:   1614c0f0000 
    Segment at 000001614c0f0000 to 000001614c0f2000 (00002000 bytes committed)
    Flags:                00001000
    ForceFlags:           00000000
    Granularity:          16 bytes
    Segment Reserve:      00100000
    Segment Commit:       00002000
    DeCommit Block Thres: 00000100
    DeCommit Total Thres: 00001000
    Total Free Size:      00000180
    Max. Allocation Size: 00007ffffffdefff
    Lock Variable at:     000001614c0f02c0
    Next TagIndex:        0000
    Maximum TagIndex:     0000
    Tag Entries:          00000000
    PsuedoTag Entries:    00000000
    Virtual Alloc List:   1614c0f0110
    Uncommitted ranges:   1614c0f00f0
    FreeList[ 00 ] at 000001614c0f0150: 000001614c0f07d0 . 000001614c0f07d0   (1 block )

    Heap entries for Segment00 in Heap 000001614c0f0000
                 address: psize . size  flags   state (requested size)
        000001614c0f0000: 00000 . 00740 [101] - busy (73f)
        000001614c0f0740: 00740 . 00020 [101] - busy (14)
        000001614c0f0760: 00020 . 00020 [101] - busy (15)
        000001614c0f0780: 00020 . 00020 [101] - busy (16)
        000001614c0f07a0: 00020 . 00020 [101] - busy (1)
        000001614c0f07c0: 00020 . 01800 [100]
        000001614c0f1fc0: 01800 . 00040 [111] - busy (3d)
        000001614c0f2000:      00000000      - uncommitted bytes.

위의 출력 결과에서 "Heap entries"를 보면 HeapWalk와는 다르게 - 예를 들어 000001614C0F0750이 아닌 000001614c0f0740으로 0x10 바이트 먼저 나오는 것은 헤더 영역을 가리킨 것이기 때문입니다.

참고로, 할당 받은 메모리 중 아무 주소나 알 수 있으면 그것이 속한 Heap Handle과 HeapAlloc 블록을 알아낼 수 있습니다.

0:000> !heap -p -a 000001614c0f0754
    address 000001614c0f0754 found in
    _HEAP @ 1614c0f0000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000001614c0f0740 0002 0000  [00]   000001614c0f0750    00014 - (busy)

0:000> !heap -p -a 000001614c0f0777
    address 000001614c0f0777 found in
    _HEAP @ 1614c0f0000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000001614c0f0760 0002 0000  [00]   000001614c0f0770    00015 - (busy)
 
0:000> !heap -p -a 000001614c0f0797
    address 000001614c0f0797 found in
    _HEAP @ 1614c0f0000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000001614c0f0780 0002 0000  [00]   000001614c0f0790    00016 - (busy)
 
0:000> !heap -p -a 000001614c0f07B7
    address 000001614c0f07b7 found in
    _HEAP @ 1614c0f0000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000001614c0f07a0 0002 0000  [00]   000001614c0f07b0    00001 - (busy)




자, 그럼 조금 더 ^^ 내려가 "!heap" 명령의 도움을 받지 않고 직접 바닥부터 알아보겠습니다. 우선, 프로세스가 가진 Heap 목록을 PEB로부터 알아낼 수 있습니다.

0:000> dt _PEB @$peb NumberOfHeaps, ProcessHeaps
ntdll!_PEB
   +0x0e8 NumberOfHeaps  : 3
   +0x0f0 ProcessHeaps   : 0x00007ffa`0c543c40  -> 0x00000161`4bf20000 Void

NumberOfHeaps를 통해 3개의 힙이 생성되어 있다는 점과, ProcessHeaps 항목을 통해 0x00007ffa`0c543c40 포인터를 덤프해 보면,

0:000> dq /c1 0x00007ffa`0c543c40 L3
00007ffa`0c543c40  00000161`4bf20000
00007ffa`0c543c48  00000161`4bcf0000
00007ffa`0c543c50  00000161`4c0f0000

차례대로 3개의 포인터가 그대로 HeapCreate로 반환받은 Heap Handle 값임을 알 수 있습니다. 자... 이렇게 구한 Heap Handle을 ntdll의 _HEAP 구조체로 덤프해 보면,

0:000> dt ntdll!_HEAP 000001614c0f0000
   +0x000 Segment          : _HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY
   +0x010 SegmentSignature : 0xffeeffee
   +0x014 SegmentFlags     : 0
   +0x018 SegmentListEntry : _LIST_ENTRY [ 0x00000161`4c0f0120 - 0x00000161`4c0f0120 ]
   +0x028 Heap             : 0x00000161`4c0f0000 _HEAP
   +0x030 BaseAddress      : 0x00000161`4c0f0000 Void
   +0x038 NumberOfPages    : 2
   +0x040 FirstEntry       : 0x00000161`4c0f0740 _HEAP_ENTRY
   +0x048 LastValidEntry   : 0x00000161`4c0f2000 _HEAP_ENTRY
   +0x050 NumberOfUnCommittedPages : 0
   +0x054 NumberOfUnCommittedRanges : 1
   +0x058 SegmentAllocatorBackTraceIndex : 0
   +0x05a Reserved         : 0
   +0x060 UCRSegmentList   : _LIST_ENTRY [ 0x00000161`4c0f1fe0 - 0x00000161`4c0f1fe0 ]
   +0x070 Flags            : 0x1000
   +0x074 ForceFlags       : 0
   +0x078 CompatibilityFlags : 0
   +0x07c EncodeFlagMask   : 0x100000
   +0x080 Encoding         : _HEAP_ENTRY
   +0x090 Interceptor      : 0
   +0x094 VirtualMemoryThreshold : 0xff00
   +0x098 Signature        : 0xeeffeeff
   +0x0a0 SegmentReserve   : 0x100000
   +0x0a8 SegmentCommit    : 0x2000
   +0x0b0 DeCommitFreeBlockThreshold : 0x100
   +0x0b8 DeCommitTotalFreeThreshold : 0x1000
   +0x0c0 TotalFreeSize    : 0x180
   +0x0c8 MaximumAllocationSize : 0x00007fff`fffdefff
   +0x0d0 ProcessHeapsListIndex : 3
   +0x0d2 HeaderValidateLength : 0x2c0
   +0x0d8 HeaderValidateCopy : (null) 
   +0x0e0 NextAvailableTagIndex : 0
   +0x0e2 MaximumTagIndex  : 0
   +0x0e8 TagEntries       : (null) 
   +0x0f0 UCRList          : _LIST_ENTRY [ 0x00000161`4c0f00f0 - 0x00000161`4c0f00f0 ]
   +0x100 AlignRound       : 0x1f
   +0x108 AlignMask        : 0xffffffff`fffffff0
   +0x110 VirtualAllocdBlocks : _LIST_ENTRY [ 0x00000161`4c0f0110 - 0x00000161`4c0f0110 ]
   +0x120 SegmentList      : _LIST_ENTRY [ 0x00000161`4c0f0018 - 0x00000161`4c0f0018 ]
   +0x130 AllocatorBackTraceIndex : 0
   +0x134 NonDedicatedListLength : 0
   +0x138 BlocksIndex      : 0x00000161`4c0f02e8 Void
   +0x140 UCRIndex         : (null) 
   +0x148 PseudoTagEntries : (null) 
   +0x150 FreeLists        : _LIST_ENTRY [ 0x00000161`4c0f07d0 - 0x00000161`4c0f07d0 ]
   +0x160 LockVariable     : 0x00000161`4c0f02c0 _HEAP_LOCK
   +0x168 CommitRoutine    : 0x26faddb8`a6556aa4     long  +26faddb8a6556aa4
   +0x170 StackTraceInitVar : _RTL_RUN_ONCE
   +0x178 CommitLimitData  : _RTL_HEAP_MEMORY_LIMIT_DATA
   +0x198 FrontEndHeap     : (null) 
   +0x1a0 FrontHeapLockCount : 0
   +0x1a2 FrontEndHeapType : 0 ''
   +0x1a3 RequestedFrontEndHeapType : 0 ''
   +0x1a8 FrontEndHeapUsageData : (null) 
   +0x1b0 FrontEndHeapMaximumIndex : 0
   +0x1b2 FrontEndHeapStatusBitmap : [129]  ""
   +0x238 Counters         : _HEAP_COUNTERS
   +0x2b0 TuningParameters : _HEAP_TUNING_PARAMETERS

"windbg/Visual Studio - HeapFree x64의 동작 분석" 글에서 다룬 SegmentSignature와 해당 Heap Handle에 속한 (HeapAlloc으로 할당받은) 블록의 첫 번째 위치를 "FirstEntry" 값으로 알 수 있습니다. 그리고 이 값은 다음과 같이 _HEAP_ENTRY로 덤프할 수 있습니다.

0:000> dt ntdll!_HEAP_ENTRY 0x00000161`4c0f0740
   +0x000 UnpackedEntry    : _HEAP_UNPACKED_ENTRY
   +0x000 PreviousBlockPrivateData : (null) 
   +0x008 Size             : 0x6e73
   +0x00a Flags            : 0x35 '5'
   +0x00b SmallTagIndex    : 0xec ''
   +0x008 SubSegmentCode   : 0xec356e73
   +0x00c PreviousSize     : 0x476e
   +0x00e SegmentOffset    : 0 ''
   +0x00e LFHFlags         : 0 ''
   +0x00f UnusedBytes      : 0xc ''
   +0x008 CompactHeader    : 0x0c00476e`ec356e73
   +0x000 ExtendedEntry    : _HEAP_EXTENDED_ENTRY
   +0x000 Reserved         : (null) 
   +0x008 FunctionIndex    : 0x6e73
   +0x00a ContextValue     : 0xec35
   +0x008 InterceptorValue : 0xec356e73
   +0x00c UnusedBytesLength : 0x476e
   +0x00e EntryOffset      : 0 ''
   +0x00f ExtendedBlockSignature : 0xc ''
   +0x000 ReservedForAlignment : (null) 
   +0x008 Code1            : 0xec356e73
   +0x00c Code2            : 0x476e
   +0x00e Code3            : 0 ''
   +0x00f Code4            : 0xc ''
   +0x00c Code234          : 0xc00476e
   +0x008 AgregateCode     : 0x0c00476e`ec356e73

그런데 문제는 "NextEntry"같은 식의 속성이 없습니다. 이전의 결과를 통해 이미 알고 있던 0x00000161`4c0f0760 항목을 역시 덤프해 봐도,

0:000> dt ntdll!_HEAP_ENTRY 0x00000161`4c0f0760
   +0x000 UnpackedEntry    : _HEAP_UNPACKED_ENTRY
   +0x000 PreviousBlockPrivateData : 0x00000000`ffffffff Void
   +0x008 Size             : 0x6e73
   +0x00a Flags            : 0x35 '5'
   +0x00b SmallTagIndex    : 0xec ''
   +0x008 SubSegmentCode   : 0xec356e73
   +0x00c PreviousSize     : 0x4718
   +0x00e SegmentOffset    : 0 ''
   +0x00e LFHFlags         : 0 ''
   +0x00f UnusedBytes      : 0xb ''
   +0x008 CompactHeader    : 0x0b004718`ec356e73
   +0x000 ExtendedEntry    : _HEAP_EXTENDED_ENTRY
   +0x000 Reserved         : 0x00000000`ffffffff Void
   +0x008 FunctionIndex    : 0x6e73
   +0x00a ContextValue     : 0xec35
   +0x008 InterceptorValue : 0xec356e73
   +0x00c UnusedBytesLength : 0x4718
   +0x00e EntryOffset      : 0 ''
   +0x00f ExtendedBlockSignature : 0xb ''
   +0x000 ReservedForAlignment : 0x00000000`ffffffff Void
   +0x008 Code1            : 0xec356e73
   +0x00c Code2            : 0x4718
   +0x00e Code3            : 0 ''
   +0x00f Code4            : 0xb ''
   +0x00c Code234          : 0xb004718
   +0x008 AgregateCode     : 0x0b004718`ec356e73

PrevEntry 또는 NextEntry와 같은 요소는 찾을 수가 없습니다. 물론 "!heap" 명령을 사용하면 다음과 같이 쉽게 구할 수 있습니다. ^^

0:000> !heap -i 000001614c0f0000
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
Heap context set to the heap 0x000001614c0f0000

0:000> !heap -i 0x00000161`4c0f0780    
Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention
Detailed information for block entry 000001614c0f0780
Assumed heap       : 0x000001614c0f0000 (Use !heap -i NewHeapHandle to change)
Header content     : 0xEC356E73 0x0A004718 (decoded : 0x03010002 0x0A000002)
Owning segment     : 0x000001614c0f0000 (offset 0)
Block flags        : 0x1 (busy )
Total block size   : 0x2 units (0x20 bytes)
Requested size     : 0x16 bytes (unused 0xa bytes)
Previous block size: 0x2 units (0x20 bytes)
Block CRC          : OK - 0x3  
Previous block     : 0x000001614c0f0760
Next block         : 0x000001614c0f07a0

아니... 도대체 어떻게 구한 것일까요? ^^ 다행히 검색해 보면, 이에 관해 설명한 글을 볼 수 있습니다.

Windows Heap Chunk Header Parsing and Size Calculation
; https://stackoverflow.com/questions/28483473/windows-heap-chunk-header-parsing-and-size-calculation

그러니까, 우선 해당 block이 속한 _HEAP의 "Encoding" 속성 값을 먼저 구해야 합니다.

0:000> dt ntdll!_HEAP 000001614c0f0000 encoding
   +0x080 Encoding : _HEAP_ENTRY

0:000> dq 000001614c0f0000 + 0x80 L2
00000161`4c0f0080  00000000`00000000 0000471a`ef346e71

그럼 위와 같이 (64비트의 경우) 8바이트 씩 2개의 값을 구한 다음, 이것을 HeapAlloc으로 할당받은 영역의 헤더 값과 XOR 연산을 합니다.

0:000> dq 0x00000161`4c0f0740 L2
00000161`4c0f0740  00000000`00000000 0c00476e`ec356e73

0:000> ? 00000000`00000000 ^ 00000000`00000000
Evaluate expression: 0 = 00000000`00000000

0:000> ? 0000471a`ef346e71 ^ 0c00476e`ec356e73
Evaluate expression: 864691626721738754 = 0c000074`03010002

(참고로, 32비트의 경우 4바이트 씩 2개의 값을 동일하게 XOR 연산하면 됩니다.)

어차피 8바이트 중 앞의 것은 0이므로 크게 중요하지 않고, 뒤의 8바이트가 의미를 가지는데 이것을 그대로 _HEAP_ENTRY 구조체에 memcpy를 해야 합니다. 하지만 windbg 사용 중에 코딩하는 것은 귀찮으므로(게다가 _HEAP_ENTRY의 구조도 OS마다, 패치마다 달라질 수 있으므로), 이것을 쉽게 해결하기 위해 현재 사용 중이지 않을 법한 메모리를 찾아낸 다음,

0:000> dq 000001614c0f07c0
00000161`4c0f07c0  00000000`00000000 00004718`6e346ff1
00000161`4c0f07d0  00000161`4c0f0150 00000161`4c0f0150
00000161`4c0f07e0  00000000`00000000 00000000`00000000
00000161`4c0f07f0  00000000`00000000 00000000`00000000
00000161`4c0f0800  00000000`00000000 00000000`00000000
00000161`4c0f0810  00000000`00000000 00000000`00000000
00000161`4c0f0820  00000000`00000000 00000000`00000000
00000161`4c0f0830  00000000`00000000 00000000`00000000

0:000> dq 000001614c0f07e0
00000161`4c0f07e0  00000000`00000000 00000000`00000000
00000161`4c0f07f0  00000000`00000000 00000000`00000000
00000161`4c0f0800  00000000`00000000 00000000`00000000
00000161`4c0f0810  00000000`00000000 00000000`00000000
00000161`4c0f0820  00000000`00000000 00000000`00000000

(위의 출력에서는 000001614c0f07e0 주소가 적당한 듯하니) 그 주소에 XOR 연산 결과로 나온 값을 그대로 써주면,

0:000> eq 000001614c0f07e0
00000161`4c0f07e0 00000000`00000000 0
0
00000161`4c0f07e8 00000000`00000000 0c000074`03010002
0c000074`03010002
00000161`4c0f07f0 00000000`00000000

0:000> dq 000001614c0f07e0
00000161`4c0f07e0  00000000`00000000 0c000074`03010002
00000161`4c0f07f0  00000000`00000000 00000000`00000000
00000161`4c0f0800  00000000`00000000 00000000`00000000
00000161`4c0f0810  00000000`00000000 00000000`00000000
00000161`4c0f0820  00000000`00000000 00000000`00000000

결국 _HEAP_ENTRY에 해당하는 메모리로 덤프할 수 있게 됩니다.

0:000> dt _HEAP_ENTRY 000001614c0f07e0
ntdll!_HEAP_ENTRY
   +0x000 UnpackedEntry    : _HEAP_UNPACKED_ENTRY
   +0x000 PreviousBlockPrivateData : (null) 
   +0x008 Size             : 2
   +0x00a Flags            : 0x1 ''
   +0x00b SmallTagIndex    : 0x3 ''
   +0x008 SubSegmentCode   : 0x3010002
   +0x00c PreviousSize     : 0x74
   +0x00e SegmentOffset    : 0 ''
   +0x00e LFHFlags         : 0 ''
   +0x00f UnusedBytes      : 0xc ''
   +0x008 CompactHeader    : 0x0c000074`03010002
   +0x000 ExtendedEntry    : _HEAP_EXTENDED_ENTRY
   +0x000 Reserved         : (null) 
   +0x008 FunctionIndex    : 2
   +0x00a ContextValue     : 0x301
   +0x008 InterceptorValue : 0x3010002
   +0x00c UnusedBytesLength : 0x74
   +0x00e EntryOffset      : 0 ''
   +0x00f ExtendedBlockSignature : 0xc ''
   +0x000 ReservedForAlignment : (null) 
   +0x008 Code1            : 0x3010002
   +0x00c Code2            : 0x74
   +0x00e Code3            : 0 ''
   +0x00f Code4            : 0xc ''
   +0x00c Code234          : 0xc000074
   +0x008 AgregateCode     : 0x0c000074`03010002

이렇게 디코딩된 결과를 보면, 이번 0x00000161`4c0f0740 HeapAlloc 블록은 크기가 2단위이고, 이전의 블록 크기는 0x74 단위였다는 것입니다. 그리고 실제 바이트 크기로 구하려면 _HEAP_ENTRY의 크기를 구한 후,

0:000>  ?? sizeof(_HEAP_ENTRY)
unsigned int64 0x10

이 값을 각각의 단위에 곱해서 2 * 0x10 = 0x20(32바이트), 0x74 * 0x10 = 0x740(1,856 바이트)를 구할 수 있습니다. 따라서, 0x00000161`4c0f0740 블록의 다음번 블록 위치는 0x00000161`4c0f0740 + 0x20 = 0x00000161`4c0f0760이 되고 이전 블록의 위치는 0x00000161`4c0f0740 - 0x740 = 0x1614c0f0000이 됩니다. 정확하군요. ^^




아래의 글을 보면,

Windows Heap Note
; http://wg135.github.io/blog/2018/05/31/windows-heap-note/

XOR 했던 값(0c000074`03010002)에서 하위 4바이트 중,

03010002

다시 하위 2바이트(0x0002)가 블록의 크기라고 하며, 상위 2바이트 중 0x03은 SmallTagIndex, 0x01은 "Heap busy"를 나타낸다고 합니다.

0x0002 means this heap is 2 blocks; 0x01 means this heap is busy; 0x03 means SmallTagIndex; Notice: 32 bits system one block is 8 bytes, while 64 bits system one block is 16 bytes.


Heap에 관한 좀 더 자세한 정보는 첨부 파일(us-16-Yason-Windows-10-Segment-Heap-Internals.zip)로도 제공하는 아래의 PDF 문서를 보시면 도움이 되실 것입니다.

us-16-Yason-Windows-10-Segment-Heap-Internals.zip
; https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals-wp.pdf




마지막으로, gflags를 이용해 hpa 옵션이 적용된 프로그램의 메모리 덤프를 보면, _HEAP/_HEAP_ENTRY보다 좀 더 자세한 정보를 담고 있는 _DPH_HEAP_ROOT, _DPH_HEAP_BLOCK을 구할 수 있다고 합니다.

b) Page heap enabled (gflags.exe /i MyApp.exe +hpa)
CreateHeap -> creates a _DPH_HEAP_ROOT (+ _HEAP + 2x _HEAP_ENTRY)**
AllocHeap -> creates a _DPH_HEAP_BLOCK
** With page heap enabled there will still be a _HEAP with two constant _HEAP_ENTRY's for every CreateHeap call.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/19/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2019-11-28 10시34분
최신 윈도우즈 익스플로잇 개발 04. 힙 (Heap)
; https://hackability.kr/entry/%EC%9D%B5%EC%8A%A4%ED%94%8C%EB%A1%9C%EC%9E%87-%EA%B0%9C%EB%B0%9C-04-%ED%9E%99-Heap

보안 취약점(19.5.29) Heap Types
; https://sudeky.tistory.com/39

Heap 메모리 생성한지 알아볼때 (WinDbg) Who called HeapAlloc
; https://gosera.tistory.com/68
정성태
2020-02-07 11시08분
정성태
2021-12-15 09시55분
워라밸 브레이커, 메모리릭을 찾아라(1/4)
; https://netmarble.engineering/break-the-memory-leak-1-4/

워라밸 브레이커, 메모리릭을 찾아라(2/4)
; https://netmarble.engineering/break-the-memory-leak-2-4/

워라밸 브레이커, 메모리릭을 찾아라(3/4)
; https://netmarble.engineering/break-the-memory-leak-3-4/

워라밸 브레이커, 메모리릭을 찾아라(4/4)
; https://netmarble.engineering/break-the-memory-leak-4-4/
정성태

... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1303정성태6/26/201227390개발 환경 구성: 152. sysnet DB를 SQL Azure 데이터베이스로 마이그레이션
1302정성태6/25/201229386개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225763오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231751.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232876제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234401VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201231043VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227669.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225074.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248519.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229770.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223749.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230267VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235077.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239234.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226460.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229291.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238211.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233261.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225689오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233301.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226103Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233182.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226150오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224914.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226144오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...