Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석 [링크 복사], [링크+제목 복사],
조회: 28298
글쓴 사람
정성태 (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/
정성태

... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1755정성태9/22/201434271오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424597VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420601오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441067Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438930.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423828.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423730.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425379개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428412오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426093.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423034개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431047.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420983오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426955개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421330.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432501.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426508.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422042.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419750VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425567VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418155.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419819오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426345.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434463Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427071개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201422158개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...