[CDA]크래쉬 덤프 분석 케이스 스터디 (1)

"이 문서는 https://www.dumpanalysis.org/blog/ blog 의 번역이며 원래의 자료가 통보 없이 변경될 수 있습니다. 이 자료는 법률적 보증이 없으며 의견을 주시기 위해 원래의 blog 를 방문하실 수 있습니다. ( https://www.dumpanalysis.org/blog/index.php/2007/02/21/crash-dump-analysis-case-study-1/ )

 

크래쉬 덤프 분석 케이스 스터디 (1)

덤프를 WinDbg로 열어 보았을때 아래와 같이 빨간색으로 되어 있는 코드를 확인해 보도록 하겠습니다.

Consider the following legacy C++/Win32 code fragment highlighted in WinDbg after opening a crash dump:

1: HANDLE hFile = CreateFile(str.GetBuffer(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 2: if (hFile != INVALID_HANDLE_VALUE) 3: { 4:    DWORD dwSize = GetFileSize(hFile, NULL); 5:    DWORD dwRead = 0; 6:    CHAR *bufferA = new CHAR[dwSize+2]; 7:    memset(bufferA, 0, dwSize+2); 8:    if (ReadFile(hFile, bufferA, dwSize, &dwRead, NULL)) 9:    { 10:      DWORD i = 0, j = 0; 11:      for (; i < dwSize+2-7; ++i) 12:      { 13:         if (bufferA[i] == 0xD && bufferA[i+1] != 0xA)

 

코드를 먼저 살펴 보면 정상적으로 보입니다. 파일을 열고 사이즈를 얻고 파일을 읽기 위한 버퍼를 할당하고… 모든 인덱스들은 배열 경계 안에 있는것으로 보입니다. 디스어셈블리 코드를 살펴 보도록 하겠습니다.

 

0:000> uf component!CMyDlg::OnTimer … … … 004021bc push    0 004021be push    esi 004021bf call    dword ptr [component!_imp__GetFileSize (0042e26c)] 004021c5 mov     edi,eax ; dwSize 004021c7 lea     ebx,[edi+2] ; dwSize+2 004021ca push    ebx 004021cb mov     dword ptr [esp+34h],0 004021d3 call    component!operator new[] (00408e35) 004021d8 push    ebx 004021d9 mov     ebp,eax ; bufferA 004021db push    0 004021dd push    ebp 004021de call    component!memset (00418500) 004021e3 add     esp,10h 004021e6 push    0 004021e8 lea     edx,[esp+34h] 004021ec push    edx 004021ed push    edi 004021ee push    ebp 004021ef push    esi 004021f0 call    dword ptr [component!_imp__ReadFile (0042e264)] 004021f6 test    eax,eax 004021f8 jne     component!CMyDlg::OnTimer+0×3b1 (00402331) … … … 00402331 xor     esi,esi ; i 00402333 add     edi,0FFFFFFFBh ; +2-7 (edi contains dwSize) 00402336 cmp     edi,esi ; loop condition 00402338 mov     dword ptr [esp+24h],esi 0040233c jbe     component!CMyDlg::OnTimer+0×43e (004023be) 00402342 mov al,byte ptr [esi+ebp] ; bufferA[i]

0:000> r eax=00002b00 ebx=00000002 ecx=00431000 edx=00000000 esi=00002b28 edi=fffffffb eip=00402342 esp=0012efd4 ebp=0095b4d8 iopl=0 nv up ei pl nz ac pe cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217 component!CMyDlg::OnTimer+0×3c2: 00402342 8a042e mov al,byte ptr [esi+ebp] ds:0023:0095e000=??

 

만약 EBX (dwSize+2) 와 EDI 레지스터( 배열의 상위 경계, dwSize+2-7)를 확인하였다면 dwSize 가 0이라는 것을 쉽게 확인할 수 있었을 것 입니다. 버퍼 오버런이 발생한 것으로 배열의 경계가 0+2-7 = FFFFFFFB 로 계산되었기 때문입니다.(루프의 인덱스는 unsinged integer, DWORD 입니다.). 인덱스가 signed integer 값(int) 라면 루프의 비교 조건이 0<0+2-7로 아무런 문제가 발생하지 않습니다.

아래와 같은 수정이 필요 합니다.

1: HANDLE hFile = CreateFile(str.GetBuffer(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 2: if (hFile != INVALID_HANDLE_VALUE) 3: { 4:    DWORD dwSize = GetFileSize(hFile, NULL); 5:    DWORD dwRead = 0; 6:    CHAR *bufferA = new CHAR[dwSize+2]; 7:    memset(bufferA, 0, dwSize+2); 8:    if (ReadFile(hFile, bufferA, dwSize, &dwRead, NULL)) 9:    { 10:      DWORD i = 0, j = 0; 10: int i = 0, j = 0; 11:      for (; i < dwSize+2-7; ++i) 11: for (; i < (int) dwSize+2-7; ++i) 12:      {

GetFileSize 는 INVALID_FILE_SIZE(0xFFFFFFFF)을 리턴할 수 있고 new 연산자 역시 실패할 수 있어 아래와 같이 수정하는 것이 좋습니다.

 

1: HANDLE hFile = CreateFile(str.GetBuffer(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 2: if (hFile != INVALID_HANDLE_VALUE) 3: { 4:    DWORD dwSize = GetFileSize(hFile, NULL); 4a: if (dwSize != INVALID_FILE_SIZE) 4b: { 5:       DWORD dwRead = 0; 6:       CHAR *bufferA = new CHAR[dwSize+2]; 6a: if (bufferA) 6b: { 7:          memset(bufferA, 0, dwSize+2); 8:          if (ReadFile(hFile, bufferA, dwSize, &dwRead, NULL)) 9:          { 10:            int i = 0, j = 0; 11:            for (; i < (int)dwSize+2-7; ++i) 12:            {