Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

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/
정성태

... 16  17  18  19  20  21  22  23  24  25  26  27  [28]  29  30  ...
NoWriterDateCnt.TitleFile(s)
12918정성태1/13/20226407Linux: 47. WSL - shell script에서 설정한 환경 변수가 스크립트 실행 후 반영되지 않는 문제
12917정성태1/12/20225653오류 유형: 785. C# - The type or namespace name '...' could not be found (are you missing a using directive or an assembly reference?)
12916정성태1/12/20225385오류 유형: 784. TFS - One or more source control bindings for this solution are not valid and are listed below.
12915정성태1/11/20225663오류 유형: 783. Visual Studio - We didn't find any interpreters
12914정성태1/11/20227557VS.NET IDE: 172. 비주얼 스튜디오 2022의 파이선 개발 환경 지원
12913정성태1/11/20228081.NET Framework: 1133. C# - byte * (바이트 포인터)를 FileStream으로 쓰는 방법 [1]
12912정성태1/11/20228718개발 환경 구성: 623. ffmpeg.exe를 사용해 비디오 파일의 이미지를 PGM(Portable Gray Map) 파일 포맷으로 출력하는 방법 [1]
12911정성태1/11/20226044VS.NET IDE: 171. 비주얼 스튜디오 - 더 이상 만들 수 없는 "ASP.NET Core 3.1 Web Application (.NET Framework)" 프로젝트
12910정성태1/10/20226527제니퍼 .NET: 30. 제니퍼 닷넷 적용 사례 (8) - CPU high와 DB 쿼리 성능에 문제가 함께 있는 사이트
12909정성태1/10/20227921오류 유형: 782. Visual Studio 2022 설치 시 "Couldn't install Microsoft.VisualCpp.Redist.14.Latest"
12908정성태1/10/20225749.NET Framework: 1132. C# - ref/out 매개변수의 IL 코드 처리
12907정성태1/9/20226219오류 유형: 781. (youtube-dl.exe) 실행 시 "This app can't run on your PC" / "Access is denied." 오류 발생
12906정성태1/9/20226814.NET Framework: 1131. C# - 네임스페이스까지 동일한 타입을 2개의 DLL에서 제공하는 경우 충돌을 우회하는 방법 [1]파일 다운로드1
12905정성태1/8/20226470오류 유형: 780. Could not load file or assembly 'Microsoft.VisualStudio.TextTemplating.VSHost.15.0, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.
12904정성태1/8/20228519개발 환경 구성: 623. Visual Studio 2022 빌드 환경을 위한 github Actions 설정 [1]
12903정성태1/7/20227120.NET Framework: 1130. C# - ELEMENT_TYPE_INTERNAL 유형의 사용 예
12902정성태1/7/20227160오류 유형: 779. SQL 서버 로그인 에러 - provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.
12901정성태1/5/20227262오류 유형: 778. C# - .NET 5+에서 warning CA1416: This call site is reachable on all platforms. '...' is only supported on: 'windows' 경고 발생
12900정성태1/5/20228906개발 환경 구성: 622. vcpkg로 ffmpeg를 빌드하는 경우 생성될 구성 요소 제어하는 방법
12899정성태1/3/20228383개발 환경 구성: 621. windbg에서 python 스크립트 실행하는 방법 - pykd (2)
12898정성태1/2/20228932.NET Framework: 1129. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 인코딩 예제(encode_video.c) [1]파일 다운로드1
12897정성태1/2/20227809.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 [4]파일 다운로드1
12896정성태1/1/202210675.NET Framework: 1127. C# - FFmpeg.AutoGen 라이브러리를 이용한 기본 프로젝트 구성파일 다운로드1
12895정성태12/31/20219169.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작 [1]파일 다운로드1
12894정성태12/30/20217158.NET Framework: 1125. C# - DefaultObjectPool<T>의 IDisposable 개체에 대한 풀링 문제 [3]파일 다운로드1
12893정성태12/27/20218692.NET Framework: 1124. C# - .NET Platform Extension의 ObjectPool<T> 사용법 소개파일 다운로드1
... 16  17  18  19  20  21  22  23  24  25  26  27  [28]  29  30  ...