Windows의 힙(heap)을 빠르게 탐색하는 방법
 

원문 : http://securityxploded.com/enumheaps.php
번역 : hackerschool

 
소개


이 문서는 Windows의 힙을 빠르게 탐색하는 방법에 대해 설명합니다.
전통적인 Windows 힙 탐색 함수는 좀 느린데, 엄청나게 많은 수의 힙 블럭들을 따라다닐 때 
시간이 많이 걸리기 때문입니다.
이 문서는 힙 관련 Windows API 리버싱을 기반으로다가 시간이 많이 걸리는 내부적인
이유를 밝혀내고, 새로운 효과적인 방법을 소개합니다.

 

 
 
이 짓을 하게 된 이유


며칠 전에, 저는 야후 메신져의 패스워드 관련 취약점을 연구하고 있었습니다.
야후 메신져는 로그인 패스워드를 힙에 저장하는데, 저는 그 패스워드를 찾아내서 뿌려주는
툴을 만들었습니다. 근데 그 패스워드는 대략 60000번째 힙 블럭에 위치하고 있었습니다 =_=
그래서 저는 패스워드가 담긴 힙에 도달하기 위해 60000번의 힙 블럭을 따라가야했습니다.
이 작업은 대략... 10분이 걸렸습니다..!
여러 PC에서 프로그램을 돌려봤으나 결과는 마찬가지였습니다.
당연히 저는 짱이 나기 시작했습니다. 그거 하나 보자고 이렇게 오래 기다려야하다니...

이 문제에 대한 해결책을 찾기 위해 인터넷을 뒤져보았으나 암것도 찾지 못했습니다.
결국엔 내가 직접 해결해보겠어!라는 생각이 들었고, 힙 관련 Windows API들을 리버싱 하기
시작했습니다. -_-v

몇 시간이 흐른 후...
저는 그렇게 시간이 오래 걸리는 이유를 발견해 냈습니다.
그리고 단 몇 초 안에 힙을 모두 검색하는 저만의 툴을 개발했습니다. ^.^
 

 
 
Windows 힙 검색하기

일반적인 Windows의힙 검색 과정은 다음과 같습니다.
 
  • * 초기화
  • CreateToolhelp32Snapshot 함수는 특정 프로세스의 정보들이 담긴 snapshot을
    얻어내기 위해 사용됩니다. 이 함수는 모든 힙 관련 API의 초반에 호출이 됩니다.

  • * 프로세스의 heap 노드들을 목록화하기
    초기화 후엔, Heap32ListFirst 와 Heap32ListNext 함수가 프로세스 안의 노드들을
    얻어내기 위해 호출됩니다.

  • * 각각의 노드 안에 있는 힙 블럭들을 검색하기
    Heap32First 와 Heap32Next 함수는 각각의 노드 안의 힙 블럭들을 나열합니다.
 

이 검색 방법은 작은 수의 힙 블럭을 탐색할 땐 큰 문제가 되지 않습니다.
하지만, 50000개 이상의 힙 블럭을 검색하기 시작하려면 10분이 넘게 걸립니다.
이 것은 오늘날의 컴퓨팅 세계에선 용납할 수 없는 시간입니다.
 

 
 
넘 느린 힙 함수들의 미스테리..


그 이유는 Heap32First과 Heap32Next 함수가 너무 너무 구리게 구현되어있었기
때문이었습니다. -_-

위 두 함수는 힙 블럭을 옮기기 위한 약간의 수학적 루틴을 제외하곤 거의 똑같았습니다.
다음은 kernel32.dll에 있는 Heap32Next 함수의 어셈 코드를 가져온 것입니다.
이 함수 내에서 무엇이 이루어지는지 함 봅시다..
 

 
.text:7C863BF8 ; BOOL __stdcall Heap32Next(LPHEAPENTRY32 lphe)
.text:7C863BF8 public _Heap32Next@4
.text:7C863BF8 _Heap32Next@4 proc near
.text:7C863BF8
.text:7C863BF8 lphe = dword ptr 8
.text:7C863BF8
.text:7C863BF8 mov edi, edi
.text:7C863BFA push ebp
.text:7C863BFB mov ebp, esp
.text:7C863BFD push ebx
.text:7C863BFE push esi
.text:7C863BFF mov esi, [ebp+lphe]
.text:7C863C02 test esi, esi
.text:7C863C04 push edi
.text:7C863C05 jz loc_7C863D0C
.text:7C863C0B cmp dword ptr [esi], 24h
.text:7C863C0E jnz loc_7C863D0C
.text:7C863C14 push 0
.text:7C863C16 push 0
.text:7C863C18 call ds:__imp__RtlCreateQueryDebugBuffer@8
.text:7C863C1E mov ebx, eax
.text:7C863C20 test ebx, ebx
.text:7C863C22 jnz short loc_7C863C2E
.text:7C863C24 mov eax, 0C0000001h
.text:7C863C29 jmp loc_7C863D18
 
 

일단 RtlCreateQueryDebugBuffer(0, FALSE) 함수를 실행하네요.
얘는 힙 데이터를 저장하기 위한 버퍼를 할당 받습니다.
이 함수 호출 후 버퍼의 주소가 리턴되고, 그게 ebx 레지스터에 저장됩니다.

만약 버퍼 할당에 실패하면 함수의 끝으로 점프하고, 그렇지 않으면 아래의 코드로
넘어갑니다.
 

 
.text:7C863C2E
.text:7C863C2E loc_7C863C2E: ; CODE XREF: Heap32Next(x)+2Aj
.text:7C863C2E push ebx ; Debug Buffer pointer
.text:7C863C2F push 14h ; PDI_HEAPS | PDI_HEAP_BLOCKS
.text:7C863C31 push dword ptr [esi+1Ch] ; Process id
.text:7C863C34 call ds:__imp__RtlQueryProcessDebugInformation@12
.text:7C863C3A mov edi, eax
.text:7C863C3C test edi, edi
.text:7C863C3E jge short loc_7C863C4D ; 다음 라인으로 계속 진행
.text:7C863C40 push ebx
.text:7C863C41 call ds:__imp__RtlDestroyQueryDebugBuffer@4
.text:7C863C47 push edi
.text:7C863C48 jmp loc_7C863D11 ; exit
 
 

다음으로 RtlQueryProcessDebugInformation 함수가 콜 됩니다.
이 함수는 프로세스의 모~~든 힙 블럭들을 로딩합니다.
다시말해 이 함수는 모든 힙 노드들과 그에 상응하는 힙 블럭들을 로딩합니다.

 
.text:7C863C4D
.text:7C863C4D loc_7C863C4D: ; CODE XREF: Heap32Next(x)+46j
.text:7C863C4D mov ecx, [ebx+38h] ; ecx = 힙 정보 구조체
.text:7C863C50 mov edx, [ecx] ;edx = 프로세스 안의 힙 노드의 수
.text:7C863C52 xor eax, eax
.text:7C863C54 test edx, edx
.text:7C863C56 jbe short loc_7C863C6A
.text:7C863C58 mov edi, [esi+20h]
.text:7C863C5B add ecx, 4 ;ecx = 첫 번째 힙 노드를 가리킴
 
 

앞서 할당받은 버퍼의 0x38번째 오프셋엔 힙 정보 구조체가 있습니다.
힙 구조체의 첫 번째 값은 node count이고, 그 다음 값은 DEBUG_HEAP_INFORMATION
구조체의 배열입니다. 이 구조체가 각각의 힙 노드를 나타내고 있습니다.

지금 설명한 힙 정보 구조체의 원형은 다음과 같슴다.

 
Struct HeapInfo
{
       DWORD heapNodeCount;
       DEBUG_HEAP_INFORMATION heapNode[heapNodeCount];
};
 
 

그리고 첫 번째 힙 노드를 가리키기 위해 ecx 레지스터가 4 증가합니다.

 
.text:7C863C5E
.text:7C863C5E loc_7C863C5E: ; CODE XREF: Heap32Next(x)+70j
.text:7C863C5E cmp [ecx], edi
.text:7C863C60 jz short loc_7C863C6A
.text:7C863C62 inc eax
.text:7C863C63 add ecx, 40h ; 다음 힙 노드로 이동
.text:7C863C66 cmp eax, edx ; 현재 count > 전체 heap node count인지 비교
.text:7C863C68 jb short loc_7C863C5E
 
 

이 것은 우리가 올바른 힙 노드에 있는지를 체크하는 루프입니다.
만약 아니라면, ecx는 다음 힙 노드를 가리키기 위해 DEBUG_HEAP_INFORMATION
사이즈로 증가됩니다.

다음은 이 구조체의 정보입니다.

 
// 힙 노드를 표현하는 구조체
typedef struct _DEBUG_HEAP_INFORMATION
{
      ULONG Base; // 0x00
      ULONG Flags; // 0x04
      USHORT Granularity; // 0x08
      USHORT Unknown; // 0x0A
      ULONG Allocated; // 0x0C
      ULONG Committed; // 0x10
      ULONG TagCount; // 0x14
      ULONG BlockCount; // 0x18
      ULONG Reserved[7]; // 0x1C
      PVOID Tags; // 0x38
      PVOID Blocks; // 0x3C 이 노드의 힙 노드를 가리키는 구조체
} DEBUG_HEAP_INFORMATION, *PDEBUG_HEAP_INFORMATION;
 
.text:7C863C6A
.text:7C863C6A loc_7C863C6A: ; CODE XREF: Heap32Next(x)+5Ej
.text:7C863C6A ; Heap32Next(x)+68j
.text:7C863C6A cmp eax, edx
.text:7C863C6C jnb short loc_7C863CB8 ; 노드를 찾지 못하면 종료
.text:7C863C6E inc dword ptr [esi+18h] ; HEAPENTRY32->dwResvd++
.text:7C863C71 mov ecx, [esi+18h]
.text:7C863C74 shl eax, 6
.text:7C863C77 mov edi, eax
.text:7C863C79 mov eax, [ebx+38h]
.text:7C863C7C lea edx, [edi+eax]
.text:7C863C7F cmp ecx, [edx+1Ch] ; 현재 count > 전체 block count를 체크
.text:7C863C82 jnb short loc_7C863CB8 ; 종료
.text:7C863C84 mov eax, ecx
.text:7C863C86 shl eax, 4
.text:7C863C89 add eax, [edx+40h] ; 이제 eax는 현재 힙 블럭을 가리킴 .text:7C863C8C test byte ptr [eax+4], 2
.text:7C863C90 jz short loc_7C863CC8
 
 

올바른 힙 노드가 발견되었다면 curHeapNode->Blocks 값을 참조하여 힙 블럭의
포인터를 얻어옵니다.

여기서 HEAPENTRY32-> dwResvd는 블럭 카운트의 트랙을 유지하기 위해 내부적으로
사용됩니다.

 
.text:7C863C92
.text:7C863C92 loc_7C863C92: ; CODE XREF: Heap32Next(x)+BEj
.text:7C863C92 mov edx, [ebx+38h]
.text:7C863C95 movzx edx, word ptr [edi+edx+0Ch] ; edx = curHeapNode->Granularity
.text:7C863C9A add edx, [eax+0Ch] ; edx += curBlock->address
.text:7C863C9D mov [esi+8], edx ; HEAPENTRY32->dwAddress = edx;
.text:7C863CA0 mov edx, [ebx+38h]
.text:7C863CA3 cmp ecx, [edi+edx+1Ch] ; check block count
.text:7C863CA7 jnb short loc_7C863CB8 ; Go to exit
.text:7C863CA9 inc ecx
.text:7C863CAA add eax, 10h ; move to next block
.text:7C863CAD mov [esi+18h], ecx ; HEAPENTRY32->dwResvd = ecx
.text:7C863CB0 test byte ptr [eax+4], 2 ; curBlock->flag & 2
.text:7C863CB4 jz short loc_7C863CCE
.text:7C863CB6 jmp short loc_7C863C92 ; continue until valid block found
 
 

위의 코드는 특정 플래그를 기반으로 유요한 힙 블럭을 찾습니다.
각가의 힙 블럭은 다음 구조체와 같습니다.

struct HeapBlock
{
      DWORD size;
      DWORD flag;
      DWORD unknown;
      DWORD address;
};
 
.text:7C863CB8
.text:7C863CB8 loc_7C863CB8: ; CODE XREF: Heap32Next(x)+74j
.text:7C863CB8 ; Heap32Next(x)+8Aj ...
.text:7C863CB8 push ebx
.text:7C863CB9 call ds:__imp__RtlDestroyQueryDebugBuffer@4
.text:7C863CBF push 12h ; dwErrCode
.text:7C863CC1 call _SetLastError@4 ; SetLastError(x)
.text:7C863CC6 jmp short loc_7C863D16
 
 

위 코드는 첨에 만들어진 버퍼를 해제하는 에러 핸들러입니다.
LAST ERROR CODE를 세팅하고, 부모 함수로 리턴합니다.

 
.text:7C863CC8
.text:7C863CC8 loc_7C863CC8: ; CODE XREF: Heap32Next(x)+98j
.text:7C863CC8 mov ecx, [esi+0Ch] ; ecx = HEAPENTRY32->dwBlockSize
.text:7C863CCB add [esi+8], ecx ; HEAPENTRY32->dwAddress += ecx
 
 

첫 번째 블럭에서, 만약 curBlock->flag & 2 != 2 이라면, 현재 블럭의 주소는
전 블럭 주소와 사이즈를 더하여 구해집니다.

 
.text:7C863CCE
.text:7C863CCE loc_7C863CCE: ; CODE XREF: Heap32Next(x)+BCj
.text:7C863CCE mov ecx, [eax]
.text:7C863CD0 xor edi, edi
.text:7C863CD2 mov [esi+0Ch], ecx ; HEAPENTRY32->dwBlockSize = ecx
.text:7C863CD5 mov ax, [eax+4]
.text:7C863CD9 inc edi
.text:7C863CDA test al, 0F1h
.text:7C863CDC jnz short loc_7C863CFE
.text:7C863CDE test ah, 2
.text:7C863CE1 jnz short loc_7C863CFE
.text:7C863CE3 test al, 20h
.text:7C863CE5 jz short loc_7C863CF0
.text:7C863CE7 mov dword ptr [esi+10h], 4 ; HEAPENTRY32->dwFlags=4
.text:7C863CEE jmp short loc_7C863D01
.text:7C863CF0
.text:7C863CF0 loc_7C863CF0: ; CODE XREF: Heap32Next(x)+EDj
.text:7C863CF0 test ah, 1
.text:7C863CF3 jz short loc_7C863D01
.text:7C863CF5 mov dword ptr [esi+10h], 2 ; HEAPENTRY32->dwFlags=2
.text:7C863CFC jmp short loc_7C863D01
.text:7C863CFE
.text:7C863CFE loc_7C863CFE: ; CODE XREF: Heap32Next(x)+E4j
.text:7C863CFE ; Heap32Next(x)+E9j
.text:7C863CFE mov [esi+10h], edi ; HEAPENTRY32->dwFlags=4
 
 

올바른 힙 블럭이 찾아졌을 때, 그 힙의 타입은 LF32_FIXED, LF32_FREE 혹은
LF32_MOVEABLE 값으로 결정됩니다.

이 정보를 이용하여 HEAPENTRY32->dwFlags 값이 업데이트 됩니다.

 
.text:7C863D01
.text:7C863D01 loc_7C863D01: ; CODE XREF: Heap32Next(x)+F6j
.text:7C863D01 ; Heap32Next(x)+FBj ...
.text:7C863D01 push ebx
.text:7C863D02 call ds:__imp__RtlDestroyQueryDebugBuffer@4
.text:7C863D08 mov eax, edi
.text:7C863D0A jmp short loc_7C863D18
 
 

마지막 부분에선 RtlDestroyQueryDebugBuffer 함수를 통해 할당받은 버퍼가 해제됩니다.

 
.text:7C863D0C
.text:7C863D0C loc_7C863D0C: ; CODE XREF: Heap32Next(x)+Dj
.text:7C863D0C ; Heap32Next(x)+16j
.text:7C863D0C push 0C0000004h
.text:7C863D11
.text:7C863D11 loc_7C863D11: ; CODE XREF: Heap32Next(x)+50j
.text:7C863D11 call _BaseSetLastNTError@4 ; BaseSetLastNTError(x)
.text:7C863D16
.text:7C863D16 loc_7C863D16: ; CODE XREF: Heap32Next(x)+CEj
.text:7C863D16 xor eax, eax
.text:7C863D18
.text:7C863D18 loc_7C863D18: ; CODE XREF: Heap32Next(x)+31j
.text:7C863D18 ; Heap32Next(x)+112j
.text:7C863D18 pop edi
.text:7C863D19 pop esi
.text:7C863D1A pop ebx
.text:7C863D1B pop ebp
.text:7C863D1C retn 4
.text:7C863D1C Heap32Next@4 endp
 
 

만약 에러가 발생하면, BaseSetLastNTError 함수가 호출되고, LAST ERROR가 세팅됩니다.
그렇지 않다면 이 함수는 정상적으로 리턴됩니다.

자, 여기까지를 정리하면 Heap32Next 함수 호출 시 다음의 행동들이 일어납니다.
 

  • 먼저 전체 힙 데이터를 저장하기 위한 커다란 버퍼가 RtlCreateQueryDebugBuffer
    함수에 의해 살당됩니다.
    다음으로 모든 힙 데이터를 위 버퍼에 저장하기 위한 RtlQueryProcessDebugInformation
    함수가 호출됩니다.
  • 그 다음엔 올바른 힙 노드를 찾기 위해 모든 힙 노드들을 탐색합니다.
    그 다음, 각각의 힙 노드 안의 모든 힙 블럭들을 탐색합니다.
    다음의 유효한 힙 블럭을 찾기 위함입니다.
    마지막으로 버퍼 해제를 위해 RtlDestroyQueryDebugBuffer 함수가 호출됩니다.
  •  
 

이러한 과정은 Heap32Next 함수가 호출될 때마다 매번 발생합니다.
만약 50000개의 힙 블럭을 탐색한다고 가정해 봅시다.
그렇다면 매 함수 호출 때마다 버퍼가 생성되고, 프로세스의 전체 힙이 로딩되고,
탐색하고, 버퍼를 해제합니다. 매 호출 때마다.. =_=

이것이 바로 많은 수의 힙을 탐색할 때 시간이 그렇게 오래 걸리는 이유였던 겁니다.

다시 말해, 잘못된 설계가 문제입니다.
버퍼 할당, 힙 저장, 등의 작업은 딱 한번만 이루어져도 됩니다.
다음 위치의 유효한 힙 블럭을 찾기 위한 탐색 작업만 하면 됩니다.

다음은 단 몇 초 안에 거대한 힙 블럭을 탐색하는 방법입니다.

 
 
힙을 열라 빠르게 탐색하는 방법!


위의 사실에 근거하여 저는 저만의 힙 탐색 함수를 짰습니다.
Windows의 힙 API들보다 훨 빠르고 효과적입니다 -_-v
다음은 제가 함수를 구현한 방법입니다.

소스 코드를 간결하고 읽기 쉽게 하기위해 에러 체킹 같은 것은 없앴습니다.

 
사용된 구조체들
 
//Debug Buffer used by RtlCreateQueryDebugBuffer
typedef struct _DEBUG_BUFFER
{
      HANDLE SectionHandle;
      PVOID SectionBase;
      PVOID RemoteSectionBase;
      ULONG SectionBaseDelta;
      HANDLE EventPairHandle;
      ULONG Unknown[2];
      HANDLE RemoteThreadHandle;
      ULONG InfoClassMask;
      ULONG SizeOfInfo;
      ULONG AllocatedSize;
      ULONG SectionSize;
      PVOID ModuleInformation;
      PVOID BackTraceInformation;
      PVOID HeapInformation;
      PVOID LockInformation;
      PVOID Reserved[8];
} DEBUG_BUFFER, *PDEBUG_BUFFER;

//This represents each heap node
typedef struct _DEBUG_HEAP_INFORMATION
{
      ULONG Base; // 0x00
      ULONG Flags; // 0x04
      USHORT Granularity; // 0x08
      USHORT Unknown; // 0x0A
      ULONG Allocated; // 0x0C
      ULONG Committed; // 0x10
      ULONG TagCount; // 0x14
      ULONG BlockCount; // 0x18
      ULONG Reserved[7]; // 0x1C
      PVOID Tags; // 0x38
      PVOID Blocks; // 0x3C
} DEBUG_HEAP_INFORMATION, *PDEBUG_HEAP_INFORMATION;

//Internal structure used to store heap block information.
struct HeapBlock
{
      PVOID dwAddress;
      DWORD dwSize;
      DWORD dwFlags;
      ULONG reserved;
};
 
 
힙 노드들을 탐색하기


다음은 프로세스의 모든 힙 노드들을 탐색하는 예제 코드입니다.
버퍼를 할당받고, RtlQueryProcessDebugInformation 함수를 이용해서 전체 힙을
로딩합니다. 그 담에 모든 힙 노드들을 열거하면서 관련 정보들을 뿌려줍니다.
마지막에선 버퍼가 해제됩니다.
 

 
void DisplayHeapNodes(DWORD pid)
{
  // Create debug buffer
  PDEBUG_BUFFER db = RtlCreateQueryDebugBuffer(0, FALSE);

  // Get process heap data
  RtlQueryProcessDebugInformation( pid, PDI_HEAPS | PDI_HEAP_BLOCKS, db);


  ULONG heapNodeCount = db->HeapInformation ? *PULONG(db->HeapInformation):0;

  PDEBUG_HEAP_INFORMATION heapInfo = PDEBUG_HEAP_INFORMATION(PULONG(db-> HeapInformation) + 1);

  // Go through each of the heap nodes and dispaly the information
  for (unsigned int i = 0; i < heapNodeCount; i++)
  {
    printf("\n Base Address = 0x%.8x", heapInfo[i].Base);
    printf("\n Block count = %d", heapInfo[i].BlockCount);
    printf("\n Committed Size= 0x%.8x", heapInfo[i].Committed);
    printf("\n Allocated Size = 0x%.8x", heapInfo[i].Allocated);
    printf("\n Flags = 0x%.8x", heapInfo[i].Flags);
  }

  // Clean up the buffer
  RtlDestroyQueryDebugBuffer( db );
}
 
 
 노드 안의 힙 블럭들을 열거하기

아래의 코드는 프로세스 내 특정 노드 안의 힙 블럭들을 열거하는 모습을 보여줍니다.
 
 
void DisplayHeapBlocks(DWORD pid, DWORD nodeAddress)
{
  HeapBlock hb ={0,0,0,0};

  // Create debug buffer
  PDEBUG_BUFFER db = RtlCreateQueryDebugBuffer(0, FALSE);

  // Get process heap data
  LONG ret = RtlQueryProcessDebugInformation( pid, PDI_HEAPS | PDI_HEAP_BLOCKS, db);

  ULONG heapNodeCount = db->HeapInformation ? *PULONG(db->HeapInformation) : 0;

  PDEBUG_HEAP_INFORMATION heapInfo = PDEBUG_HEAP_INFORMATION(PULONG(db->HeapInformation) + 1);

  // Go through each of the heap nodes
  for (unsigned int i = 0; i < heapNodeCount; i++)
  {
    if(heapInfo[i].Base == nodeAddress)
    {
      // Now enumerate all blocks within this heap node...
      int c = 0;
      memset(&hb,0,sizeof(hb));

      if( GetFirstHeapBlock(&heapInfo[i] , &hb) )
      {
        do
        {
          printf("\n Block count = %d", c+1);
          printf("\n Block address = 0x%.8x", hb.dwAddress);
          printf("\n Block Size = 0x%.8x", hb.dwSize);

          if( hb.dwFlags == LF32_FREE )
             printf("\n Status = Free");
          else
             printf("\n Status = Allocated");

          c++;
        }
        while( GetNextHeapBlock( &heapInfo[i], &hb) );
      }
      break;
    }
  }

  // Clean up the buffer
  RtlDestroyQueryDebugBuffer( db );
}
 
 

이 함수는 힙을 초기화하고, 특정 힙 노드를 찾기 위해 모든 힙 노드들을 탐색합니다.
그 담에 GetFirstHeapBlock과GetNextHeapBlock 함수를 이용하여 그 노드 안의 모든
힙 블럭들을 열거하고, 관련 정보를 뿌려줍니다.

 
BOOL GetFirstHeapBlock( PDEBUG_HEAP_INFORMATION curHeapNode, HeapBlock *hb)
{
  int *block;

  hb->reserved = 0;
  hb->dwAddress = 0;
  hb->dwFlags = 0;

  block = (int*) curHeapNode->Blocks;

  while( ( *(block+1) & 2 ) == 2 )
  {
    hb->reserved++;
    hb->dwAddress = (void *) ( *(block+3) + curHeapNode->Granularity );
    block = block+4;
    hb->dwSize = *block;
  }

  // Update the flags...
  USHORT flags = *(block+1);

  if( ( flags & 0xF1 ) != 0 || ( flags & 0x0200 ) != 0 )
    hb->dwFlags = 1;
  else if( (flags & 0x20) != 0 )
         hb->dwFlags = 4;
       else if( (flags & 0x0100) != 0 )
              hb->dwFlags = 2;

   return TRUE;
}



BOOL GetNextHeapBlock( PDEBUG_HEAP_INFORMATION curHeapNode, HeapBlock *hb)
{
  int *block;

  hb->reserved++;
  block = (int*) curHeapNode->Blocks;

  // Make it point to next block address entry
  block = block + hb->reserved * 4;

  if( ( *(block+1) & 2 ) == 2 )
  {
    do
    {
      // new address = curBlockAddress + Granularity ;
      hb->dwAddress = (void *) ( *(block+3) + curHeapNode->Granularity );

      // If all the blocks have been enumerated....exit
      if( hb->reserved > curHeapNode->BlockCount)
        return FALSE;

      hb->reserved++;
      blockAddress = block + 4; //move to next block
      hb->dwSize = *block;
     }
     while( ( *(block+1) & 2 ) == 2 );
  }
  else
  {
    // New Address = prev Address + prev block size ;
    hb->dwAddress = (void*) ( (int)hb->dwAddress + hb->dwSize );
    hb->dwSize = *block;
  }

  // Update the flags...
  USHORT flags = *( block+1);

  if( ( flags & 0xF1 ) != 0 || ( flags & 0x0200 ) != 0 )
    hb->dwFlags = 1;
  else if( (flags & 0x20) != 0 )
         hb->dwFlags = 4;
       else if( (flags & 0x0100) != 0 )
              hb->dwFlags = 2;

  return TRUE;
}
 
 

위 코드의 대부분은 Heap32First 와 Heap32Next 함수를 리버싱해서 뽑아온 것입니다.
GetFirstHeapBlock 함수는 현재 힙 노드 안의 첫 번째 유효한 힙을 리턴합니다.
GetNextHeapBlock 함수는 다음 힙 블럭을 찾기 위한 비슷한 일을 합니다.

바로 직전 힙의 정보는 HeapBlock 구조체에 저장되어 있습니다.
얘는 부모 함수에 의해 건드려지면 안됩니다.

만약 당신의 프로젝트에서 이와 같은 함수를 구현하길 원한다면, 다음의 템플릿을
가져다 쓰시면 됩니다. ^.^

 
1. InitHeap()
이 것은 RtlCreateQueryDebugBuffer 와 RtlQueryProcessDebugInformation
함수를 호출하여 프로세스 힙 데이터를 초기화합니다.

2. GetHeapNode(int index)
이 함수는 노드들을 탐색한 다음 특정 인덱스의 노드를 리턴합니다.

3. GetFirstHeapBlock(PDEBUG_HEAP_INFORMATION curHeapNode, HeapBlock *hb)
특정 노드 내의 첫 번째 유효한 힙 블럭을 리턴합니다.

4. GetNextHeapBlock(PDEBUG_HEAP_INFORMATION curHeapNode, HeapBlock *hb)
그 다음 유효한 힙 블럭을 리턴합니다.
이 힙 블럭은 이전의 힙 블럭의 정보를 가지고 있습니다.

5. DestroyHeap()
RtlDestroyQueryDebugBuffer 함수를 호출해서 버퍼를 초기화합니다.
 
 
 결론


이 문서를 통해 Windows 힙 API의 내부 구현을 살펴봤습니다.
글구 도대체 왜케 느린지에 대해 분석을 해봤습니다.
마지막으로 더 빠르게 힙 탐색을 할 수 있는 방법에 대해 알아보았습니다. ^.^ 

 
 
 툴 다운로드
   http://securityxploded.com/ProcHeapViewer.php