Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 132. windbg/Visual Studio - HeapFree x64의 동작 분석 [링크 복사], [링크+제목 복사]
조회: 478
글쓴 사람
홈페이지
첨부 파일
 

windbg/Visual Studio - HeapFree x64의 동작 분석

지난 글에서 x86으로 다뤘는데,

windbg/Visual Studio - HeapFree x86의 동작 분석
; https://www.sysnet.pe.kr/2/0/12059

이번엔 동일한 예제 코드를 x64로 바꿔 분석해 보겠습니다.

#include <iostream>
#include <windows.h>

int main()
{
    int size = 20;

    HANDLE hHandle = HeapCreate(0, 0, 8192);
    printf("hHandle == 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);

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

    HeapDestroy(hHandle);

    printf("Exited!");

    return 0;
}

미리 언급하자면, 위의 코드로 출력된 pVoid 주소의 값을 메모리 창으로 확인하면 (64비트 임에도) 윈도우 Heap의 경우 여전히 8바이트 값만 의미 있게 채워져 있는 것을 볼 수 있습니다. (좀 더 자세한 사항은 아래에서 한 번 더 다룹니다.)

vs_heap_free_2.png




지난번과 마찬가지로 역시 HeapFree부터 Disassembly(Alt + G) 창을 띄워 call 명령까지 F11 키를 눌러 살펴보겠습니다.

    67:     HeapFree(hHandle, 0, pVoid1);
00007FF6D86159BC 4C 8B 85 08 02 00 00 mov    r8,qword ptr [pVoid4]     // HeapFree에 전달할 세 번째 인자
00007FF6D86159C3 33 D2                xor    edx,edx                   // HeapFree의 전달할 두 번째 인자
00007FF6D86159C5 48 8B 4D 28          mov    rcx,qword ptr [hHandle]   // HeapFree의 전달할 첫 번째 인자
00007FF6D86159C9 FF 15 B1 A6 00 00    call   qword ptr [__imp_HeapFree (07FF6D8620080h)]  

07FF6D8620080h 주소를 메모리 창에서 보면 "00007ffdf5d66350" 값이 나오는데 실제로 call에서 F11 키를 누르면 다음의 주소로 이동하고,

HeapFreeStub:
00007FFDF5D66350 48 FF 25 21 1A 06 00 jmp         qword ptr [__imp_HeapFree (07FFDF5DC7D78h)]   // 07FFDF5DC7D78h == 00007FFDF6EFFBC0

JUMP를 하면 다음의 코드가 나옵니다.

00007FFDF6EFFBC0 48 89 5C 24 08       mov         qword ptr [rsp+8],rbx  // non-volatile regsiter RBX 보관
00007FFDF6EFFBC5 48 89 74 24 10       mov         qword ptr [rsp+10h],rsi  // non-volatile regsiter RSI 보관
00007FFDF6EFFBCA 57                   push        rdi  
00007FFDF6EFFBCB 48 83 EC 30          sub         rsp,30h   // 여기까지 x64 함수 prologue

00007FFDF6EFFBCF 49 8B F8             mov         rdi,r8    // rdi == r8 == pVoid1
00007FFDF6EFFBD2 8B F2                mov         esi,edx   // esi == edx == 0
00007FFDF6EFFBD4 48 8B D9             mov         rbx,rcx   // rbx == rcx == hHandle
00007FFDF6EFFBD7 4D 85 C0             test        r8,r8  
00007FFDF6EFFBDA 74 52                je          RtlFreeHeap+6Eh (07FFDF6EFFC2Eh)  
00007FFDF6EFFBDC 48 85 C9             test        rcx,rcx  
00007FFDF6EFFBDF 0F 84 29 50 07 00    je          memset+11C4Eh (07FFDF6F74C0Eh)  
00007FFDF6EFFBE5 81 7B 10 EE DD EE DD cmp         dword ptr [rbx+10h],0DDEEDDEEh // dword([rbx + 10h]) == 0xffeeffee
00007FFDF6EFFBEC 44 8B C6             mov         r8d,esi   // RtlpFreeHeapInternal에 전달할 3번째 인자 == 0 (HeapFree에 전달한 dwFlags)
00007FFDF6EFFBEF 48 8B D7             mov         rdx,rdi   // RtlpFreeHeapInternal에 전달할 2번째 인자 == pVoid1
00007FFDF6EFFBF2 48 8B CB             mov         rcx,rbx   // RtlpFreeHeapInternal에 전달할 1번째 인자 == hHandle
00007FFDF6EFFBF5 74 30                je          RtlFreeHeap+67h (07FFDF6EFFC27h)  
00007FFDF6EFFBF7 F6 05 5A 64 12 00 02 test        byte ptr [RtlpHpHeapFeatures (07FFDF7026058h)],2  
00007FFDF6EFFBFE 75 43                jne         RtlFreeHeap+83h (07FFDF6EFFC43h)  
00007FFDF6EFFC00 45 33 C9             xor         r9d,r9d   // RtlpFreeHeapInternal에 전달할 4번째 인자 == 0
00007FFDF6EFFC03 48 C7 44 24 20 00 00 00 00 mov         qword ptr [rsp+20h],0  // // RtlpFreeHeapInternal에 전달할 5번째 인자 == 0
00007FFDF6EFFC0C E8 6F 04 00 00       call        RtlpFreeHeapInternal (07FFDF6F00080h)  
00007FFDF6EFFC11 44 8B C8             mov         r9d,eax  
00007FFDF6EFFC14 41 8B C1             mov         eax,r9d  

00007FFDF6EFFC17 48 8B 5C 24 40       mov         rbx,qword ptr [rsp+40h]  // 이후 epilogue
00007FFDF6EFFC1C 48 8B 74 24 48       mov         rsi,qword ptr [rsp+48h]  
00007FFDF6EFFC21 48 83 C4 30          add         rsp,30h  
00007FFDF6EFFC25 5F                   pop         rdi  
00007FFDF6EFFC26 C3                   ret 

초기의 [rsp + 8], [rsp + 10] 등은 원래 parameter homing space로 rcx, rdx, r8, r9를 저장하는 용도인데,

[rsp+08h] : 호출 함수에 전달한 파라미터 1을 저장
[rsp+10h] : 호출 함수에 전달한 파라미터 2를 저장
[rsp+18h] : 호출 함수에 전달한 파라미터 3을 저장
[rsp+20h] : 호출 함수에 전달한 파라미터 4를 저장

(어차피 release 빌드에서는 쓰지도 않으므로) 그냥 non-volatile 레지스터를 위한 (스택 대신) 보관 용도로 쓰고 있습니다. 어쨌든 결국, HeapFree는 (x86과 같이) 내부적으로 "RtlpFreeHeapInternal(hHandle, pVoid1, 0, 0, 0)"을 호출하고 있습니다.

이제 RtlpFreeHeapInternal로 넘어가면,

00007FFDF6F00080 48 89 5C 24 10       mov         qword ptr [rsp+10h],rbx  
00007FFDF6F00085 48 89 6C 24 18       mov         qword ptr [rsp+18h],rbp  
00007FFDF6F0008A 48 89 74 24 20       mov         qword ptr [rsp+20h],rsi  
00007FFDF6F0008F 57                   push        rdi  
00007FFDF6F00090 41 54                push        r12  
00007FFDF6F00092 41 55                push        r13  
00007FFDF6F00094 41 56                push        r14  
00007FFDF6F00096 41 57                push        r15  
00007FFDF6F00098 48 81 EC 80 00 00 00 sub         rsp,80h  // 여기까지 x64 함수 prologue
00007FFDF6F0009F 33 FF                xor         edi,edi  // edi = 0
00007FFDF6F000A1 4D 8B E1             mov         r12,r9   // r12 == r9 == 4번째 인자 == 0
00007FFDF6F000A4 81 79 10 EE DD EE DD cmp         dword ptr [rcx+10h],0DDEEDDEEh  // dword([hHandle + 10]) == 0xffeeffee
00007FFDF6F000AB 45 8B F8             mov         r15d,r8d // r15 == r8 == 3번째 인자 == 0
00007FFDF6F000AE 48 8B F2             mov         rsi,rdx  // rsi == rdx == 2번째 인자 == pVoid1
00007FFDF6F000B1 48 8B E9             mov         rbp,rcx  // rbp == rcx == 1번째 인자 == hHandle
00007FFDF6F000B4 44 8B EF             mov         r13d,edi  // r13d == edi == 0
00007FFDF6F000B7 0F 85 E9 06 00 00    jne         RtlpFreeHeapInternal+726h (07FFDF6F007A6h)  
...[이하 생략]...

(일반적인 Heap Handle의 경우) [rcx+10h]의 값이 0DDEEDDEEh와는 달라서 07FFDF6F007A6h로 jump합니다. (0DDEEDDEE의 heap을 만들 수 있는 방법을 아신다면 덧글 부탁드립니다. ^^ 게다가 이 작업은 __imp_HeapFree에서도 이미 한번 했었습니다.)

00007FFDF6F007A6 F7 41 74 00 00 00 01 test        dword ptr [rcx+74h],1000000h  // hHandle + offset 74h == 0000000000000000
00007FFDF6F007AD 75 4D                jne         RtlpFreeHeapInternal+77Ch (07FFDF6F007FCh)  
00007FFDF6F007AF F6 41 78 01          test        byte ptr [rcx+78h],1  // hHandle + offset 78h == 0010000000000000
00007FFDF6F007B3 0F 85 B1 55 06 00    jne         memset+2DAAh (07FFDF6F65D6Ah)  
00007FFDF6F007B9 40 F6 C6 0F          test        sil,0Fh   // sil == esi 하위 8비트 == rsi(pVoid1)의 하위 8비트
                                                            // pVoid1이 0x1c1c40d07b0인 경우, 0xb0
                                                            // 0xb0 and 0xf == 0
00007FFDF6F007BD 0F 84 87 00 00 00    je          RtlpFreeHeapInternal+7CAh (07FFDF6F0084Ah)  
...[이하 생략]...

위의 코드는 HeapAlloc으로 할당받은 주소의 하위 8비트에 대해 0x0f와 and 연산 값이 0인 탓에 07FFDF6F0084Ah 주소로 점프합니다.

00007FFDF6F0084A 4C 8D 6A F0          lea         r13,[rdx-10h]  // r13 == rdx(pVoid1)의 16바이트 이전 주소
00007FFDF6F0084E 41 0F 0D 4D 00       prefetchw   [r13]  
00007FFDF6F00853 41 80 7D 0F 05       cmp         byte ptr [r13+0Fh],5  // pVoid1의 8바이트 이전 값의 첫 번째 1 바이트
                                                                        // pVoid1 주솟값이 0x000001C1C40D07B0인 경우,
                                                                        // [0x000001C1C40D07B0 - 0x10 + 0x0f] 위치의 1바이트
                                                                        // 이 글에서 분석 시에는 0x1f (ZR = 0)
00007FFDF6F00858 75 0C                jne         RtlpFreeHeapInternal+7E6h (07FFDF6F00866h) // ==> jump to 00007FFDF6F00866
...[이하 생략]...
00007FFDF6F00866 41 F6 45 0F 3F       test        byte ptr [r13+0Fh],3Fh  // 다시 0x1f와 3fh를 and 연산 테스트 (ZR = 0)
00007FFDF6F0086B 0F 85 6E FF FF FF    jne         RtlpFreeHeapInternal+75Fh (07FFDF6F007DFh) // ==> jump to 07FFDF6F007DFh
...[이하 생략]...

역시 x86 코드와 마찬가지로 header 부분의 바이트 1개를 가지고 체크를 한 다음 00007FFDF6F007DF로 점프합니다.

00007FFDF6F007DF 4D 85 ED             test        r13,r13  // r13 == rdx(pVoid1)의 16바이트 이전 주소 
                                                           // 따라서 ZR = 0
00007FFDF6F007E2 0F 84 8F 55 06 00    je          memset+2DB7h (07FFDF6F65D77h)  
00007FFDF6F007E8 80 7E FF 05          cmp         byte ptr [rsi-1],5  // rsi(pVoid1)의 1바이트, 결국 r13 + 0fh와 같은 값 (ZR = 0)
00007FFDF6F007EC 0F 84 B6 55 06 00    je          memset+2DE8h (07FFDF6F65DA8h)  
00007FFDF6F007F2 41 38 7D 0F          cmp         byte ptr [r13+0Fh],dil  // [r13+0fh]는 이전과 같이 1f
                                                               // dil == [rdi의 하위 8비트] = 0 (HeapFree 함수에서 rdi = 0으로 초기화)
                                                               // OV = 0, PL = 0
00007FFDF6F007F6 0F 8C 1A 57 06 00    jl          memset+2F56h (07FFDF6F65F16h)  
00007FFDF6F007FC 41 8B D7             mov         edx,r15d  // edx == r15(3번째 인자) == 0
00007FFDF6F007FF 4C 8B CE             mov         r9,rsi    // r9 == rsi(pVoid1)
00007FFDF6F00802 83 CA 02             or          edx,2     // 0 or 2 == 2
00007FFDF6F00805 4D 8B C5             mov         r8,r13    // r8 == r13 == rdx(pVoid1)의 16바이트 이전 주소 
00007FFDF6F00808 48 8B CD             mov         rcx,rbp   // rcx == rbp == 1번째 인자 == hHandle
00007FFDF6F0080B E8 50 11 00 00       call        RtlpFreeHeap (07FFDF6F01960h)  
...[이하 생략]...

한 번 더 1바이트 값으로 체크를 한 다음, 실제적인 Heap 해제를 위한 RtlpFreeHeap 단계로 아래와 같은 호출 유형으로 넘어갑니다.

RtlpFreeHeap(hHandle, 2, pVoid1 - 0x10, pVoid1);

대충 초입을 분석하긴 했지만 실질적인 해제 작업이므로 여기서는 더 이상 분석하지 않겠습니다.

00007FFDF6F01960 48 89 5C 24 10       mov         qword ptr [rsp+10h],rbx  
00007FFDF6F01965 48 89 74 24 20       mov         qword ptr [rsp+20h],rsi  
00007FFDF6F0196A 4C 89 44 24 18       mov         qword ptr [rsp+18h],r8  
00007FFDF6F0196F 48 89 4C 24 08       mov         qword ptr [rsp+8],rcx  
00007FFDF6F01974 57                   push        rdi  
00007FFDF6F01975 41 54                push        r12  
00007FFDF6F01977 41 55                push        r13  
00007FFDF6F01979 41 56                push        r14  
00007FFDF6F0197B 41 57                push        r15  
00007FFDF6F0197D 48 81 EC 30 01 00 00 sub         rsp,130h  // 여기까지 함수 prologue
00007FFDF6F01984 8B FA                mov         edi,edx   // edi == edx == 두 번째 인자 == 2
00007FFDF6F01986 48 8B D9             mov         rbx,rcx   // rbx == rcx == 첫 번째 인자 == hHandle
00007FFDF6F01989 41 B4 01             mov         r12b,1    // r12b == 1
00007FFDF6F0198C 44 88 64 24 41       mov         byte ptr [rsp+41h],r12b   // (byte)rsp+41h == r12b == 1
00007FFDF6F01991 C6 44 24 40 00       mov         byte ptr [rsp+40h],0      // (byte)rsp+40h == 0
00007FFDF6F01996 C7 44 24 68 01 00 00 00 mov         dword ptr [rsp+68h],1  // (int)rsp+68h == 1
00007FFDF6F0199E 45 33 ED             xor         r13d,r13d  // r13d == 0
00007FFDF6F019A1 4C 89 6C 24 70       mov         qword ptr [rsp+70h],r13  // (size_t)rsp+70h == 0
00007FFDF6F019A6 66 44 89 6C 24 44    mov         word ptr [rsp+44h],r13w  // (short)rsp+44h == 0
00007FFDF6F019AC 49 3B C8             cmp         rcx,r8    // cmp hHandle, r8(3번째 인자 == pVoid1의 16바이트 이전 주소)
                                                            // PL = 1, ZR = 0
00007FFDF6F019AF 0F 84 C7 3C 07 00    je          memset+126BCh (07FFDF6F7567Ch)  
00007FFDF6F019B5 0B 79 74             or          edi,dword ptr [rcx+74h]  // 2 or [rcx + 74h] = 2
00007FFDF6F019B8 F7 C7 60 0F 01 7D    test        edi,7D010F60h  // 2 and 7D010F60h
                                                                 // ZR = 1
00007FFDF6F019BE 0F 85 3C 11 00 00    jne         RtlpFreeHeap+11A0h (07FFDF6F02B00h)  
00007FFDF6F019C4 45 8D 45 03          lea         r8d,[r13+3]    // r8d == r13 + 3 == 0 + 3
00007FFDF6F019C8 65 48 8B 04 25 60 00 00 00 mov         rax,qword ptr gs:[60h]  // rax == gs[60h] == 0000000004010000 == PEB 주소
00007FFDF6F019D1 48 8B 88 90 00 00 00 mov         rcx,qword ptr [rax+90h]  // rcx == peb:90h == ProcessHeaps
00007FFDF6F019D8 48 85 C9             test        rcx,rcx  
00007FFDF6F019DB 0F 85 D5 3C 07 00    jne         memset+126F6h (07FFDF6F756B6h)  
00007FFDF6F019E1 41 BE 80 03 FE 7F    mov         r14d,7FFE0380h  
00007FFDF6F019E7 41 8B CE             mov         ecx,r14d  
00007FFDF6F019EA 44 38 29             cmp         byte ptr [rcx],r13b  
00007FFDF6F019ED 0F 85 EE 3C 07 00    jne         memset+12721h (07FFDF6F756E1h)  
00007FFDF6F019F3 48 8B B4 24 70 01 00 00 mov         rsi,qword ptr [rsp+170h]  
00007FFDF6F019FB 40 F6 C7 01          test        dil,1  
00007FFDF6F019FF 0F 85 AA 0A 00 00    jne         RtlpFreeHeap+0B4Fh (07FFDF6F024AFh)  
00007FFDF6F01A05 48 8B 8B 60 01 00 00 mov         rcx,qword ptr [rbx+160h]  
00007FFDF6F01A0C E8 4F 4C 00 00       call        RtlTryEnterCriticalSection (07FFDF6F06660h)  
00007FFDF6F01A11 85 C0                test        eax,eax  
00007FFDF6F01A13 0F 84 81 07 00 00    je          RtlpFreeHeap+83Ah (07FFDF6F0219Ah)  
00007FFDF6F01A19 FF 83 68 02 00 00    inc         dword ptr [rbx+268h]  
00007FFDF6F01A1F C6 44 24 40 01       mov         byte ptr [rsp+40h],1  
00007FFDF6F01A24 83 7B 7C 00          cmp         dword ptr [rbx+7Ch],0  
00007FFDF6F01A28 74 25                je          RtlpFreeHeap+0EFh (07FFDF6F01A4Fh)  
...[생략]...




일단 중요한 것은, 사실상 Header 영역은 "lea r13, [rdx - 10h]" 코드에 따라 이전의 16바이트 주소라는 점입니다. 하지만 실제로 코드 분석을 해보면 8 ~ 16 바이트 영역은 검사하지 않고 0 ~ 8 바이트 영역만 검사를 합니다. 확인을 위해 8 ~ 16 바이트 영역의 데이터를 변조해 봐도,

*(((size_t*)pVoid4) - 2) = 0x8bf1f1f2f2f2f2f2;
*(((size_t*)pVoid3) - 2) = 0x8bf1f1f2f2f2f2f2;
*(((size_t*)pVoid2) - 2) = 0x8bf1f1f2f2f2f2f2;
*(((size_t*)pVoid1) - 2) = 0x8bf1f1f2f2f2f2f2;

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

비정상 종료가 발생하지 않습니다. 반면, 0 ~ 8바이트 영역의 1바이트만 바꿔보면,

0x000001C1C40D0788  0c0001730cdfbe8c
0x000001C1C40D0790  ffffffffffffffff

RtlpFreeHeap 내부에서 x86 때와 마찬가지로 2 종류의 예외가 발생하게 됩니다. (별로 보고 싶지 않겠지만 그냥 기록용으로 남깁니다. ^^)

...[생략]...
00007FFDF6F01A2A 8B 93 88 00 00 00    mov         edx,dword ptr [rbx+88h]  // [hHandle + 88h] == 0x0fdebe8e
00007FFDF6F01A30 33 56 08             xor         edx,dword ptr [rsi+8]  // rsi == pVoid + 16바이트의 주소
                                                                         // 0x0fdebe8e xor (rsi + 8 == security cookie) == 0x03010002
00007FFDF6F01A33 89 56 08             mov         dword ptr [rsi+8],edx  // 0c0001730cdfbe8c 하위 4바이트에 0x03010002 쓰기
                                                                         // 0c00017303010002
00007FFDF6F01A36 8B CA                mov         ecx,edx  // ecx == 0x03010002
00007FFDF6F01A38 C1 E9 10             shr         ecx,10h  // ecx == 0x03010002 >> 10 == 301
00007FFDF6F01A3B 8B C2                mov         eax,edx  // eax == 0x03010002
00007FFDF6F01A3D C1 E8 08             shr         eax,8    // eax == 0x03010002 >> 8 == 30100
00007FFDF6F01A40 32 C8                xor         cl,al    // cl == 1, al == 0
                                                           // cl xor al == 1
00007FFDF6F01A42 32 CA                xor         cl,dl    // cl == 1, dl == 2
                                                           // 1 xor 2 == 3
00007FFDF6F01A44 C1 EA 18             shr         edx,18h  // edx == 0x03010002 >> 18h == 3
00007FFDF6F01A47 3A D1                cmp         dl,cl    // cmp 3, 3 (ZR = 1)
00007FFDF6F01A49 0F 85 EA 0A 00 00    jne         RtlpFreeHeap+0BD9h (07FFDF6F02539h)  
00007FFDF6F01A4F 0F B7 56 08          movzx       edx,word ptr [rsi+8]  // edx == short[rsi + 8] == 0x0002
00007FFDF6F01A53 48 8B 8B 38 01 00 00 mov         rcx,qword ptr [rbx+138h]  // [hHandle + 138h] == 00000248890002e8
00007FFDF6F01A5A 66 0F 1F 44 00 00    nop         word ptr [rax+rax]  
00007FFDF6F01A60 8B 41 08             mov         eax,dword ptr [rcx+8]  // [00000248890002e8 + 8] == 0000000000000080
00007FFDF6F01A63 48 3B D0             cmp         rdx,rax  // rdx == 2, rax == 0x80 (PL = 1, ZR = 0, CY = 1)
00007FFDF6F01A66 0F 83 AA 03 00 00    jae         RtlpFreeHeap+4B6h (07FFDF6F01E16h)  
00007FFDF6F01A6C 4C 8D 46 0A          lea         r8,[rsi+0Ah]  // rsi == pVoid + 16바이트의 주소
                                                                // r8 == pVoid + 0xa바이트의 주소, 즉 0x03010002 중에서 두 번째 0x01 위치
00007FFDF6F01A70 41 0F B6 00          movzx       eax,byte ptr [r8] // eax == 0x01 
00007FFDF6F01A74 A8 08                test        al,8  // 0x01 and 0x08 == 0 (PL = 0, ZR = 1, CY = 0)
00007FFDF6F01A76 0F 85 BF 06 00 00    jne         RtlpFreeHeap+7DBh (07FFDF6F0213Bh)  
00007FFDF6F01A7C 80 7E 0F 04          cmp         byte ptr [rsi+0Fh],4  // [rsi + 0fh] == 0c00017303010002 중에서 첫 번째 0x0c
                                                        // cmp 0xc, 4 (ZR = 0, PE = 0)
00007FFDF6F01A80 0F 84 6A 07 00 00    je          RtlpFreeHeap+890h (07FFDF6F021F0h)  
00007FFDF6F01A86 48 8D 7E 08          lea         rdi,[rsi+8]  // rdi == rsi + 8 == 0x0000024889000748
                                                                // [rsi + 8] == 0c00017303010002
00007FFDF6F01A8A 0F B7 07             movzx       eax,word ptr [rdi]  // eax == (short)rdi == 0x0002
00007FFDF6F01A8D 66 3B 83 B0 01 00 00 cmp         ax,word ptr [rbx+1B0h]  // [hHandle + 1b0h] == 0000000000000000
                                                                          // ZR = 0, CY = 0
00007FFDF6F01A94 73 44                jae         RtlpFreeHeap+17Ah (07FFDF6F01ADAh)  
...[생략]...


00007FFDF6F01ADA 45 84 E4             test        r12b,r12b  // r12 == 1 (ZR = 0)
00007FFDF6F01ADD 0F 84 09 08 00 00    je          RtlpFreeHeap+98Ch (07FFDF6F022ECh)  
00007FFDF6F01AE3 44 0F B7 07          movzx       r8d,word ptr [rdi]  // rdi == 0x0000024889000748
                                                                      // [rdi] == 0c00017303010002, r8d == 0x0002
00007FFDF6F01AE7 4C 89 44 24 50       mov         qword ptr [rsp+50h],r8  // [rsp + 50h] == 2
00007FFDF6F01AEC F6 43 70 80          test        byte ptr [rbx+70h],80h  // [hHandle + 70h] and 0x80h
                                                                          // 0000000000001000 and 0x80h
                                                                          // 00 and 0x80h (ZR = 1)
00007FFDF6F01AF0 0F 85 C6 01 00 00    jne         RtlpFreeHeap+35Ch (07FFDF6F01CBCh)  
00007FFDF6F01AF6 45 32 ED             xor         r13b,r13b  // r13 == 0
00007FFDF6F01AF9 44 88 6C 24 48       mov         byte ptr [rsp+48h],r13b  // [rsp + 48h] == 0
                                                // 0x000000F5EE4FF528  00 cc cc cc cc cc cc cc == [rsp + 48h]
                                                // 0x000000F5EE4FF530  02 00 00 00 00 00 00 00 == [rsp + 50h]
00007FFDF6F01AFE 48 89 B4 24 A0 00 00 00 mov         qword ptr [rsp+0A0h],rsi  // rsi == pVoid + 16 위치
00007FFDF6F01B06 0F B7 8B 8C 00 00 00 movzx       ecx,word ptr [rbx+8Ch]   // [hHandle + 0x8c] == 0000000000005207
                                                                        // ecx == (short)[rbx+8ch] == 0x5207
00007FFDF6F01B0D 0F B7 46 0C          movzx       eax,word ptr [rsi+0Ch]  // [rsi + 0ch] == 73 01 00 0c ff ff ff ff 
                                                                        // eax == 0x0173
00007FFDF6F01B11 48 33 C8             xor         rcx,rax  // rcx == 0x5207 xor 0x0173 == 0x5374
00007FFDF6F01B14 48 C1 E1 04          shl         rcx,4  // 0x5374 << 4 == 53740
00007FFDF6F01B18 48 8B FE             mov         rdi,rsi  // rdi == rsi(pVoid - 16바이트 주소)
00007FFDF6F01B1B 48 2B F9             sub         rdi,rcx  // rdi - 0x53740 == 0x0000024888fad000
00007FFDF6F01B1E 48 3B FE             cmp         rdi,rsi  // cmp 0x0000024888fad000, 0x0000024889000740
                                                           // PL = 1, CY = 1, ZR = 0
00007FFDF6F01B21 74 19                je          RtlpFreeHeap+1DCh (07FFDF6F01B3Ch)  
00007FFDF6F01B23 8B 4B 7C             mov         ecx,dword ptr [rbx+7Ch]  // ecx == [hHandle + 0x7c] == 00100000
00007FFDF6F01B26 8B C1                mov         eax,ecx  // eax == 00100000
00007FFDF6F01B28 C1 E8 14             shr         eax,14h  // eax == 1
00007FFDF6F01B2B 22 83 8A 00 00 00    and         al,byte ptr [rbx+8Ah]  // 1 and ([hHandle + 8ah] == 0xde)
                                                                         // al == a and 0xde == 0
00007FFDF6F01B31 32 47 0A             xor         al,byte ptr [rdi+0Ah]  // [invalid offset] == 0x0000024888fad000 + 0x0a
                                                                         // crash!!!!!!!!!!!!!!!!!!!!!!!!!!
...[생략]...

예외 메시지도 비슷합니다.

Exception thrown at 0x00007FFDF6F01B31 (ntdll.dll) in ConsoleApplication1.exe: 0xC0000005: Access violation reading location 0x0000024888FAD00A.


또한 운이 좋아 _RtlpReportHeapFailure가 발생한다면 문맥 정보를 얻을 수 있는 것도 같습니다.

Unhandled exception at 0x00007FFDF6FB9269 (ntdll.dll) in ConsoleApplication1.exe: 0xC0000374: A heap has been corrupted (parameters: 0x00007FFDF70227F0).


0x00007FFDF70227F0  000006d000000002 
0x00007FFDF70227F8  0000000000000004 
0x00007FFDF7022800  0000000000b30000 
0x00007FFDF7022808  0000000000b304b0 
0x00007FFDF7022810  0000000000000000 
0x00007FFDF7022818  0000000000000000 
0x00007FFDF7022820  0000000000000000 
0x00007FFDF7022828  0000000000b30000 // hHandle
0x00007FFDF7022830  0000000000b30740 // (pVoid - 0x10) 위치, 따라서 HeapFree에 전달한 주소는 0000000000b30750 
0x00007FFDF7022838  0000000000000000 
0x00007FFDF7022840  0000000000000000 




지난 x86 글과 함께 이 정도 보조 지식이면 이제 Heap Corruption 문제를 분석할 준비가 대충 되었습니다. ^^

참고로, 예전에 윈도우 힙 관련한 글이 있어 연관 글로 남깁니다.

Win32 Debug CRT Heap Internals의 0xBAADF00D 표시 재현
; https://www.sysnet.pe.kr/2/0/11255




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

[연관 글]





[최초 등록일: ]
[최종 수정일: 11/21/2019 ]

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

비밀번호

댓글 쓴 사람
 




1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12129정성태1/26/2020525.NET Framework: 882. C# - 키움 Open API+ 사용 시 Registry 등록 없이 KHOpenAPI.ocx 사용하는 방법
12128정성태1/26/2020195오류 유형: 591. The code execution cannot proceed because mfc100.dll was not found. Reinstalling the program may fix this problem.
12127정성태1/28/2020263.NET Framework: 881. C# DLL에서 제공하는 Win32 export 함수의 내부 동작 방식(VT Fix up Table)파일 다운로드1
12126정성태1/25/2020229.NET Framework: 880. C# - PE 파일로부터 IMAGE_COR20_HEADER 및 VTableFixups 테이블 분석파일 다운로드1
12125정성태1/24/2020136VS.NET IDE: 141. IDE0019 - Use pattern matching
12124정성태1/24/2020407VS.NET IDE: 140. IDE1006 - Naming rule violation: These words must begin with upper case characters: ...
12123정성태1/23/2020210웹: 39. Google Analytics - gtag 함수를 이용해 페이지 URL 수정 및 별도의 이벤트 생성 방법
12122정성태1/22/2020224.NET Framework: 879. C/C++의 UNREFERENCED_PARAMETER 매크로를 C#에서 우회하는 방법(IDE0060 - Remove unused parameter '...')파일 다운로드1
12121정성태1/24/2020168VS.NET IDE: 139. Visual Studio - Error List: "Could not find schema information for the ..."파일 다운로드1
12120정성태1/20/2020261.NET Framework: 878. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 네 번째 이야기(IL 코드로 직접 구현)파일 다운로드1
12119정성태1/17/2020287디버깅 기술: 160. Windbg 확장 DLL 만들기 (3) - C#으로 만드는 방법
12118정성태1/17/2020275개발 환경 구성: 466. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 세 번째 이야기
12117정성태1/15/2020246디버깅 기술: 159. C# - 디버깅 중인 프로세스를 강제로 다른 디버거에서 연결하는 방법파일 다운로드1
12116정성태1/15/2020212디버깅 기술: 158. Visual Studio로 디버깅 시 sos.dll 확장 명령어를 (비롯한 windbg의 다양한 기능을) 수행하는 방법
12115정성태1/14/2020166디버깅 기술: 157. C# - PEB.ProcessHeap을 이용해 디버깅 중인지 확인하는 방법파일 다운로드1
12114정성태1/13/2020367디버깅 기술: 156. C# - PDB 파일로부터 심벌(Symbol) 및 타입(Type) 정보 열거 [1]파일 다운로드3
12113정성태1/12/2020334오류 유형: 590. Visual C++ 빌드 오류 - fatal error LNK1104: cannot open file 'atls.lib'
12112정성태1/12/2020144오류 유형: 589. PowerShell - 원격 Invoke-Command 실행 시 "WinRM cannot complete the operation" 오류 발생
12111정성태3/23/2020526디버깅 기술: 155. C# - KernelMemoryIO 드라이버를 이용해 실행 프로그램을 숨기는 방법(DKOM: Direct Kernel Object Modification) [1]
12110정성태1/12/2020262디버깅 기술: 154. Patch Guard로 인해 블루 스크린(BSOD)가 발생하는 사례파일 다운로드1
12109정성태1/10/2020187오류 유형: 588. Driver 프로젝트 빌드 오류 - Inf2Cat error -2: "Inf2Cat, signability test failed."
12108정성태1/10/2020144오류 유형: 587. Kernel Driver 시작 시 127(The specified procedure could not be found.) 오류 메시지 발생
12107정성태1/10/2020261.NET Framework: 877. C# - 프로세스의 모든 핸들을 열람 - 두 번째 이야기
12106정성태1/8/2020295VC++: 136. C++ - OSR Driver Loader와 같은 Legacy 커널 드라이버 설치 프로그램 제작 [1]
12105정성태1/8/2020213디버깅 기술: 153. C# - PEB를 조작해 로드된 DLL을 숨기는 방법
12104정성태1/9/2020424DDK: 9. 커널 메모리를 읽고 쓰는 NT Legacy driver와 C# 클라이언트 프로그램 [2]
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...