WinDbg 디버깅2007. 9. 16. 23:57
반응형

오늘은 메모리가 깨졌을 경우에 발생하는 BugCheck 0x19: BAD_POOL_HEADER 에 대해서 분석해 보겠습니다.
이것은 메모리를 할당 또는 해제할 때 커널에서 Pool Header 를 체크하여 발생시키는 버그체크입니다.
Verifier 의 Special Pool 을 사용하지 않아도 커널의 자체적인 체크에 의하여 발생합니다.

먼저 !analyze 로 기본적인 사항을 검토해 봅니다.

kd> !analyze -v
***************************************************************************
*                                                                         *
*                        Bugcheck Analysis                                *
*                                                                         *
***************************************************************************

BAD_POOL_HEADER (19)
The pool is already corrupt at the time of the current request.
This may or may not be due to the caller.
The internal pool links must be walked to figure out a possible cause of
the problem, and then special pool applied to the suspect tags or the driver
verifier to a suspect driver.
Arguments:
Arg1: 00000020, a pool block header size is corrupt.
Arg2: 827b49f8, The pool entry we were looking for within the page.
Arg3: 827b4a08, The next pool entry.
Arg4: 0a020001, (reserved)

Debugging Details:
------------------

BUGCHECK_STR:  0x19_20
POOL_ADDRESS:  827b49f8 Nonpaged pool
DEFAULT_BUCKET_ID:  DRIVER_FAULT
PROCESS_NAME:  System
LAST_CONTROL_TRANSFER:  from 8054de41 to 805359ae

STACK_TEXT: 
f7975d00 8054de41 00000019 00000020 827b49f8 nt!KeBugCheckEx+0x1b
f7975d50 8054d7b9 827b4a00 00000000 f7975dac nt!ExFreePoolWithTag+0x2be
f7975d60 f7808d07 827b4a00 82c47718 80563b7c nt!ExFreePool+0xf
WARNING: Stack unwind information not available. Following frames may be wrong.
f7975dac 8057ffed 82c47718 00000000 00000000 MyDrv+0x2d07
f7975ddc 804fc477 804e6729 00000001 00000000 nt!PspSystemThreadStartup+0x34
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

STACK_COMMAND:  kb

FOLLOWUP_IP:
MyDrv+2d07
f7808d07 56              push    esi

SYMBOL_STACK_INDEX:  3
FOLLOWUP_NAME:  MachineOwner
MODULE_NAME: MyDrv
IMAGE_NAME:  MyDrv.SYS
DEBUG_FLR_IMAGE_TIMESTAMP:  3cf1f0eb
SYMBOL_NAME:  MyDrv+2d07
FAILURE_BUCKET_ID:  0x19_20_MyDrv+2d07
BUCKET_ID:  0x19_20_MyDrv+2d07

Followup: MachineOwner
---------

Pool 이 깨진 것 같다는 메시지로 시작합니다.
이 호출을 한 드라이버가 범인일 수도 아닐 수도 있다고 하구요.
원인을 찾기 위해서 internal pool link 를 따라가 보라구 하네요. 이건 밑에서 자세히 설명합니다.
그리고 verifier와 special pool 을 의심가는 드라이버에 적용해서 문제를 찾으라고 하네요.
정말 친절하지 않습니까? 사실 오늘의 분석도 하라는 대로 그대로 진행하는 내용입니다. ^^

Arg1: 00000020 는 pool block header 가 깨졌다는 것을 알려주는 코드입니다.
BugCheck 0x19 에 대한 WinDbg Help 를 보시면 몇가지 더 있는 것을 볼 수 있습니다.

Arg2: 827b49f8 는 현재 사용되던 문제의 메모리 주소네요.
콜스택상의 메모리 주소와 비교해 볼 필요가 있는데요...

f7975d60 f7808d07 827b4a00 82c47718 80563b7c nt!ExFreePool+0xf

ExFreePool 의 첫번째 파라미터로 들어간 저 827b4a00 과 어떤 관계가 있는 걸까요?
잘 보시면 827b4a00 - 8 한 결과가 바로 Arg2 에 나타난 827b49f8 입니다.

827b4a00 는 사용자가 할당받아 사용하던 메모리 시작주소이구요. 사실 이 메모리는 앞에 8 바이트의 헤더가 붙어있습니다. 따라서 실제 할당된 메모리영역은 827b49f8 부터 시작하게 되구요
커널에서 pool 관리를 할 때는 pool header 부터 관리하기 때문에 문제의 시점에 다루고 있던 메모리 주소를 827b49f8 라고 보여준겁니다.

827b49f8 이 어떤 메모리인지 !pool 을 통해서 알아봅니다.

kd> !pool 827b49f8
Pool page 827b49f8 region is Nonpaged pool
 827b4000 size:   60 previous size:    0  (Allocated)  Ddk
 827b4060 size:    8 previous size:   60  (Free)       ....
 ...
 ...
 827b47e0 size:   18 previous size:   20  (Free)       Ntfi
 827b47f8 size:   68 previous size:   18  (Allocated)  MmCa
 827b4860 size:   98 previous size:   68  (Allocated)  File (Protected)
 827b48f8 size:   20 previous size:   98  (Allocated)  VadS
 827b4918 size:   38 previous size:   20  (Allocated)  MmSd
 827b4950 size:   38 previous size:   38  (Allocated)  MmSd
 827b4988 size:   38 previous size:   38  (Allocated)  MmSd
 827b49c0 size:   10 previous size:   38  (Free)       CcSc
 827b49d0 size:   20 previous size:   10  (Allocated)  ReTa
 827b49f0 size:    8 previous size:   20  (Free)       Ntfi
*827b49f8 size:   10 previous size:    8  (Allocated) *Ddk
  Pooltag Ddk  : Default for driver allocated memory (user's of ntddk.h)
827b4a08 is not a valid small pool allocation, checking large pool...
unable to get pool big page table - either wrong symbols or pool tagging is disabled
827b4a08 is freed (or corrupt) pool
Bad previous allocation size @827b4a08, last size was 2

***
*** An error (or corruption) in the pool was detected;
*** Attempting to diagnose the problem.
***
*** Use !poolval 827b4000 for more details.
***

Pool page [ 827b4000 ] is __inVALID.

Analyzing linked list...
[ 827b4a08 ]: invalid previous size [ 0x0 ] should be [ 0x2 ]

Scanning for single bit errors...
[ 827b4a08 ]: previous size [ 0x0 ] should be [ 0x2 ]

!pool 명령이 827b49f8 까지는 잘 보여주다가 827b4a08 을 제대로 보여주지 못하네요.
이런 오류 메시지가 나오면 십중팔구 pool header 가 깨진 것입니다.
!pool 명령은 커널이 그렇게 하듯이 pool header의 size 와 previous size 를 보면서 위와 같은 결과를 출력합니다. internal pool link 라는 것은 이것을 의미합니다.
!pool 명령이 처음부터 잘 보여주다가 827b4a08 을 제대로 보여주지 못한다는 것은 827b4a08 메모리 pool header 헤더의 내용이 온전하지 못하다는 의미입니다.

827b49f8 메모리의 내용은 어떤 것인지 한번 볼까요?

kd> db 827b49f8
827b49f8  01 00 02 0a 44 64 6b 20-53 2d 31 2d 35 2d 31 38  ....Ddk S-1-5-18
827b4a08  00 00 13 0a 46 69 6c e5-00 00 00 00 00 00 00 00  ....Fil.........
827b4a18  01 00 00 00 00 00 00 00-40 b0 fe 82 00 08 00 42  ........@......B
827b4a28  01 00 00 00 00 00 00 00-05 00 70 00 d0 a1 fb 82  ..........p.....

!pool 명령의 결과에서 보면 이 메모리의 size 는 0x10 (16바이트) 이고 풀태그는 Ddk 입니다.
827b49f8 부터 16바이트만 보면 됩니다. 앞에 8바이트는 헤더이고 뒤에 8바이트가 실제 사용된 메모리입니다.
실제 메모리 내용에는 S-1-5-18 라는 문자열이 들어 있네요. 운이 좋으면 이 문자열이 중요한 단서가 됩니다. 이번 경우는 뭔지 잘 모르겠네요.

헤더에서 뒤 4바이트는 풀태그라는 것을 알 수 있습니다. Ddk 라고 정확히 적혀있네요.
헤더에서 앞 4바이트의 의미는 어디 나와있는 곳은 없구요 제가 파악한 바로는 다음과 같습니다.

1번째 바이트: previous size ( 1 은 8 바이트를 의미, 2는 16바이트 )
2번째 바이트: 모르겠네요 -_-;;;
3번재 바이트: size ( 1 은 8 바이트를 의미, 2는 16바이트 )
4번재 바이트: 00 = Free, 0a = Allocated (확실치 않음 ^^)

이 사실을 확인해 보기 위해서 조금 앞에 있는 메모리 827b49c0 부터 살펴봅니다.

kd> db 827b49c0
827b49c0  07 00 02 00 43 63 53 63-f0 97 76 82 70 06 78 82  ....CcSc..v.p.x.
827b49d0  02 00 04 0a 52 65 54 61-00 00 00 00 03 00 00 00  ....ReTa........
827b49e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
827b49f0  04 00 01 00 4e 74 66 69-01 00 02 0a 44 64 6b 20  ....Ntfi....Ddk
827b4a00  53 2d 31 2d 35 2d 31 38-00 00 13 0a 46 69 6c e5  S-1-5-18....Fil.
827b4a10  00 00 00 00 00 00 00 00-01 00 00 00 00 00 00 00  ................

827b49d0 부터 보시면 첫번째 바이트 02 는 앞의 메모리 827b49c0 의 size 와 일치합니다.
827b49d0 의 세번째 바이트 04 는 8 만 곱하면 4*8 = 32 로 !pool 명령에서 보여주는 size: 20 hex = 32 decimal 과 일치합니다.

827b49d0 에서 size 만큼 더하면 다음 메모리인 827b49f0 가 나오고 이것의 첫번재 바이트 04 는 previous인 827b49d0 의 size: 04 와 일치합니다. 이런 식으로 계속 연결되어 있는 것이죠.

하지만 해제하려는 메모리 827b49f8 까지는 잘 연될되는데 827b4a08 를 보니 문제가 있습니다.
827b4a08 의 첫번째 바이트는 previous size 인 02 가 있어야 하는데 엉뚱하게 00 이 들어 있는 거죠.

이것을 감지한 커널은 ExFreePool 을 더 진행하지 않고 BugCheck 0x19 를 띄운 것입니다.

메모리를 깨먹는 녀석이 있다는 사실을 알았으니 어떤 녀석인지 찾아야 겠네요.
Verifier로 Special Pool 을 켜고 의심가는 드라이버를 지정합니다. 지난번에도 언급한 적이 있는지 모르겠지만 개발중에는 항상 자신의 드라이버를 먼저 의심해야 합니다. 따라서 이번에는 MyDrv.sys 를 지정했습니다.

사실 메모리를 깨먹는 문제는 어떤 녀석이 범인인지 예측하기가 거의 불가능합니다. MyDrv.sys 를 지정한 것이 효과가 없다면 다른 드라이버들을 적용해 봐야 하고 최악의 경우 모든 드라이버를 지정하는 상황까지 갈 수 있습니다. 이렇게 해서라도 문제의 드라이버를 찾을 수 있다면 그나마 다행이죠. ^^

Verifier 로 얻고자 하는 결과는 BugCheck 0xD6: DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION 입니다. 지난번에 한번 분석해 본적이 있는 BugCheck 입니다. 그 내용을 잊지 않고 있다면 범인은 거의 다 잡은 셈입니다.

원하는 것은 BugCheck 0xD6 이지만 이 버그체크를 얻기가 사실 쉽지 않았습니다.
Verifier 를 걸었지만 BugCheck 0x19 가 다시 발생하기도 하고 다른 곳에서 죽기도 하고 해서 몇번의 시도 끝에 겨우 BugCheck 0xD6 을 얻을 수 있었습니다.

kd> !analyze -v
****************************************************************************
*                                                                          *
*                        Bugcheck Analysis                                 *
*                                                                          *
****************************************************************************

DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION (d6)
N bytes of memory was allocated and more than N bytes are being referenced.
This cannot be protected by try-except.
When possible, the guilty driver's name (Unicode string) is printed on
the bugcheck screen and saved in KiBugCheckDriver.
Arguments:
Arg1: 83801000, memory referenced
Arg2: 00000001, value 0 = read operation, 1 = write operation
Arg3: f7828176, if non-zero, the address which referenced memory.
Arg4: 00000000, (reserved)

Debugging Details:
------------------

WRITE_ADDRESS:  83801000 Special pool

FAULTING_IP:
MyDrv+1176
f7828176 f3a4            rep movs byte ptr es:[edi],byte ptr [esi]

MM_INTERNAL_CODE:  0

IMAGE_NAME:  MyDrv.SYS
DEBUG_FLR_IMAGE_TIMESTAMP:  3cf1f0eb
MODULE_NAME: MyDrv
FAULTING_MODULE: f7827000 MyDrv
DEFAULT_BUCKET_ID:  DRIVER_FAULT

BUGCHECK_STR:  0xD6

PROCESS_NAME:  explorer.exe

TRAP_FRAME:  b985c748 -- (.trap ffffffffb985c748)
ErrCode = 00000002
eax=00000009 ebx=8066e90d ecx=00000001 edx=83800ff8 esi=b985c7dc edi=83801000
eip=f7828176 esp=b985c7bc ebp=836a6fe8 iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010202
MyDrv+0x1176:
f7828176 f3a4            rep movs byte ptr es:[edi],byte ptr [esi] es:0023:83801000=?? ds:0023:b985c7dc=00
Resetting default scope

LAST_CONTROL_TRANSFER:  from 805266fb to 805359ae

STACK_TEXT: 
b985c6e4 805266fb 00000050 83801000 00000001 nt!KeBugCheckEx+0x1b
b985c730 804e3ff1 00000001 83801000 00000000 nt!MmAccessFault+0x6f5
b985c730 f7828176 00000001 83801000 00000000 nt!KiTrap0E+0xcc
WARNING: Stack unwind information not available. Following frames may be wrong.
b985c804 80674576 828aeda8 0000001f 80673bf9 MyDrv+0x1176
b985ca28 804e5d77 82c50b60 83864e00 806f02e8 MyDrv+0x3779
b985ca38 8066c2c5 83864e10 83864e00 82d1fd98 nt!IopfCallDriver+0x31
b985ca5c 80572f9c 82e358e8 82c67ce4 b985cc04 nt!IovCallDriver+0xa0
b985cb3c 8056586c 82e35900 00000000 82c67c40 nt!IopParseDevice+0xa58
b985cbc4 80569c63 00000000 b985cc04 00000042 nt!ObpLookupObjectName+0x56a
b985cc18 80573477 00000000 00000000 c50b6001 nt!ObOpenObjectByName+0xeb
b985cc94 80573546 00e5db2c 40100080 00e5dacc nt!IopCreateFile+0x407
b985ccf0 8057367c 00e5db2c 40100080 00e5dacc nt!IoCreateFile+0x8e
b985cd30 804e106b 00e5db2c 40100080 00e5dacc nt!NtCreateFile+0x30
b985cd30 7c93eb94 00e5db2c 40100080 00e5dacc nt!KiFastCallEntry+0xf8
00e5db24 00000000 00000000 00000000 00000000 0x7c93eb94

STACK_COMMAND:  kb

FOLLOWUP_IP:
MyDrv+1176
f7828176 f3a4            rep movs byte ptr es:[edi],byte ptr [esi]

SYMBOL_STACK_INDEX:  3
FOLLOWUP_NAME:  MachineOwner
SYMBOL_NAME:  MyDrv+1176
FAILURE_BUCKET_ID:  0xD6_W_MyDrv+1176
BUCKET_ID:  0xD6_W_MyDrv+1176

Followup: MachineOwner
---------

할당된 영역을 넘어선 메모리 접근이 있었다는 내용이 나옵니다.
불행인지 다행인지 MyDrv 에서 한 짓이었군요. ^^

Arg2: 00000001, value 0 = read operation, 1 = write operation

이 메시지를 보니 쓰기 시도가 있었다는 것을 알 수 있습니다.

.trap 에 대한 메시지가 있네요. 그대로 해 줍니다.

kd> .trap ffffffffb985c748
ErrCode = 00000002
eax=00000009 ebx=8066e90d ecx=00000001 edx=83800ff8 esi=b985c7dc edi=83801000
eip=f7828176 esp=b985c7bc ebp=836a6fe8 iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010202
MyDrv+0x1176:
f7828176 f3a4     rep movs byte ptr es:[edi],byte ptr [esi] es:0023:83801000=?? ds:0023:b985c7dc=00

마지막 부분에서 죽었을 당시의 코드를 볼 수 있습니다.
esi 주소를 edi 로 한 바이트씩 복사하다가 edi=83801000 가 되었을 때 죽은 거네요.

kd> db 83801000
83801000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
83801010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
83801020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
83801030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

83801000 는 접근할 수 없는 메모리입니다. 왜냐하면 Guard Page 이기 때문입니다.
BugCheck 0xD6 에서 설명했었기 때문에 자세한 설명은 생략하구요.

83801000 주소 앞쪽에는 복사를 잘 했을 것이므로 약간 앞쪽 메모리를 살펴보겠습니다.

kd> db 83801000 - 10
83800ff0  cf cf cf cf cf cf cf cf-53 2d 31 2d 35 2d 31 38  ........S-1-5-18
83801000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
83801010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
83801020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

눈에 익은 문자열이 보입니다. 상황이 좀 파악되시나요?

메모리 할당은 8 바이트를 하고 나서 9 번째 바이트에 쓰기시도를 하면서 BugCheck 0xD6 가 발생한 겁니다.
이것은 이 메모리가 special pool 에 할당되었기 때문에 감지가 가능한 것입니다.

Verifier 를 설정하지 않은 상태에서는 BucCheck 0x19가 발생하는 이유를 이제야 이해할 수 있습니다.
일반 pool 이었다면 S-1-5-18 바로 다음은 다음 엔트리의 헤더가 됩니다.
헤더의 첫번재 바이트는 previous size 인데 이것을 깨뜨리게 되는 것이죠.
안타깝게도 이 때에는 문제가 노출되지 않습니다. 나중에 다른 메모리 할당/해제 요청이 있을 때 커널이 pool link 를 체크하다가 그제서야 문제를 알게 되지요. 이 시점에서는 원인을 찾을 수 없습니다. 이것이 바로 메모리 깨뜨리는 문제가 풀기 어려운 문제에 속하는 이유입니다.

따라서 이와 같은 상황에서는 위와 같이 Verifier 같은 툴을 잘 활용하여 원인을 찾아야 합니다.
이상 BugCheck 0x19 로 문제를 진단하고 BugCheck 0xD6 으로 범인을 잡는 협공이었습니다. ^^

http://www.driveronline.org/bbs/view.asp?tb=tipbbs&GotoPage=2&s_bulu=&s_key=&no=79

반응형
Posted by GreeMate