다음을 통해 공유


예제 12: 페이지 힙 확인을 사용하여 버그 찾기

다음 일련의 명령은 GFlags 및 NTSD 디버거의 페이지 힙 확인 기능을 사용하여 힙 메모리 사용 시 오류를 검색하는 방법을 보여 줍니다. 이 예제에서 프로그래머가 가상의 애플리케이션인 pheap-buggy.exe 힙 오류가 있다고 의심하고 일련의 테스트를 진행하여 오류를 식별합니다.

NTSD에 대한 자세한 내용은 CDB 및 NTSD를 사용하여 디버깅을 참조하세요.

1단계: 표준 페이지 힙 확인 사용

다음 명령을 사용하면 pheap-buggy.exe 대한 표준 페이지 힙 확인을 사용할 수 있습니다.

gflags /p /enable pheap-buggy.exe

2단계: 페이지 힙이 사용하도록 설정되어 있는지 확인

다음 명령은 페이지 힙 확인을 사용하는 이미지 파일을 나열합니다.

gflags /p

이에 대한 응답으로 GFlags는 다음 프로그램 목록을 표시합니다. 이 디스플레이에서 추적은 표준 페이지 힙 확인을 나타내고 전체 추적은 전체 페이지 힙 확인을 나타냅니다. 이 경우 pheap-buggy.exe 추적과 함께 나열되며, 이는 표준 페이지 힙 확인이 의도한 대로 사용하도록 설정되어 있음을 나타냅니다.

pheap-buggy.exe: page heap enabled with flags (traces )

3단계: 디버거 실행

다음 명령은 NTSD에서 -g(초기 중단점 무시) 및 -x(액세스 위반 예외 시 두 번째 중단 설정) 매개 변수를 사용하여 pheap-buggy.exe CorruptAfterEnd 함수를 실행합니다.

ntsd -g -x pheap-buggy CorruptAfterEnd

애플리케이션이 실패하면 NTSD는 pheap-buggy.exe 오류를 감지했음을 나타내는 다음 디스플레이를 생성합니다.

===========================================================
VERIFIER STOP 00000008: pid 0xAA0: corrupted suffix pattern

        00C81000 : Heap handle 
        00D81EB0 : Heap block 
        00000100 : Block size 
#         00000000 :
===========================================================

Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00d81eb0 ecx=77f7e257 edx=0006fa18 esi=00000008 edi=00c81000
eip=77f7e098 esp=0006fc48 ebp=0006fc5c iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77f7e098 cc               int     3

헤더 정보에는 손상된 블록이 있는 힙의 주소(00C81000 : 힙 핸들), 손상된 블록의 주소(00D81EB0 : 힙 블록) 및 할당 크기(00000100 : 블록 크기)가 포함됩니다.

"손상된 접미사 패턴" 메시지는 애플리케이션이 pheap-buggy.exe 힙 할당이 끝난 후 GFlags가 삽입한 데이터 무결성 패턴을 위반했음을 나타냅니다.

4단계: 호출 스택 표시

다음 단계에서는 NTSD가 보고한 주소를 사용하여 오류를 발생시킨 함수를 찾습니다. 다음 두 명령은 디버거에서 줄 번호 덤프를 켜고 줄 번호가 있는 호출 스택을 표시합니다.

C:\>.lines

Line number information will be loaded 

C:\>kb

ChildEBP RetAddr  Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fc5c 77f9e6dd 00000008 77f9e3e8 00c81000 ntdll!DbgBreakPoint
0006fcd8 77f9f3c8 00c81000 00000004 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x2879
0006fcfc 77f9f5bb 00c81000 01001002 00000010 ntdll!RtlpNtEnumerateSubKey+0x3564
0006fd4c 77fa261e 00c80000 01001002 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x3757
0006fdc0 77fc0dc2 00c80000 01001002 00d81eb0 ntdll!RtlpNtEnumerateSubKey+0x67ba
0006fe78 77fbd87b 00c80000 01001002 00d81eb0 ntdll!RtlSizeHeap+0x16a8
0006ff24 010013a4 00c80000 01001002 00d81eb0 ntdll!RtlFreeHeap+0x69
0006ff3c 01001450 00000000 00000001 0006ffc0 pheap-buggy!TestCorruptAfterEnd+0x2b [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 185]
0006ff4c 0100157f 00000002 00c65a68 00c631d8 pheap-buggy!main+0xa9 [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 69]
0006ffc0 77de43fe 00000000 00000001 7ffdf000 pheap-buggy!mainCRTStartup+0xe3 [crtexe.c @ 349]
0006fff0 00000000 0100149c 00000000 78746341 kernel32!DosPathToSessionPathA+0x204

결과적으로 디버거는 줄 번호가 있는 pheap-buggy.exe 대한 호출 스택을 표시합니다. 호출 스택 표시는 pheap-buggy.exe TestCorruptAfterEnd 함수가 RtlFreeHeap으로 리디렉션된 HeapFree를 호출하여 0x00c80000 할당을 해제하려고 할 때 오류가 발생했음을 보여 줍니다.

이 오류의 가장 큰 원인은 프로그램이 이 함수에 할당된 버퍼의 끝을 지나서 작성했다는 것입니다.

5단계: 전체 페이지 힙 확인 사용

표준 페이지 힙 확인과 달리 전체 페이지 힙 확인은 발생하는 즉시 이 힙 버퍼의 오용을 catch할 수 있습니다. 다음 명령은 pheap-buggy.exe 전체 페이지 힙 확인을 사용하도록 설정합니다.

gflags /p /enable pheap-buggy.exe /full

6단계: 전체 페이지 힙이 사용하도록 설정되어 있는지 확인

다음 명령은 페이지 힙 확인이 사용하도록 설정된 프로그램을 나열합니다.

gflags /p

이에 대한 응답으로 GFlags는 다음 프로그램 목록을 표시합니다. 이 디스플레이에서 추적은 표준 페이지 힙 확인을 나타내고 전체 추적은 전체 페이지 힙 확인을 나타냅니다. 이 경우 pheap-buggy.exe 전체 추적과 함께 나열되어 전체 페이지 힙 확인이 의도한 대로 사용하도록 설정되어 있음을 나타냅니다.

pheap-buggy.exe: page heap enabled with flags (full traces )

7단계: 디버거 다시 실행

다음 명령은 NTSD 디버거에서 -g(초기 중단점 무시) 및 -x(액세스 위반 예외 시 두 번째 기회 중단 설정) 매개 변수를 사용하여 pheap-buggy.exe CorruptAfterEnd 함수를 실행합니다.

ntsd -g -x pheap-buggy CorruptAfterEnd

애플리케이션이 실패하면 NTSD는 pheap-buggy.exe 오류를 감지했음을 나타내는 다음 디스플레이를 생성합니다.

Page heap: process 0x5BC created heap @ 00880000 (00980000, flags 0x3)
ModLoad: 77db0000 77e8c000   kernel32.dll
ModLoad: 78000000 78046000   MSVCRT.dll
Page heap: process 0x5BC created heap @ 00B60000 (00C60000, flags 0x3)
Page heap: process 0x5BC created heap @ 00C80000 (00D80000, flags 0x3)
Access violation - code c0000005 (first chance)
Access violation - code c0000005 (!!! second chance !!!)
eax=00c86f00 ebx=00000000 ecx=77fbd80f edx=00c85000 esi=00c80000 edi=00c16fd0
eip=01001398 esp=0006ff2c ebp=0006ff4c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000206
pheap-buggy!TestCorruptAfterEnd+1f:
01001398 889801010000     mov     [eax+0x101],bl          ds:0023:00c87001=??

전체 페이지 힙 확인을 사용하도록 설정하면 디버거가 액세스 위반 시 중단됩니다. 액세스 위반의 정확한 위치를 찾으려면 줄 번호 덤프를 켜고 호출 스택 추적을 표시합니다.

번호가 매겨진 호출 스택 추적은 다음과 같이 표시됩니다.

ChildEBP RetAddr  Args to Child
0006ff3c 01001450 00000000 00000001 0006ffc0 pheap-buggy!TestCorruptAfterEnd+0x1f [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 184]
0006ff4c 0100157f 00000002 00c16fd0 00b70eb0 pheap-buggy!main+0xa9 [d:\nttest\base\testsrc\kernel\rtl\pageheap\pheap-buggy.cxx @ 69]
0006ffc0 77de43fe 00000000 00000001 7ffdf000 pheap-buggy!mainCRTStartup+0xe3 [crtexe.c @ 349]
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fff0 00000000 0100149c 00000000 78746341 kernel32!DosPathToSessionPathA+0x204

스택 추적은 pheap-buggy.exe 줄 184에서 문제가 발생했음을 보여 줍니다. 전체 페이지 힙 확인을 사용하도록 설정했으므로 호출 스택은 시스템 DLL이 아닌 프로그램 코드에서 시작됩니다. 결과적으로 힙 블록이 해제된 시점이 아니라 위반이 발생한 위치에 위반이 발견되었습니다.

8단계: 코드에서 오류 찾기

빠른 검사를 통해 문제의 원인을 알 수 있습니다. 프로그램은 일반적인 오프-바이-바이(off-by-one) 오류인 256비트(0x100) 버퍼의 257번째 바이트(0x101)에 쓰려고 합니다.

*((PCHAR)Block + 0x100) = 0;