성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>windbg와 Win32 API로 알아보는 Windows Heap 정보 분석</h1> <p> 지난 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12062'>http://www.sysnet.pe.kr/2/0/12062</a> </pre> <br /> 엎어진 김에 쉬어간다고 ^^ 기왕 이렇게 되었으니 힙 관련 정보를 얻기 위한 방법을 이참에 짚고 넘어가 보겠습니다. 우선, HeapWalk Win32 API를 이용해 프로그램 스스로 HeapCreate 및 HeapAlloc으로 할당받은 메모리를 알아낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HeapWalk function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapwalk'>https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapwalk</a> Enumerating a Heap ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/memory/enumerating-a-heap'>https://learn.microsoft.com/en-us/windows/win32/memory/enumerating-a-heap</a> </pre> <br /> 위의 함수를 이용해 <a target='tab' href='http://www.sysnet.pe.kr/2/0/12060'>지난 글에 다룬 예제 코드</a>에 살을 붙여 보면, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include <iostream> #include <stdio.h> #include <wchar.h> #include <combaseapi.h> #include <tchar.h> void ShowHeapInfo(HANDLE hHandle) { <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-process_heap_entry'>PROCESS_HEAP_ENTRY</a> entry; entry.lpData = nullptr; <span style='color: blue; font-weight: bold'>while (HeapWalk(hHandle, &entry) != false)</span> { 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); <span style='color: blue; font-weight: bold'>ShowHeapInfo(hHandle);</span> 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; } </pre> <br /> 실행 후 HeapAlloc으로 할당받은 메모리의 주소를 printf로 출력한 내용이,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Handle == <span style='color: blue; font-weight: bold'>0x1614c0f0000</span> pVoid1 == <span style='color: blue; font-weight: bold'>0x1614c0f0750</span> pVoid2 == <span style='color: blue; font-weight: bold'>0x1614c0f0770</span> pVoid3 == <span style='color: blue; font-weight: bold'>0x1614c0f0790</span> pVoid4 == <span style='color: blue; font-weight: bold'>0x1614c0f07b0</span> </pre> <br /> 그대로 HeapWalk에서도 나오고 심지어 HeapAlloc 호출 시 요청한 Size까지 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Region 8192 bytes committed 0 bytes uncommitted First block address: 000001614C0F0750 Last block address: 000001614C0F2000 Data portion begins at: <span style='color: blue; font-weight: bold'>000001614C0F0000</span> Size: 1856 bytes Overhead: 0 bytes Region index: 0 Allocated block Data portion begins at: <span style='color: blue; font-weight: bold'>000001614C0F0750</span> Size: 20 bytes Overhead: 12 bytes Region index: 0 Allocated block Data portion begins at: <span style='color: blue; font-weight: bold'>000001614C0F0770</span> Size: 21 bytes Overhead: 11 bytes Region index: 0 Allocated block Data portion begins at: <span style='color: blue; font-weight: bold'>000001614C0F0790</span> Size: 22 bytes Overhead: 10 bytes Region index: 0 Allocated block Data portion begins at: <span style='color: blue; font-weight: bold'>000001614C0F07B0</span> 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 </pre> <br /> 만약 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cotaskmemalloc'>CoTaskMemAlloc</a>으로 할당한 메모리 내역을 보고 싶다면 Default Heap handle을 구한 다음 그것을 전달하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HANDLE hDefaultHeap = <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-getprocessheap'>GetProcessHeap</a>(); printf("hDefaultHeap == 0x%I64x\n", hDefaultHeap); // hDefaultHeap == 0x1614bf20000 ShowHeapInfo(hDefaultHeap); </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1515&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 코드를 통해 알아봤으니, 이제 메모리 덤프를 다루기 위해 windbg에서 heap 정보를 알아내는 것도 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Common WinDbg Commands (Thematically Grouped) - 20) Memory: Heap ; <a target='tab' href='http://windbg.info/doc/1-common-cmds.html#20_memory_heap'>http://windbg.info/doc/1-common-cmds.html#20_memory_heap</a> !heap ; <a target='tab' href='https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/-heap'>https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/-heap</a> </pre> <br /> 우선, 프로그램에서 생성한 Heap 목록을 "-s" 옵션으로 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!heap -s</span> 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 ------------------------------------------------------------------------------------- <span style='color: blue; font-weight: bold'>000001614bf20000</span> 00000002 1220 104 1020 2 6 1 0 0 LFH 000001614bcf0000 00008000 64 4 64 2 1 1 0 0 <span style='color: blue; font-weight: bold'>000001614c0f0000</span> 00001000 8 8 8 6 1 1 0 0 ------------------------------------------------------------------------------------- </pre> <br /> 보는 바와 같이 000001614bf20000 == GetProcessHeap이고, 000001614c0f0000 항목이 코드에서 HeapCreate로 생성한 것입니다. (중간의 000001614bcf0000 항목은 아마도 CRT에서 생성했을 것입니다.) 또한, "-s" 옵션 말고 "-m" 옵션으로도 유사한 정보를 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!heap -m</span> 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 <span style='color: blue; font-weight: bold'>000001614bf20000</span> to 000001614c01f000 (00018000 bytes committed) 2: 1614bcf0000 Segment at <span style='color: blue; font-weight: bold'>000001614bcf0000</span> to 000001614bd00000 (00001000 bytes committed) 3: 1614c0f0000 Segment at <span style='color: blue; font-weight: bold'>000001614c0f0000</span> to 000001614c0f2000 (00002000 bytes committed) </pre> <br /> 그리고 HeapWalk처럼 개별 힙 내부에 할당된 메모리 조각을 열람하려면 "-stat -h" 옵션을 적용하거나,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!heap -stat -h 000001614c0f0000 </span> heap @ 000001614c0f0000 group-by: TOTSIZE max-display: 20 size #blocks total ( %) (percent of total busy bytes) <span style='color: blue; font-weight: bold'>16 1 - 16 (34.38) 15 1 - 15 (32.81) 14 1 - 14 (31.25) 1 1 - 1 (1.56)</span> </pre> <br /> "-stat" 옵션을 빼면 더욱 자세한 정보를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!heap -h 000001614c0f0000</span> 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) <span style='color: blue; font-weight: bold'>000001614c0f0740</span>: 00740 . 00020 [101] - busy (14) <span style='color: blue; font-weight: bold'>000001614c0f0760</span>: 00020 . 00020 [101] - busy (15) <span style='color: blue; font-weight: bold'>000001614c0f0780</span>: 00020 . 00020 [101] - busy (16) <span style='color: blue; font-weight: bold'>000001614c0f07a0</span>: 00020 . 00020 [101] - busy (1) 000001614c0f07c0: 00020 . 01800 [100] 000001614c0f1fc0: 01800 . 00040 [111] - busy (3d) 000001614c0f2000: 00000000 - uncommitted bytes. </pre> <br /> 위의 출력 결과에서 "Heap entries"를 보면 HeapWalk와는 다르게 - 예를 들어 000001614C0F0<span style='color: blue; font-weight: bold'>750</span>이 아닌 000001614c0f0<span style='color: blue; font-weight: bold'>740</span>으로 0x10 바이트 먼저 나오는 것은 헤더 영역을 가리킨 것이기 때문입니다. <br /> <br /> 참고로, 할당 받은 메모리 중 아무 주소나 알 수 있으면 그것이 속한 Heap Handle과 HeapAlloc 블록을 알아낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!heap -p -a 000001614c0f0754</span> address 000001614c0f0754 found in _HEAP @ 1614c0f0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 000001614c0f0740 0002 0000 [00] 000001614c0f0750 00014 - (busy) 0:000> <span style='color: blue; font-weight: bold'>!heap -p -a 000001614c0f0777</span> address 000001614c0f0777 found in _HEAP @ 1614c0f0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 000001614c0f0760 0002 0000 [00] 000001614c0f0770 00015 - (busy) 0:000> <span style='color: blue; font-weight: bold'>!heap -p -a 000001614c0f0797</span> address 000001614c0f0797 found in _HEAP @ 1614c0f0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 000001614c0f0780 0002 0000 [00] 000001614c0f0790 00016 - (busy) 0:000> <span style='color: blue; font-weight: bold'>!heap -p -a 000001614c0f07B7</span> address 000001614c0f07b7 found in _HEAP @ 1614c0f0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 000001614c0f07a0 0002 0000 [00] 000001614c0f07b0 00001 - (busy) </pre> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 조금 더 ^^ 내려가 "!heap" 명령의 도움을 받지 않고 직접 바닥부터 알아보겠습니다. 우선, 프로세스가 가진 Heap 목록을 PEB로부터 알아낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt _PEB @$peb NumberOfHeaps, ProcessHeaps</span> ntdll!_PEB +0x0e8 NumberOfHeaps : 3 +0x0f0 ProcessHeaps : 0x00007ffa`0c543c40 -> 0x00000161`4bf20000 Void </pre> <br /> NumberOfHeaps를 통해 3개의 힙이 생성되어 있다는 점과, ProcessHeaps 항목을 통해 0x00007ffa`0c543c40 포인터를 덤프해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dq /c1 0x00007ffa`0c543c40 L3</span> 00007ffa`0c543c40 00000161`4bf20000 00007ffa`0c543c48 00000161`4bcf0000 00007ffa`0c543c50 00000161`4c0f0000 </pre> <br /> 차례대로 3개의 포인터가 그대로 HeapCreate로 반환받은 Heap Handle 값임을 알 수 있습니다. 자... 이렇게 구한 Heap Handle을 ntdll의 _HEAP 구조체로 덤프해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt ntdll!_HEAP 000001614c0f0000</span> +0x000 Segment : _HEAP_SEGMENT +0x000 Entry : _HEAP_ENTRY <span style='color: blue; font-weight: bold'>+0x010 SegmentSignature : 0xffeeffee</span> +0x014 SegmentFlags : 0 +0x018 SegmentListEntry : _LIST_ENTRY [ 0x00000161`4c0f0120 - 0x00000161`4c0f0120 ] +0x028 Heap : 0x00000161`4c0f0000 _HEAP +0x030 BaseAddress : 0x00000161`4c0f0000 Void +0x038 NumberOfPages : 2 <span style='color: blue; font-weight: bold'>+0x040 FirstEntry : 0x00000161`4c0f0740 _HEAP_ENTRY</span> +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 </pre> <br /> "<a target='tab' href='http://www.sysnet.pe.kr/2/0/12060'>windbg/Visual Studio - HeapFree x64의 동작 분석</a>" 글에서 다룬 SegmentSignature와 해당 Heap Handle에 속한 (HeapAlloc으로 할당받은) 블록의 첫 번째 위치를 "FirstEntry" 값으로 알 수 있습니다. 그리고 이 값은 다음과 같이 _HEAP_ENTRY로 덤프할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt ntdll!_HEAP_ENTRY 0x00000161`4c0f0740</span> +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 </pre> <br /> 그런데 문제는 "NextEntry"같은 식의 속성이 없습니다. 이전의 결과를 통해 이미 알고 있던 0x00000161`4c0f0760 항목을 역시 덤프해 봐도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt ntdll!_HEAP_ENTRY 0x00000161`4c0f0760</span> +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 </pre> <br /> PrevEntry 또는 NextEntry와 같은 요소는 찾을 수가 없습니다. 물론 "!heap" 명령을 사용하면 다음과 같이 쉽게 구할 수 있습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!heap -i 000001614c0f0000</span> Failed to read heap keySEGMENT HEAP ERROR: failed to initialize the extention Heap context set to the heap 0x000001614c0f0000 0:000> <span style='color: blue; font-weight: bold'>!heap -i 0x00000161`4c0f0780</span> 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 <span style='color: blue; font-weight: bold'>Previous block : 0x000001614c0f0760 Next block : 0x000001614c0f07a0</span> </pre> <br /> 아니... 도대체 어떻게 구한 것일까요? ^^ 다행히 검색해 보면, 이에 관해 설명한 글을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Windows Heap Chunk Header Parsing and Size Calculation ; <a target='tab' href='https://stackoverflow.com/questions/28483473/windows-heap-chunk-header-parsing-and-size-calculation'>https://stackoverflow.com/questions/28483473/windows-heap-chunk-header-parsing-and-size-calculation</a> </pre> <br /> 그러니까, 우선 해당 block이 속한 _HEAP의 "Encoding" 속성 값을 먼저 구해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt ntdll!_HEAP 000001614c0f0000 encoding</span> +0x080 Encoding : _HEAP_ENTRY 0:000> <span style='color: blue; font-weight: bold'>dq 000001614c0f0000 + 0x80 L2</span> 00000161`4c0f0080 00000000`00000000 0000471a`ef346e71 </pre> <br /> 그럼 위와 같이 (64비트의 경우) 8바이트 씩 2개의 값을 구한 다음, 이것을 HeapAlloc으로 할당받은 영역의 헤더 값과 XOR 연산을 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dq 0x00000161`4c0f0740 L2</span> 00000161`4c0f0740 <span style='color: blue; font-weight: bold'>00000000`00000000 0c00476e`ec356e73</span> 0:000> <span style='color: blue; font-weight: bold'>? 00000000`00000000 ^ 00000000`00000000</span> Evaluate expression: 0 = 00000000`00000000 0:000> <span style='color: blue; font-weight: bold'>? 0000471a`ef346e71 ^ 0c00476e`ec356e73</span> Evaluate expression: 864691626721738754 = 0c000074`03010002 (참고로, 32비트의 경우 4바이트 씩 2개의 값을 동일하게 XOR 연산하면 됩니다.) </pre> <br /> 어차피 8바이트 중 앞의 것은 0이므로 크게 중요하지 않고, 뒤의 8바이트가 의미를 가지는데 이것을 그대로 _HEAP_ENTRY 구조체에 memcpy를 해야 합니다. 하지만 windbg 사용 중에 코딩하는 것은 귀찮으므로(게다가 _HEAP_ENTRY의 구조도 OS마다, 패치마다 달라질 수 있으므로), 이것을 쉽게 해결하기 위해 현재 사용 중이지 않을 법한 메모리를 찾아낸 다음,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dq 000001614c0f07c0</span> 00000161`4c0f07c0 00000000`00000000 00004718`6e346ff1 00000161`4c0f07d0 00000161`4c0f0150 00000161`4c0f0150 <span style='color: blue; font-weight: bold'>00000161`4c0f07e0</span> 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> <span style='color: blue; font-weight: bold'>dq 000001614c0f07e0</span> 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 </pre> <br /> (위의 출력에서는 000001614c0f07e0 주소가 적당한 듯하니) 그 주소에 XOR 연산 결과로 나온 값을 그대로 써주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>eq 000001614c0f07e0</span> 00000161`4c0f07e0 00000000`00000000 0 0 00000161`4c0f07e8 00000000`00000000 0c000074`03010002 0c000074`03010002 00000161`4c0f07f0 00000000`00000000 </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dq 000001614c0f07e0</span> 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 </pre> <br /> 결국 _HEAP_ENTRY에 해당하는 메모리로 덤프할 수 있게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dt _HEAP_ENTRY 000001614c0f07e0</span> ntdll!_HEAP_ENTRY +0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY +0x000 PreviousBlockPrivateData : (null) <span style='color: blue; font-weight: bold'>+0x008 Size : 2</span> +0x00a Flags : 0x1 '' +0x00b SmallTagIndex : 0x3 '' +0x008 SubSegmentCode : 0x3010002 <span style='color: blue; font-weight: bold'>+0x00c PreviousSize : 0x74</span> +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 </pre> <br /> 이렇게 디코딩된 결과를 보면, 이번 0x00000161`4c0f0740 HeapAlloc 블록은 크기가 2단위이고, 이전의 블록 크기는 0x74 단위였다는 것입니다. 그리고 실제 바이트 크기로 구하려면 _HEAP_ENTRY의 크기를 구한 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>?? sizeof(_HEAP_ENTRY)</span> unsigned int64 0x10 </pre> <br /> 이 값을 각각의 단위에 곱해서 2 * 0x10 = 0x20(32바이트), 0x74 * 0x10 = 0x740(1,856 바이트)를 구할 수 있습니다. 따라서, 0x00000161`4c0f0740 블록의 다음번 블록 위치는 0x00000161`4c0f0740 + 0x20 = 0x00000161`4c0f0760이 되고 이전 블록의 위치는 0x00000161`4c0f0740 - 0x740 = 0x1614c0f0000이 됩니다. 정확하군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 아래의 글을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Windows Heap Note ; <a target='tab' href='http://wg135.github.io/blog/2018/05/31/windows-heap-note/'>http://wg135.github.io/blog/2018/05/31/windows-heap-note/</a> </pre> <br /> XOR 했던 값(0c000074`03010002)에서 하위 4바이트 중,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 03010002 </pre> <br /> 다시 하위 2바이트(0x0002)가 블록의 크기라고 하며, 상위 2바이트 중 0x03은 SmallTagIndex, 0x01은 "Heap busy"를 나타낸다고 합니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> 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.<br /> </div><br /> <br /> Heap에 관한 좀 더 자세한 정보는 첨부 파일(<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1514&boardid=331301885'>us-16-Yason-Windows-10-Segment-Heap-Internals.zip</a>)로도 제공하는 아래의 PDF 문서를 보시면 도움이 되실 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > us-16-Yason-Windows-10-Segment-Heap-Internals.zip ; <a target='tab' href='https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals-wp.pdf'>https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals-wp.pdf</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로, <a target='tab' href='https://www.sysnet.pe.kr/2/0/11255'>gflags를 이용해 hpa 옵션</a>이 적용된 프로그램의 메모리 덤프를 보면, _HEAP/_HEAP_ENTRY보다 좀 더 자세한 정보를 담고 있는 _DPH_HEAP_ROOT, _DPH_HEAP_BLOCK을 구할 수 있다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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. </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1764
(왼쪽의 숫자를 입력해야 합니다.)