BLOG main image
분류 전체보기 (17)
Life (2)
Dump Analysis (9)
Reversing (1)
Windows (1)
Book (2)
Reference (2)
Visitors up to today!
Today hit, Yesterday hit
daisy rss
tistory 티스토리 가입하기!
'POOL_DESCRIPTOR'에 해당되는 글 2건
2018. 7. 19. 22:41

이번 덤프는 또 다른 BugCheck 0xC5: DRIVER_CORRUPTED_EXPOOL이다.

역시나 풀 메모리가 손상됐을 경우 주로 발생한다. 이번에는 nt 커널이 전역으로 관리하는 풀 해제 리스트가 손상되어 메모리 해제 중 문제가 발생했다.

미리 밝히지만 메모리 손상 방법이 악랄하여 일반적으로는 분석이 거의 불가능한 유형의 덤프다.

그럼에도 기존과는 다른 접근법으로 답을 찾아 가는 방법을 소개하려고 한다.

kd> !analyze -v

*******************************************************************************

*      *

*      Bugcheck Analysis   *

*      *

*******************************************************************************

DRIVER_CORRUPTED_EXPOOL (c5)

An attempt was made to access a pageable (or completely invalid) address at an

interrupt request level (IRQL) that is too high. This is

caused by drivers that have corrupted the system pool. Run the driver

verifier against any new (or suspect) drivers, and if that doesn't turn up

the culprit, then use gflags to enable special pool.

Arguments:

Arg1: 11223344, memory referenced

Arg2: 00000002, IRQL

Arg3: 00000000, value 0 = read operation, 1 = write operation

Arg4: 82b5f795, address which referenced memory

Debugging Details:

------------------

DUMP_CLASS: 1

DUMP_QUALIFIER: 401

BUILD_VERSION_STRING: 7601.17514.x86fre.win7sp1_rtm.101119-1850

SYSTEM_MANUFACTURER: VMware, Inc.

VIRTUAL_MACHINE: VMware

SYSTEM_PRODUCT_NAME: VMware Virtual Platform

SYSTEM_VERSION: None

BIOS_VENDOR: Phoenix Technologies LTD

BIOS_VERSION: 6.00

BIOS_DATE: 07/31/2013

BASEBOARD_MANUFACTURER: Intel Corporation

BASEBOARD_PRODUCT: 440BX Desktop Reference Platform

BASEBOARD_VERSION: None

DUMP_TYPE: 1

BUGCHECK_P1: 11223344

BUGCHECK_P2: 2

BUGCHECK_P3: 0

BUGCHECK_P4: ffffffff82b5f795

BUGCHECK_STR: 0xC5_2

CURRENT_IRQL: 2

FAULTING_IP:

nt!ExDeferredFreePool+135

82b5f795 8b10 mov edx,dword ptr [eax]

CPU_COUNT: 4

CPU_MHZ: e07

CPU_VENDOR: GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 3c

CPU_STEPPING: 3

CPU_MICROCODE: 6,3c,3,0 (F,M,S,R) SIG: 19'00000000 (cache) 19'00000000 (init)

DEFAULT_BUCKET_ID: WIN7_DRIVER_FAULT

PROCESS_NAME: System

ANALYSIS_SESSION_HOST: PAUL-PC

ANALYSIS_SESSION_TIME: 12-01-2017 17:48:31.0130

ANALYSIS_VERSION: 10.0.10575.567 amd64fre

TRAP_FRAME: 82b65ab4 -- (.trap 0xffffffff82b65ab4)

ErrCode = 00000000

eax=11223344 ebx=000001ff ecx=000001ff edx=82b74d60 esi=8649a158 edi=82b74940

eip=82b5f795 esp=82b65b28 ebp=82b65b60 iopl=0 nv up ei pl nz na pe nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010206

nt!ExDeferredFreePool+0x135:

82b5f795 8b10 mov edx,dword ptr [eax]

ds:0023:11223344=????????

Resetting default scope

LAST_CONTROL_TRANSFER: from 82b5f795 to 82a7e5cb

STACK_TEXT:

82b65ab4 82b5f795 badb0d00 82b74d60 82b65b08 nt!KiTrap0E+0x2cf

82b65b60 82b5f35f 82b74940 00000000 82b5eaba nt!ExDeferredFreePool+0x135

82b65bc8 8cb1294d 8458fd10 70627375 82a780e8 nt!ExFreePoolWithTag+0x8a4

82b65c00 8cb13178 8655c518 8544dc58 8643ee30 USBPORT!USBPORT_Core_iCompleteDoneTransfer+0x7c5

82b65c2c 8cb169af 8555a028 8555a0f0 8555aa98 USBPORT!USBPORT_Core_iIrpCsqCompleteDoneTransfer+0x33b

82b65c54 8cb10d18 8555a028 8555aa98 8555a002 USBPORT!USBPORT_Core_UsbIocDpc_Worker+0xbc

82b65c78 82ab51b5 8555aaa4 8555a002 00000000 USBPORT!USBPORT_Xdpc_Worker+0x173

82b65cd4 82ab5018 82b68d20 82b72380 00000000 nt!KiExecuteAllDpcs+0xf9

82b65d20 82ab4e38 00000000 0000000e 00000000 nt!KiRetireDpcList+0xd5

82b65d24 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x38

STACK_COMMAND: kb

THREAD_SHA1_HASH_MOD_FUNC: a81891ed81b7e7e7cf7e48a31a162387a34eb470

THREAD_SHA1_HASH_MOD_FUNC_OFFSET: e3429ed576cacf32e33aea2731862fb99fe1a8e9

THREAD_SHA1_HASH_MOD: d1ea541659404c79a51ef0cc026f2229287008dc

FOLLOWUP_IP:

nt!ExDeferredFreePool+135

82b5f795 8b10 mov edx,dword ptr [eax]

FAULT_INSTR_CODE: 44ff108b

SYMBOL_STACK_INDEX: 1

SYMBOL_NAME: nt!ExDeferredFreePool+135

FOLLOWUP_NAME: Pool_corruption

IMAGE_NAME: Pool_Corruption

DEBUG_FLR_IMAGE_TIMESTAMP: 0

IMAGE_VERSION: 6.1.7601.17514

MODULE_NAME: Pool_Corruption

FAILURE_BUCKET_ID: 0xC5_2_nt!ExDeferredFreePool+135

BUCKET_ID: 0xC5_2_nt!ExDeferredFreePool+135

PRIMARY_PROBLEM_CLASS: 0xC5_2_nt!ExDeferredFreePool+135

TARGET_TIME: 2017-11-30T09:55:42.000Z

OSBUILD: 7601

OSSERVICEPACK: 1000

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK: 272

PRODUCT_TYPE: 1

OSPLATFORM_TYPE: x86

OSNAME: Windows 7

OSEDITION: Windows 7 WinNt (Service Pack 1) TerminalServer SingleUserTS

OS_LOCALE:

USER_LCID: 0

OSBUILD_TIMESTAMP: 2010-11-20 17:42:49

BUILDDATESTAMP_STR: 101119-1850

BUILDLAB_STR: win7sp1_rtm

BUILDOSVER_STR: 6.1.7601.17514.x86fre.win7sp1_rtm.101119-1850

ANALYSIS_SESSION_ELAPSED_TIME: 756

ANALYSIS_SOURCE: KM

FAILURE_ID_HASH_STRING: km:0xc5_2_nt!exdeferredfreepool+135

FAILURE_ID_HASH: {8f7dc44e-d604-b619-9e06-69593c07a8bc}

Followup: Pool_corruption

---------


풀 메모리 해제 중 메모리 손상이 감지되어 문제가 발생했다. BugCode 의 파라미터를 살펴보자.

Arguments:

Arg1: 11223344, memory referenced

Arg2: 00000002, IRQL

Arg3: 00000000, value 0 = read operation, 1 = write operation

Arg4: 82b5f795, address which referenced memory



첫 번째 파라미터를 보면 "11223344" 주소에 접근하다 문제가 발생했다.

"11223344"는 그냥 봐도 이상한 주소 값이기 때문에 "누가 또 메모리를 깼네"라고 생각하면 속 편하다.

메모리 손상 이슈는 많이 분석해 본 나도 결말이 어떻게 될지 예측할 수 없다.

덤프 상에 분석을 위한 최소한의 정보도 남아 있지 않으면 분석이 불가능하기 때문이다.

그러니, 이쯤에서 한숨을 크게 한 번 쉬어주자.

준비가 됐으면 kv 명령어로 파라미터를 포함한 콜 스택부터 살펴보자.

kd> kv

# ChildEBP RetAddr Args to Child

00 82b65ab4 82b5f795 badb0d00 82b74d60 82b65b08 nt!KiTrap0E+0x2cf (FPO: [0,0]

TrapFrame @ 82b65ab4)

01 82b65b60 82b5f35f 82b74940 00000000 82b5eaba nt!ExDeferredFreePool+0x135

02 82b65bc8 8cb1294d 8458fd10 70627375 82a780e8 nt!ExFreePoolWithTag+0x8a4

03 82b65c00 8cb13178 8655c518 8544dc58 8643ee30 USBPORT!USBPORT_Core_iCompleteDoneTransfer+0x7c5 (FPO: [Non-Fpo])

04 82b65c2c 8cb169af 8555a028 8555a0f0 8555aa98 USBPORT!USBPORT_Core_iIrpCsqCompleteDoneTransfer+0x33b (FPO: [Non-Fpo])

05 82b65c54 8cb10d18 8555a028 8555aa98 8555a002 USBPORT!USBPORT_Core_UsbIocDpc_Worker+0xbc (FPO: [Non-Fpo])

06 82b65c78 82ab51b5 8555aaa4 8555a002 00000000 USBPORT!USBPORT_Xdpc_Worker+0x173 (FPO: [Non-Fpo])

07 82b65cd4 82ab5018 82b68d20 82b72380 00000000 nt!KiExecuteAllDpcs+0xf9

08 82b65d20 82ab4e38 00000000 0000000e 00000000 nt!KiRetireDpcList+0xd5

09 82b65d24 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x38 (FPO:

[0,0,0])


콜 스택을 보면 nt 커널의 ExDeferredFreePool 함수를 통해 풀 메모리를 해제하는 도중 해제 리스트 손상이 감지되어 0xC5 오류 코드가 발생했다.

참고로 ExDeferredFreePool 함수는 공개되지 않은 커널 내부 함수로 외부에서 사용 가능한 ExFreePoolWithTag 함수를 통해 호출된다.

ExFreePoolWithTag 함수 원형은 다음과 같다.

VOID ExFreePoolWithTag(

_In_ PVOID P,

_In_ ULONG Tag

);



첫 번째 파라미터가 해제하려는 풀 메모리의 포인터인데, 콜 스택에 보이는 ExFreePoolWithTag의 첫 번째 파라미터인 8458fd10 을 해제하는 과정에서 문제가 발생한 것으로 추측해볼 수 있다.

ExDeferredFreePool 함수는 이름 그대로 바로 해제가 어려울 경우 나중에 해제하기 위해 지연 해제 리스트에 넣는 작업을 처리하는 함수다.

해제를 요청한 풀이 문제가 없는지는 보통 해제 함수 앞 부분에서 검사한다. 따라서 여기서 문제가 발생하는 경우 해제를 요청한 풀 자체보다는 지연 해제 관련 작업 중에 문제가 발생하는 경우가 많다.

이제 본격적으로 문제가 발생한 부분을 살펴보자.

먼저 문제가 발생한 지점으로 trap 을 맞춰보자.

kd> .trap 0xffffffff82b65ab4

ErrCode = 00000000

eax=11223344 ebx=000001ff ecx=000001ff edx=82b74d60 esi=8649a158 edi=82b74940

eip=82b5f795 esp=82b65b28 ebp=82b65b60 iopl=0 nv up ei pl nz na pe nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010206

nt!ExDeferredFreePool+0x135:

82b5f795 8b10 mov edx,dword ptr [eax]

ds:0023:11223344=????????


nt!ExDeferredFreePool+0x135 위치에서 mov edx, dword ptr [eax] 명령을 수행하다 eax 11223344 로 접근할 수 없는 주소여서 문제가 발생했다고 보여준다.

앞서 다른 메모리 손상 덤프 분석을 통해 언급했듯이 메모리 손상 유형은 크게 2가지다.

1.  앞 쪽 메모리 영역에서 다음 메모리 영역을 침범한 경우

2.  해당 위치의 메모리만 손상시킨 경우

주로 첫 번째 유형인 경우가 많으며 대체로 분석이 가능하다. 하지만 두 번째 유형인 경우 분석이 거의 불가능하다.

메모리가 손상된 원인을 분석하기 위한 일반적인 방법은 손상된 값의 위치를 찾아 그 앞 부분을 살펴보는  것이다.

왜냐하면 첫 번째 유형의 경우 다음과 같은 과정으로 메모리가 손상되기 때문이다.

1. 정상 상태

구분

주소

모듈 A의 풀 1

0xa0000000

0x11223344

... ...

모듈 B의 풀 2

0xa0000100

0xb0000000

... ...

 

 

2. 풀 1에서 풀 2 영역 침범

구분

주소

모듈 A의 풀 1

0xa0000000

0x11223344

... ...

모듈 B의 풀 2

0xa0000100

0x11223344

... ...



1번은 서로 다른 모듈 A, B에서 각각 풀 메모리 1, 2 를 할당 받아 자신이 필요한 값을 설정한 상태를 나타낸다.

2번은 모듈 A에서 자신이 사용하는 풀 1 영역을 넘어 실수로 모듈 B가 사용하는 풀 2 영역의 메모리까지 0x11223344 값으로 덮어 쓴 상황이다.

이런 상황에서 모듈 B가 자신의 풀 2의 주소인 0xa0000100 에서 값을 읽어 참조하는 동작을 수행한다고 해보자.

모듈 B는 당연히 원래 설정해 놓은 유효한 주소값인 0xb0000000을 잘 가져오리라 기대하겠지만, 0xb0000000 값은 모듈 A에 의해 이미 0x11223344 값으로 변경되어 손상된 상태다.

모듈 B는 유효하지 않은 주소값인 0x11223344을 참조하다 문제를 발생시킨다.

이 과정이 이해가 된다면 자연스럽게 문제가 된 "0x11223344" 값을 담고 있는 풀 2 영역의 주소인 0xa0000100을 찾아 그 앞 쪽 풀이 어떤 모듈의 것인지 찾아 내는 것이 문제 해결의 열쇠임을 알 수 있을 것이다.

물론, 이번 덤프에서도 eax 레지스터에 담긴 "0x11223344" 값이 어디서부터 왔는지 찾아야 한다.

언제나처럼 문제가 발생한 함수인 ExDeferredFreePool 부터 차근히 살펴보자.

kd> u nt!ExDeferredFreePool L5d

nt!ExDeferredFreePool:

82b5f660 8bff mov edi,edi

82b5f662 55 push ebp

82b5f663 8bec mov ebp,esp

82b5f665 83e4f8 and esp,0FFFFFFF8h

82b5f668 83ec2c sub esp,2Ch

82b5f66b 53 push ebx

82b5f66c 56 push esi

82b5f66d 57 push edi

82b5f66e 8b7d08 mov edi,dword ptr [ebp+8]

82b5f671 8d4f4c lea ecx,[edi+4Ch]

82b5f674 33c0 xor eax,eax

82b5f676 8bd1 mov edx,ecx

82b5f678 40 inc eax

82b5f679 f00fc102 lock xadd dword ptr [edx],eax

82b5f67d 40 inc eax

82b5f67e 83f801 cmp eax,1

82b5f681 7412 je nt!ExDeferredFreePool+0x35 (82b5f695)

82b5f683 837d0c00 cmp dword ptr [ebp+0Ch],0

82b5f687 750c jne nt!ExDeferredFreePool+0x35 (82b5f695)

82b5f689 83c8ff or eax,0FFFFFFFFh

82b5f68c f00fc101 lock xadd dword ptr [ecx],eax

82b5f690 e9c4030000 jmp nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f695 33c0 xor eax,eax

82b5f697 f60701 test byte ptr [edi],1

82b5f69a c644243400 mov byte ptr [esp+34h],0

82b5f69f 8944241c mov dword ptr [esp+1Ch],eax

82b5f6a3 89442414 mov dword ptr [esp+14h],eax

82b5f6a7 89442418 mov dword ptr [esp+18h],eax

82b5f6ab 89442424 mov dword ptr [esp+24h],eax

82b5f6af 8d7704 lea esi,[edi+4]

82b5f6b2 750e jne nt!ExDeferredFreePool+0x62 (82b5f6c2)

82b5f6b4 8d54242c lea edx,[esp+2Ch]

82b5f6b8 8bce mov ecx,esi

82b5f6ba ff1534e1a382 call dword ptr [nt!_imp_KeAcquireInStackQueuedSpinLock (82a3e134)]

82b5f6c0 eb2d jmp nt!ExDeferredFreePool+0x8f (82b5f6ef)

82b5f6c2 648b1d24010000 mov ebx,dword ptr fs:[124h]

82b5f6c9 66ff8b86000000 dec word ptr [ebx+86h]

82b5f6d0 8bc6 mov eax,esi

82b5f6d2 f00fba3000 lock btr dword ptr [eax],0

82b5f6d7 7207 jb nt!ExDeferredFreePool+0x80 (82b5f6e0)

82b5f6d9 8bce mov ecx,esi

82b5f6db e8690df7ff call nt!KiAcquireGuardedMutex (82ad0449)

82b5f6e0 64a124010000 mov eax,dword ptr fs:[00000124h]

82b5f6e6 895e04 mov dword ptr [esi+4],ebx

82b5f6e9 fe808b020000 inc byte ptr [eax+28Bh]

82b5f6ef 8d9700010000 lea edx,[edi+100h]

82b5f6f5 8b02 mov eax,dword ptr [edx]

82b5f6f7 85c0 test eax,eax

82b5f6f9 0f858b000000 jne nt!ExDeferredFreePool+0x12a (82b5f78a)

82b5f6ff 8d474c lea eax,[edi+4Ch]

82b5f702 83c9ff or ecx,0FFFFFFFFh

82b5f705 f00fc108 lock xadd dword ptr [eax],ecx

82b5f709 f60701 test byte ptr [edi],1

82b5f70c 750f jne nt!ExDeferredFreePool+0xbd (82b5f71d)

82b5f70e 8d4c242c lea ecx,[esp+2Ch]

82b5f712 ff1530e1a382 call dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (82a3e130)]

82b5f718 e93c030000 jmp nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f71d 64a124010000 mov eax,dword ptr fs:[00000124h]

82b5f723 fe888b020000 dec byte ptr [eax+28Bh]

82b5f729 83660400 and dword ptr [esi+4],0

82b5f72d 33c9 xor ecx,ecx

82b5f72f 41 inc ecx

82b5f730 8bc6 mov eax,esi

82b5f732 f00fc108 lock xadd dword ptr [eax],ecx

82b5f736 85c9 test ecx,ecx

82b5f738 741f je nt!ExDeferredFreePool+0xf9 (82b5f759)

82b5f73a f6c102 test cl,2

82b5f73d 751a jne nt!ExDeferredFreePool+0xf9 (82b5f759)

82b5f73f 41 inc ecx

82b5f740 8d41fe lea eax,[ecx-2]

82b5f743 8bd0 mov edx,eax

82b5f745 8bfe mov edi,esi

82b5f747 8bc1 mov eax,ecx

82b5f749 f00fb117 lock cmpxchg dword ptr [edi],edx

82b5f74d 3bc1 cmp eax,ecx

82b5f74f 7508 jne nt!ExDeferredFreePool+0xf9 (82b5f759)

82b5f751 8d460c lea eax,[esi+0Ch]

82b5f754 e806faf0ff call nt!KeSignalGateBoostPriority (82a6f15f)

82b5f759 648b0d24010000 mov ecx,dword ptr fs:[124h]

82b5f760 8d8186000000 lea eax,[ecx+86h]

82b5f766 66ff00 inc word ptr [eax]

82b5f769 0fb700 movzx eax,word ptr [eax]

82b5f76c 6685c0 test ax,ax

82b5f76f 0f85e4020000 jne nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f775 8d4140 lea eax,[ecx+40h]

82b5f778 3900 cmp dword ptr [eax],eax

82b5f77a 0f84d9020000 je nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f780 e8145df0ff call nt!KiCheckForKernelApcDelivery (82a65499)

82b5f785 e9cf020000 jmp nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f78a b9ff010000 mov ecx,1FFh

82b5f78f 8b02            mov     eax,dword ptr [edx]  1) edx 값을 eax 설정

82b5f791 89442420        mov     dword ptr [esp+20h],eax  2) eax esp+20 저장

82b5f795 8b10            mov     edx,dword ptr [eax]  3) eax 11223344 여서 문제 발생


1)번부터 순서대로 살펴보면, 우선 1)번에서 edx 값을 eax에 설정했고, 2)번에서 설정한 eax esp+20 에 저장한 부분이 나온다.

3)번에서 eax 11223344 였으니 edx 의 값과 esp+20 11223344 값이 설정되어 있어야 앞뒤가 맞다.

예상이 맞는지 한 번 확인해보자.

kd> dd edx L1

82b74d60 8448ad20

 

kd> dd esp+20 L1

82b65b48 8457ec68


말도 안되는 값이 확인된다. edx에 설정된 값은 8448ad20이다. 심지어 esp+20에 설정된 값은 84d7ec68이다. 찾고 있는 11223344 값은 커녕 edx esp+20끼리도 서로 다르다.

이런 경우 실행 흐름을 잘못 잡고 있을 가능성이 크다.

아무래도 지금 보는 곳보다 더 아래 부분에서 문제가 발생한 3) 82b5f795 주소로 바로 이동하는 코드가 있을 가능성이 의심된다.

ExDeferredFreePool 함수를 아래 부분까지 좀 더 살펴보자.

kd> u nt!ExDeferredFreePool Le6

nt!ExDeferredFreePool:

82b5f660 8bff            mov     edi,edi

82b5f662 55              push    ebp

82b5f663 8bec            mov     ebp,esp

82b5f665 83e4f8          and     esp,0FFFFFFF8h

82b5f668 83ec2c          sub     esp,2Ch

82b5f66b 53              push    ebx

82b5f66c 56              push    esi

82b5f66d 57              push    edi

82b5f66e 8b7d08          mov     edi,dword ptr [ebp+8]

82b5f671 8d4f4c          lea     ecx,[edi+4Ch]

82b5f674 33c0            xor     eax,eax

82b5f676 8bd1            mov     edx,ecx

82b5f678 40              inc     eax

82b5f679 f00fc102        lock xadd dword ptr [edx],eax

82b5f67d 40              inc     eax

82b5f67e 83f801          cmp     eax,1

82b5f681 7412            je      nt!ExDeferredFreePool+0x35 (82b5f695)

82b5f683 837d0c00        cmp     dword ptr [ebp+0Ch],0

82b5f687 750c            jne     nt!ExDeferredFreePool+0x35 (82b5f695)

82b5f689 83c8ff          or      eax,0FFFFFFFFh

82b5f68c f00fc101        lock xadd dword ptr [ecx],eax

82b5f690 e9c4030000      jmp     nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f695 33c0            xor     eax,eax

82b5f697 f60701          test    byte ptr [edi],1

82b5f69a c644243400      mov     byte ptr [esp+34h],0

82b5f69f 8944241c        mov     dword ptr [esp+1Ch],eax

82b5f6a3 89442414        mov     dword ptr [esp+14h],eax

82b5f6a7 89442418        mov     dword ptr [esp+18h],eax

82b5f6ab 89442424        mov     dword ptr [esp+24h],eax

82b5f6af 8d7704          lea     esi,[edi+4]

82b5f6b2 750e            jne     nt!ExDeferredFreePool+0x62 (82b5f6c2)

82b5f6b4 8d54242c        lea     edx,[esp+2Ch]

82b5f6b8 8bce            mov     ecx,esi

82b5f6ba ff1534e1a382    call    dword ptr [nt!_imp_KeAcquireInStackQueuedSpinLock (82a3e134)]

82b5f6c0 eb2d            jmp     nt!ExDeferredFreePool+0x8f (82b5f6ef)

82b5f6c2 648b1d24010000  mov     ebx,dword ptr fs:[124h]

82b5f6c9 66ff8b86000000  dec     word ptr [ebx+86h]

82b5f6d0 8bc6            mov     eax,esi

82b5f6d2 f00fba3000      lock btr dword ptr [eax],0

82b5f6d7 7207            jb      nt!ExDeferredFreePool+0x80 (82b5f6e0)

82b5f6d9 8bce            mov     ecx,esi

82b5f6db e8690df7ff      call    nt!KiAcquireGuardedMutex (82ad0449)

82b5f6e0 64a124010000    mov     eax,dword ptr fs:[00000124h]

82b5f6e6 895e04          mov     dword ptr [esi+4],ebx

82b5f6e9 fe808b020000    inc     byte ptr [eax+28Bh]

82b5f6ef 8d9700010000    lea     edx,[edi+100h]  // 1) _POOL_DESCRIPTOR.PendingFrees 주소를 edx 저장

82b5f6f5 8b02            mov     eax,dword ptr [edx]

82b5f6f7 85c0            test    eax,eax

82b5f6f9 0f858b000000    jne     nt!ExDeferredFreePool+0x12a (82b5f78a)    // 2) 82b5f78a 이동

82b5f6ff 8d474c          lea     eax,[edi+4Ch]

82b5f702 83c9ff          or      ecx,0FFFFFFFFh

82b5f705 f00fc108        lock xadd dword ptr [eax],ecx

82b5f709 f60701          test    byte ptr [edi],1

82b5f70c 750f            jne     nt!ExDeferredFreePool+0xbd (82b5f71d)

82b5f70e 8d4c242c        lea     ecx,[esp+2Ch]

82b5f712 ff1530e1a382    call    dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (82a3e130)]

82b5f718 e93c030000      jmp     nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f71d 64a124010000    mov     eax,dword ptr fs:[00000124h]

82b5f723 fe888b020000    dec     byte ptr [eax+28Bh]

82b5f729 83660400        and     dword ptr [esi+4],0

82b5f72d 33c9            xor     ecx,ecx

82b5f72f 41              inc     ecx

82b5f730 8bc6            mov     eax,esi

82b5f732 f00fc108        lock xadd dword ptr [eax],ecx

82b5f736 85c9            test    ecx,ecx

82b5f738 741f            je      nt!ExDeferredFreePool+0xf9 (82b5f759)

82b5f73a f6c102          test    cl,2

82b5f73d 751a            jne     nt!ExDeferredFreePool+0xf9 (82b5f759)

82b5f73f 41              inc     ecx

82b5f740 8d41fe          lea     eax,[ecx-2]

82b5f743 8bd0            mov     edx,eax

82b5f745 8bfe            mov     edi,esi

82b5f747 8bc1            mov     eax,ecx

82b5f749 f00fb117        lock cmpxchg dword ptr [edi],edx

82b5f74d 3bc1            cmp     eax,ecx

82b5f74f 7508            jne     nt!ExDeferredFreePool+0xf9 (82b5f759)

82b5f751 8d460c          lea     eax,[esi+0Ch]

82b5f754 e806faf0ff      call    nt!KeSignalGateBoostPriority (82a6f15f)

82b5f759 648b0d24010000  mov     ecx,dword ptr fs:[124h]

82b5f760 8d8186000000    lea     eax,[ecx+86h]

82b5f766 66ff00          inc     word ptr [eax]

82b5f769 0fb700          movzx   eax,word ptr [eax]

82b5f76c 6685c0          test    ax,ax

82b5f76f 0f85e4020000    jne     nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f775 8d4140          lea     eax,[ecx+40h]

82b5f778 3900            cmp     dword ptr [eax],eax

82b5f77a 0f84d9020000    je      nt!ExDeferredFreePool+0x3f8 (82b5fa59)

82b5f780 e8145df0ff      call    nt!KiCheckForKernelApcDelivery (82a65499)

82b5f785 e9cf020000      jmp     nt!ExDeferredFreePool+0x3f8 (82b5fa59)

 

82b5f78a b9ff010000      mov     ecx,1FFh

82b5f78f 8b02            mov     eax,dword ptr [edx]  // 3) edx 가리키는 _POOL_DESCRIPTOR.PendingFrees 값을 eax 저장

82b5f791 89442420        mov     dword ptr [esp+20h],eax

 

82b5f795 8b10            mov     edx,dword ptr [eax]  // 4) eax  값을 edx 설정 (문제 발생 위치)

82b5f797 ff44241c        inc     dword ptr [esp+1Ch]

82b5f79b 8364241000      and     dword ptr [esp+10h],0

82b5f7a0 89542428        mov     dword ptr [esp+28h],edx  // 5) edx esp+28 저장

82b5f7a4 83c0f8          add     eax,0FFFFFFF8h

82b5f7a7 0fb710          movzx   edx,word ptr [eax]

82b5f7aa 33db            xor     ebx,ebx

82b5f7ac c1ea09          shr     edx,9

82b5f7af 8d7744          lea     esi,[edi+44h]

82b5f7b2 43              inc     ebx

82b5f7b3 f00fc11e        lock xadd dword ptr [esi],ebx

82b5f7b7 0fb77002        movzx   esi,word ptr [eax+2]

82b5f7bb 23f1            and     esi,ecx

82b5f7bd 6bf6f8          imul    esi,esi,0FFFFFFF8h

82b5f7c0 8bde            mov     ebx,esi

82b5f7c2 8d7750          lea     esi,[edi+50h]

82b5f7c5 f00fc11e        lock xadd dword ptr [esi],ebx

82b5f7c9 0fb77002        movzx   esi,word ptr [eax+2]

82b5f7cd 23f1            and     esi,ecx

82b5f7cf 8d34f0          lea     esi,[eax+esi*8]

82b5f7d2 8974240c        mov     dword ptr [esp+0Ch],esi

82b5f7d6 f7c6ff0f0000    test    esi,0FFFh

82b5f7dc 7468            je      nt!ExDeferredFreePool+0x1e6 (82b5f846)

 

... ... (함수가 길어 중간 내용 생략)

 

82b5f93e 8930            mov     dword ptr [eax],esi

82b5f940 895004          mov     dword ptr [eax+4],edx

82b5f943 894604          mov     dword ptr [esi+4],eax

82b5f946 8902            mov     dword ptr [edx],eax

82b5f948 8b442428        mov     eax,dword ptr [esp+28h]  // 6) 앞서 저장했던 esp+28 값을 eax 설정

82b5f94c 3b442424        cmp     eax,dword ptr [esp+24h]

82b5f950 0f853ffeffff    jne     nt!ExDeferredFreePool+0x135 (82b5f795)  // 7) 4) 82b5f795 이동


역시나 7)번을 보면 4)번 위치인 82b5f795로 이동하는 부분이 보인다(이전 함수 출력 결과에서는 3)번 위치).

kd> dd esp+28 L1

82b65b50 11223344


6)번의 esp+28 값을 확인해보니 우리가 찾던 "11223344" 값이 확인된다. 7)번까지 수행하고 다시 4)번으로 돌아가서 명령을 수행하다 문제가 발생한 것이 분명하다.

함수가 좀 복잡하여 이해하기 쉽게 미리 확인한 내용을 주석으로 적어 보았다.

1)~7)번까지 동작을 한 줄로 정리하면 다음과 같다.

Pool Descriptor PendingFrees 리스트를 순회하다 해당 엔트리 하나가 11223344 여서 문제 발생

1)번 동작인 "lea edx,[edi+100h]"에서 edi 는 해제하려는 풀 메모리의 POOL_DESCRIPTOR 이다.

edi 값인 82b74940가 콜 스택의 첫 번째 파라미터와 동일하기 때문이다.

kd> r edi

Last set context:

edi=82b74940

 

kd> dd ebp+8 L1

82b65b68 82b74940


이어서, ExDeferredFreePool 함수 원형을 살펴보면 첫 번째 파라미터는 POOL_DESCRIPTOR.

VOID

ExDeferredFreePool(

IN PPOOL_DESCRIPTOR PoolDesc

);


따라서 1)번 동작은 POOL_DESCRIPTOR +100번 위치의 필드를 구하는 동작이다.

edi 값인 82b74940를 dt _POOL_DESCRIPTOR 명령으로 확인해보자.

kd> dt _POOL_DESCRIPTOR 82b74940

nt!_POOL_DESCRIPTOR

+0x000 PoolType : 0 ( NonPagedPool )

+0x004 PagedLock : _KGUARDED_MUTEX

+0x004 NonPagedLock : 0x8b451858

+0x040 RunningAllocs : 0x7715b

+0x044 RunningDeAllocs : 0x6af0a

+0x048 TotalBigPages : 0xce1

+0x04c ThreadsProcessingDeferrals : 1

+0x050 TotalBytes : 0x128c1a0

+0x080 PoolIndex : 0

+0x0c0 TotalPages : 0x7b2

+0x100 PendingFrees : 0x8652f468 -> 0x8458fbd0 Void

+0x104 PendingFreeDepth : 0x22

+0x140 ListHeads : [512] _LIST_ENTRY [ 0x82b74a80 - 0x82b74a80 ]


+0x100 위치의 필드는 지연 해제할 리스트의 목록인 PendingFrees 이다.

3)~7)번을 살펴보면 이 PendingFrees 의 다음 포인터를 계속 가져오면서 리스트 순회를 하는 동작 중에 "11223344"라는 잘못된 값을 만나 문제가 발생한 것이다.

좋다. 이러면 PendingFrees 리스트 어딘가에 11223344 값을 담고 있는 엔트리가 있어야 할 것이다.

리스트를 출력하는 dl 명령으로 PendingFrees가 가리키는 0x8652f468을 넣어 살펴보자.

kd> dl 0x8652f468

8652f468 8458fbd0 00000000 00060940 8458fc00

8458fbd0 8457ec68 82b74bb8 00000000 00000000

8457ec68 82b74d70 82b74d70 00000000 00000000

82b74d70 82b74d70 82b74d70 84598008 84584008


어라? 11223344 값을 갖고 있는 엔트리가 있어야 하는데 보이지 않는다.

82b74d70에서 리스트가 끝나는데, 잘 보면 리스트 마지막이 NULL 이 아니다.

PendingFrees 리스트는 SINGLE_LIST_ENTRY 여서 마지막은 NULL로 끝나야 한다. 메모리 손상에 의해 리스트의 연결이 엉뚱하게 바뀐 것으로 보인다.

PendingFrees 리스트를 순회하다 손상된 엔트리에 있는 11223344 값을 가져왔지만, 손상된 엔트리의 흔적은 이후에 리스트의 링크가 변경되면서 없어져 버렸다.

참고로 이런 일이 가능한 이유는 PendingFrees 리스트가 여러 스레드에 의해 재구성될 수 있는 구조여서 중간에 바뀌어 버릴 수 있기 때문이다. 디버깅을 통해 확인된 내용으로 주제를 벗어나는 내용이라 자세히 다루지 않았다. 여기서는 리스트에서 손상된 부분을 찾을 수 없는 상황으로 이해하면 되겠다.

어쨌든 추적 중인 리스트가 정상적이지 않아 일반적인 방법으로는 더 이상 추적이 불가능한 상황이다.

한층 더 어려워진 문제를 풀기 위해서 잠시 생각을 정리할 시간이 필요해 보인다.

일단 풀 메모리가 해제될 때 풀의 상태가 어떻게 변할지 생각해보자.

풀 메모리가 ExFreePoolWithTag 함수로 해제되면 풀의 첫 부분에 이전 해제된 풀의 주소가 설정된다.

이전 해제된 풀의 첫 부분에는 마찬가지로 또 다른 해제된 풀 주소가 설정되므로 해제된 풀들의 싱글 링크드 리스트가 구성된다.

위에 dl 명령으로 확인했던 첫 번째 풀 주소인 8652f468을 다시 살펴보자.

kd> !pool 8652f468

Pool page 8652f468 region is Nonpaged pool

8652f000 size: 2e8 previous size: 0 (Allocated) Scbf

8652f2e8 size: 8 previous size: 2e8 (Free) }n=\

8652f2f0 size: 40 previous size: 8 (Allocated) Even (Protected)

8652f330 size: 50 previous size: 40 (Allocated) Vadl

8652f380 size: e0 previous size: 50 (Free) Thre

*8652f460 size: 208 previous size: e0 (Free ) *Irp Process: 85d35030

Pooltag Irp : Io, IRP packets

8652f668 size: 18 previous size: 208 (Allocated) ReEv

8652f680 size: 68 previous size: 18 (Allocated) FMsl

8652f6e8 size: 28 previous size: 68 (Allocated) VadS

8652f710 size: 68 previous size: 28 (Allocated) EtwR (Protected)

8652f778 size: 2e8 previous size: 68 (Allocated) Thre (Protected)

8652fa60 size: b8 previous size: 2e8 (Allocated) File (Protected)

8652fb18 size: 68 previous size: b8 (Allocated) FMsl

8652fb80 size: b0 previous size: 68 (Allocated) AfdC (Protected)

8652fc30 size: 68 previous size: b0 (Allocated) FMsl

8652fc98 size: 8 previous size: 68 (Free) KSpp

8652fca0 size: 48 previous size: 8 (Allocated) Vad

8652fce8 size: 68 previous size: 48 (Allocated) EtwR (Protected)

8652fd50 size: 138 previous size: 68 (Allocated) ALPC (Protected)

8652fe88 size: 138 previous size: 138 (Allocated) ALPC (Protected)

8652ffc0 size: 40 previous size: 138 (Allocated) Even (Protected)


역시 Irp 풀 태그를 갖고 있는 해제된 풀이 확인된다.

kd> db 8652f468

8652f468 d0 fb 58 84 00 00 00 00-40 09 06 00 00 fc 58 84 ..X.....@.....X.

8652f478 78 f4 52 86 78 f4 52 86-00 00 00 00 18 00 00 00 x.R.x.R.........

8652f488 00 01 0b 0d 00 00 00 01-38 da af ff 00 00 00 00 ........8.......

8652f498 0a 0f 52 82 10 da af ff-00 00 00 00 6c da af ff ..R.........l...

8652f4a8 12 00 30 00 00 00 00 00-30 00 f7 85 70 00 f7 85 ..0.....0...p...

8652f4b8 70 00 f7 85 6e 94 c1 82-72 53 cf 82 0a 0f 52 82 p...n...rS....R.

8652f4c8 10 da af ff 38 da af ff-00 00 00 00 00 00 00 00 ....8...........

8652f4d8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................


그리고, 4바이트에 8458fbd0 (거꾸로 읽어야 한다!) 값이 확인된다.

8458fbd0 값이 어떤 메모리 영역인지 확인하기 위해 !pool 명령으로 확인해보자.

kd> !pool 8458fbd0

Pool page 8458fbd0 region is Nonpaged pool

8458f000 size: c8 previous size: 0 (Allocated) Ntfx

8458f0c8 size: 30 previous size: c8 (Free) ....

8458f0f8 size: c8 previous size: 30 (Allocated) Ntfx

8458f1c0 size: 90 previous size: c8 (Allocated) MmCa

8458f250 size: a8 previous size: 90 (Allocated) File (Protected)

8458f2f8 size: 68 previous size: a8 (Allocated) FMsl

8458f360 size: c8 previous size: 68 (Allocated) Ntfx

8458f428 size: 28 previous size: c8 (Free) usbp

8458f450 size: 68 previous size: 28 (Allocated) FMsl

8458f4b8 size: c8 previous size: 68 (Allocated) Ntfx

8458f580 size: 90 previous size: c8 (Allocated) MmCa

8458f610 size: a8 previous size: 90 (Allocated) File (Protected)

8458f6b8 size: 68 previous size: a8 (Allocated) FMsl

8458f720 size: 90 previous size: 68 (Allocated) MmCa

8458f7b0 size: c8 previous size: 90 (Allocated) Ntfx

8458f878 size: c8 previous size: c8 (Allocated) Ntfx

8458f940 size: 20 previous size: c8 (Free) XSav

8458f960 size: 90 previous size: 20 (Allocated) MmCa

8458f9f0 size: a8 previous size: 90 (Allocated) File (Protected)

8458fa98 size: 68 previous size: a8 (Allocated) FMsl

8458fb00 size: c8 previous size: 68 (Allocated) Ntfx

*8458fbc8 size: 140 previous size: c8 (Free ) *Io Process: 85d35030

Pooltag Io : general IO allocations, Binary : nt!io

8458fd08 size: 2f8 previous size: 140 (Free ) usbp


!pool 명령으로 확인해보면 역시나 다음 해제된 풀의 주소이다. 여기서 알 수 있는 사실은 해제된 풀의 첫 4바이트는 해제된 풀들의 리스트이고 이는 POOL_DESCRIPTOR PendingFrees 리스트와 연관이 있다는 것이다.

달리 말해 이 첫 4바이트가 손상되면 PendingFrees 리스트에도 문제가 생겨 이번과 같은 이슈가 발생하게 된다.

그렇다면 우리가 찾고 있는 "11223344" 값은 누군가에 의해 손상된 값이 아닐까?

이를 테면 누군가 메모리를 해제하고 해제된 메모리의 첫 4바이트에 11223344 값을 써서 의도치 않게 PendingFrees 리스트를 손상시키는 상황을 상상해보자.

그럴 듯 하다. 그렇다면... 그래! 모두 뒤져보자.

메모리 어딘가에는 11223344 값이 기록되어 있을 테니 전체 메모리 탐색을 통해 증거를 찾아보자.

메모리 검색을 위해 s 명령을 사용했으며 검색 범위는 커널 주소 영역 전체인 80000000 부터 ffffffff까지 설정했다.

참고로 검색 크기를 설정하는 L 옵션에 ? 를 사용해야 큰 범위 설정이 가능하다.

kd> s 80000000 L?80000000 44 33 22 11

// kdvm.dll 모듈

80bd0342 44 33 22 11 02 00 00 00-00 00 00 00 ff ff ff ff D3".............

// nt 모듈

82b656c4 44 33 22 11 50 2b ba 82-50 2b ba 82 95 f7 b5 82 D3".P+..P+......

// nt 모듈

82b656d8 44 33 22 11 95 f7 b5 82-60 5b b6 82 e7 13 01 87 D3".....`[......

// nt 모듈

82b6570c 44 33 22 11 00 00 00 00-80 23 b7 82 00 00 00 00 D3"......#......

// nt 모듈

82b65a30 44 33 22 11 58 a1 49 86-60 5b b6 82 03 01 00 00 D3".X.I.`[......

// nt 모듈

82b65aa0 44 33 22 11 02 00 00 00-00 00 00 00 95 f7 b5 82 D3".............

// nt 모듈

82b65af8 44 33 22 11 60 01 40 01-ff ff ff ff 30 00 13 8d D3".`.@.....0...

// nt 모듈

82b65b50 44 33 22 11 58 18 45 8b-46 49 b7 82 02 00 00 00 D3".X.E.FI......

// nt 모듈

82b69008 44 33 22 11 00 50 18 00-f9 06 00 00 00 00 00 00 D3"..P..........

// nt 모듈

82b9fa44 44 33 22 11 02 00 00 00-00 00 00 00 95 f7 b5 82 D3".............

// nt 모듈

82ba2138 44 33 22 11 95 f7 b5 82-60 5b b6 82 e7 13 01 87 D3".....`[......

// nt 모듈

82ba216c 44 33 22 11 03 00 00 00-80 23 b7 82 00 00 00 00 D3"......#......

// nt 모듈

82ba2490 44 33 22 11 58 a1 49 86-60 5b b6 82 03 01 00 00 D3".X.I.`[......

// nt 모듈

82ba2500 44 33 22 11 02 00 00 00-00 00 00 00 95 f7 b5 82 D3".............

// nt 모듈

82ba2558 44 33 22 11 60 01 40 01-ff ff ff ff 30 00 13 8d D3".`.@.....0...

// nt 모듈

82ba25b0 44 33 22 11 00 00 00 00-46 49 b7 82 02 00 00 00 D3".....FI......

// nt 모듈

82dab21c 44 33 22 11 00 50 18 00-f9 06 00 00 00 00 00 00 D3"..P..........

// nt 모듈

82dab4cc 44 33 22 11 02 00 00 00-00 00 00 00 ff ff ff ff D3".............

// ***

8448ae08 44 33 22 11 40 30 fb 85-10 ae 48 84 10 ae 48 84 D3".@0....H...H.

// BSOD 관련 crashdmp.sys 영역

85b8102c 44 33 22 11 02 00 00 00-00 00 00 00 95 f7 b5 82 D3".............

// crashdmp.sys 모듈

8d1682d0 44 33 22 11 02 00 00 00-00 00 00 00 95 f7 b5 82 D3".............

// crashdmp.sys 모듈

8d1686e8 44 33 22 11 02 00 00 00-00 00 00 00 95 f7 b5 82 D3".............

// MyDrv.sys 모듈

90e01322 44 33 22 11 8b e5 5d c3-cc cc cc cc cc cc 8b ff D3"...].........


우리는 해제된 풀을 찾을 목적이기 때문에 결과에서 모듈 영역 주소와 연관이 없는 풀 메모리 영역을 제외하면 8448ae08 주소 하나가 남는다.(주석 내용은 내가 lmva !pool 명령으로 하나씩 확인하면서 적은 것으로 *** 표시한 주소가 모듈 영역 주소도 아니면서 가장 의심이 가는 주소다)

8448ae08 내용을 풀 헤더를 포함해서 한 번 살펴보자.

kd> db 8448ae08-8

   ↓"MDrv"     ↓"11223344"

8448ae00 1d 00 40 08 4d 44 72 76-44 33 22 11 40 30 fb 85 ..@.MDrvD3".@0..

8448ae10 10 ae 48 84 10 ae 48 84-00 00 00 00 18 00 00 00 ..H...H.........

8448ae20 00 01 0b 0d 00 00 00 01-38 da af ff 00 00 00 00 ........8.......

8448ae30 0a 0f 52 82 10 da af ff-00 00 00 00 6c da af ff ..R.........l...

8448ae40 12 00 30 00 00 00 00 00-30 00 f7 85 70 00 f7 85 ..0.....0...p...

8448ae50 70 00 f7 85 6e 94 c1 82-72 53 cf 82 0a 0f 52 82 p...n...rS....R.

8448ae60 10 da af ff 38 da af ff-00 00 00 00 00 00 00 00 ....8...........

8448ae70 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................


8448ae08 주소에 찾고 있는 11223344 값이 보이고, 4바이트 앞 풀 태그 영역에는 "MDrv"라는 의미 있는 문자열이 확인된다.

검증을 위해 풀 헤더 시작 주소인 8448ae00 POOL_HEADER 구조체로 확인해보자.

kd> dt _POOL_HEADER 8448ae00

nt!_POOL_HEADER

+0x000 PreviousSize : 0y000011101 (0x1d)

+0x000 PoolIndex : 0y0000000 (0)

+0x002 BlockSize : 0y001000000 (0x40)   // 크기

+0x002 PoolType : 0y0000100 (0x4)

+0x000 Ulong1 : 0x840001d

+0x004 PoolTag : 0x7672444d             // MDrv 태그

+0x004 AllocatorBackTraceIndex : 0x444d

+0x006 PoolTagHash : 0x7672


BlockSize 0x40이라고 나온다. 실제 바이트로 환산하려면 8을 곱해줘야 한다.

kd> ?40*8

Evaluate expression: 512 = 00000200


0x200이 풀 크기로 계산된다. 만약 8448ae00이 유효한 풀 주소가 맞다면 전체 풀 크기인 8448ae00+0x200 뒤에 다음 풀 정보가 있을 것이다.

kd> db 8448ae00 L200+10

8448ae00 1d 00 40 08 4d 44 72 76-44 33 22 11 40 30 fb 85 ..@.MDrvD3".@0..

8448ae10 10 ae 48 84 10 ae 48 84-00 00 00 00 18 00 00 00 ..H...H.........

8448ae20 00 01 0b 0d 00 00 00 01-38 da af ff 00 00 00 00 ........8.......

8448ae30 0a 0f 52 82 10 da af ff-00 00 00 00 6c da af ff ..R.........l...

8448ae40 12 00 30 00 00 00 00 00-30 00 f7 85 70 00 f7 85 ..0.....0...p...

8448ae50 70 00 f7 85 6e 94 c1 82-72 53 cf 82 0a 0f 52 82 p...n...rS....R.

8448ae60 10 da af ff 38 da af ff-00 00 00 00 00 00 00 00 ....8...........

8448ae70 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448ae80 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448ae90 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448aea0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448aeb0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448aec0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448aed0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448aee0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448aef0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af00 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af10 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af20 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af30 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af40 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af50 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af60 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af70 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af80 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448af90 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448afa0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448afb0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448afc0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

8448afd0 00 00 00 00 00 00 00 00-03 00 00 00 00 00 00 00 ................

8448afe0 00 00 00 00 00 00 00 00-00 00 00 00 78 35 4a 85 ............x5J.

8448aff0 00 00 00 00 00 00 00 00-00 00 00 00 30 50 d3 85 ............0P..

 

// 다음 주소 8448b000

8448b000 00 00 fb 15 49 6f 20 20-00 00 00 00 04 00 01 00 ....Io ........


다음 풀 주소 위치인 8448b000 Io 라는 문자열이 확인된다. !pool 명령으로도 확인해보자.

kd> !pool 8448b000

Pool page 8448b000 region is Nonpaged pool

*8448b000 size: fd8 previous size: 0 (Allocated) *Io Process: 8618c1d0

Pooltag Io : general IO allocations, Binary : nt!io

8448bfd8 size: 28 previous size: fd8 (Allocated) VadS


Io 풀 태그를 갖는 정상적인 풀 메모리가 맞다.

그렇다면 찾은 8448ae00 MDrv 풀 태그를 갖는 정상적으로 해제된 풀 메모리였을 것이다.

무엇보다 해제된 풀 헤더 다음 첫 4바이트에 해제된 풀의 주소가 아닌 "11223344" 값을 갖고 있는 것이 결정적인 증거다.

이는 풀의 소유자가 자신이 해제한 메모리를 재사용했다는 증거이다.

이제 앞서 배운 !for_each_module 명령과 lmva 명령을 통해 MDrv 풀 태그의 소유자를 찾아보자.

kd> !for_each_module s -a @#Base @#End MDrv

90e012f7 4d 44 72 76 68 f8 01 00-00 6a 00 ff 15 48 20 e0 MDrvh....j...H .

 

kd> lmva 90e012f7

Browse full module list

start end module name

90e00000 90e08000 MyDrv (private pdb symbols)

Loaded symbol image file: MyDrv.sys

Image path: \??\C:\WinDbg\MyDrv.sys

Image name: MyDrv.sys

Browse all global symbols functions data

Timestamp: Thu Nov 30 18:44:39 2017 (5A1FD307)

CheckSum: 000094C4

ImageSize: 00008000

Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4


이런! MyDrv.sys 모듈이 범인이었다. 마지막 퍼즐 한 조각을 찾은 것 같은 가슴 벅찬 순간이다.

이쯤에서 MyDrv.sys 모듈에서 해제 리스트를 손상시킨 소스코드를 공개하겠다.

(참고로 오늘 덤프는 학습을 위해 아래 예제 코드를 통해 생성한 덤프다)

typedef struct _FREE_LIST_DATA

{

ULONG ulTag;

CHAR cBuffer[500];

} FREE_LIST_DATA, *PFREE_LIST_DATA;

 

void CorruptPoolHeader(void)

{

PFREE_LIST_DATA pData;

 

pData = ExAllocatePoolWithTag(NonPagedPool, sizeof(FREE_LIST_DATA), 'vrDM');

if (pData)

{

ExFreePool(pData);

}

 

// Oops!

pData->ulTag = 0x11223344;

}


메모리를 해제한 뒤에 첫 번째 필드인 ulTag 11223344 값을 쓰고 있다!

이런 단순한 실수로 이렇게 엄청난 덤프를 만들 수 있다는 사실이 놀랍다.

이번 예제는 일부러 눈에 잘 띄는 값으로 해제된 메모리를 손상시켰지만, 실제로는 비트 연산으로 1 비트만 손상시키는 경우도 있다. 이런 경우 분석이 거의 불가능하다. 이번처럼 분석이 성공하는 경우는 굉장히 운이 좋은 날이라고 생각하면 된다. 보통 메모리 손상의 유형 중 특정 위치의 메모리만 손상시키는 2번 시나리오는 손상된 위치에서 범인을 찾기가 매우 어렵기 때문이다.

예제를 통해 실수는 한 줄이지만 이로 인한 분석은 굉장히 어렵다는 사실을 충분히 느꼈으리라 생각한다.

이런 이유로 개발시 드라이버 확인 프로그램을 충분히 활용하고 해제된 메모리는 재사용하지 않게 주의해야 한다.





'Dump Analysis' 카테고리의 다른 글

[Hang] CPU 과점유  (0) 2018.08.13
[0x133] DPC_WATCHDOG_VIOLATION  (0) 2018.07.29
[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 해제된 핸들  (0) 2018.07.09
2018. 7. 16. 23:39

이번 덤프는 BugCheck 0xC5: DRIVER_CORRUPTED_EXPOOL인데 주로 풀 메모리 관련해서 유효하지 않은 주소 영역에 접근했을 때 발생한다.

0xA, 0x50 등과 함께 이런 오류 코드가 발생하면 누군가가 메모리를 손상시켰구나라고 생각하면 대부분 맞다.

기본 분석부터 시작해보자.

kd> !analyze -v

*******************************************************************************

*      *

*      Bugcheck Analysis   *

*      *

*******************************************************************************

DRIVER_CORRUPTED_EXPOOL (c5)

An attempt was made to access a pageable (or completely invalid) address at an

interrupt request level (IRQL) that is too high.  This is

caused by drivers that have corrupted the system pool.  Run the driver

verifier against any new (or suspect) drivers, and if that doesn't turn up

the culprit, then use gflags to enable special pool.

Arguments:

Arg1: 00000004, memory referenced

Arg2: 00000002, IRQL

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

Arg4: 8355a4c1, address which referenced memory

Debugging Details:

------------------

 

DUMP_CLASS: 1

DUMP_QUALIFIER: 402

BUILD_VERSION_STRING:  7601.18044.x86fre.win7sp1_gdr.130104-1431

SYSTEM_MANUFACTURER:  LG Electronics                  

SYSTEM_PRODUCT_NAME:  U460-MFBJL

SYSTEM_SKU:  System SKUNumber

SYSTEM_VERSION:  1.0

BIOS_VENDOR:  Phoenix Technologies Ltd.

BIOS_VERSION:  UNCNSF09

BIOS_DATE:  04/22/2013

BASEBOARD_MANUFACTURER:  LG Electronics

BASEBOARD_PRODUCT:  U560

BASEBOARD_VERSION:  1.0

DUMP_TYPE:  0

BUGCHECK_P1: 4

BUGCHECK_P2: 2

BUGCHECK_P3: 1

BUGCHECK_P4: ffffffff8355a4c1

BUGCHECK_STR:  0xC5_2

CURRENT_IRQL:  2

FAULTING_IP: 

nt!ExAllocatePoolWithTag+4b7

8355a4c1 897004          mov     dword ptr [eax+4],esi

CPU_COUNT: 4

CPU_MHZ: 703

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 3a

CPU_STEPPING: 9

CPU_MICROCODE: 6,3a,9,0 (F,M,S,R)  SIG: 17'00000000 (cache) 17'00000000 (init)

DEFAULT_BUCKET_ID:  WIN7_DRIVER_FAULT

PROCESS_NAME:  explorer.exe

ANALYSIS_SESSION_HOST:  PAUL-PC

ANALYSIS_SESSION_TIME:  11-14-2017 10:36:26.0368

ANALYSIS_VERSION: 10.0.10575.567 amd64fre

TRAP_FRAME:  b683f62c -- (.trap 0xffffffffb683f62c)

ErrCode = 00000002

eax=00000000 ebx=835706c0 ecx=8b6ac9f0 edx=86b117b0 esi=83570840 edi=835706c4

eip=8355a4c1 esp=b683f6a0 ebp=b683f6e8 iopl=0         nv up ei pl zr na pe nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010246

nt!ExAllocatePoolWithTag+0x4b7:

8355a4c1 897004          mov     dword ptr [eax+4],esi ds:0023:00000004=????????

Resetting default scope

LAST_CONTROL_TRANSFER:  from 8355a4c1 to 8347ac7b

STACK_TEXT:  

b683f62c 8355a4c1 badb0d00 86b117b0 b683f690 nt!KiTrap0E+0x2cf

b683f6e8 83ba6aa1 00000000 00000040 7843464d nt!ExAllocatePoolWithTag+0x4b7

b683f700 83ba8091 8ae62bf8 8bbdc820 869f1530 fltmgr!ExAllocateFromPagedLookasideList+0x27

b683f718 95224e17 86a2aaf8 00000010 00000010 fltmgr!FltAllocateContext+0xa9

b683f73c 952251b6 8bbdc820 b683f79c 86e48320 MyDrv+0x8e17

b683f758 952252cc 8bbdc820 b683f79c 86e48320 MyDrv+0x91b6

b683f77c 83ba7aeb 8bbdc820 b683f79c b683f7c8 MyDrv+0x92cc

b683f7e8 83baa9f0 b683f82c d39a3dd8 00000000 fltmgr!FltpPerformPreCallbacks+0x34d

b683f800 83bbe1fe b683f82c 83bc1f3c 00000000 fltmgr!FltpPassThroughInternal+0x40

b683f814 83bbe8b7 b683f82c d39a3dd8 86b76f80 fltmgr!FltpCreateInternal+0x24

b683f858 83470c0e 86e1c590 86e0f7e8 8b67de58 fltmgr!FltpCreate+0x2c9

b683f870 b65ddffa 86b76f80 8b67de58 86b76f80 nt!IofCallDriver+0x63

b683f900 83470c0e 8b67de58 d39a3dd8 86b76fdc SomeDrv+0x5ffa

b683f918 836803ee a90c97a7 b683fac0 00000000 nt!IofCallDriver+0x63

b683f9f0 8365fc1e 86d4ae20 85ebb9c8 89a08d20 nt!IopParseDevice+0xee6

b683fa6c 83670030 00000000 b683fac0 00000040 nt!ObpLookupObjectName+0x4fa

b683fac8 83666b0e 081df704 85ebb9c8 00000001 nt!ObOpenObjectByName+0x165

b683fb44 8366cc94 179b0980 00020000 081df704 nt!IopCreateFile+0x673

b683fb8c b65b9725 179b0980 00020000 081df704 nt!NtOpenFile+0x2a

b683fc14 8347789a 179b0980 00020000 081df704 BadDrv+0x1725

b683fc14 775c7094 179b0980 00020000 081df704 nt!KiFastCallEntry+0x12a

081df6d8 775c5ce4 740723ea 179b0980 00020000 ntdll!KiFastSystemCallRet

081df6dc 740723ea 179b0980 00020000 081df704 ntdll!ZwOpenFile+0xc

081df724 74072a9e 081df744 00000000 00020000 ntmarta!I_MartaFileNtOpenFile+0x4d

081df754 74072b79 179b0038 00020000 081df7a8 ntmarta!MartaGetFileParentContext+0x5c

081df7b4 740726fe 179b0038 081df7e0 00000005 ntmarta!MartaGetRightsFromContext+0xd1

081df808 6fca3819 17a823a0 00000001 00000005 ntmarta!AccRewriteGetNamedRights+0x7f

081df844 6fca3f7c 17a823a0 081df86c 00000000 ntshrui!CFolderAclEngine::_GetAcl+0x30

081df874 6fca3f05 17d65814 081df894 6fca316c ntshrui!CFolderAclEngine::_IsItemPrivate+0x63

081df88c 6fca4960 00000000 17d65814 081df8c8 ntshrui!CSmbShareEngine::GetItemSharingStatus+0x1b

081df8b4 6fca4a18 17d65814 17f28478 00659530 ntshrui!CSharingOverlayPrivate::_GetSharingStatus+0x85

081df8d4 76512ca8 00622348 17bafb04 00000010 ntshrui!CSharingOverlayPrivate::IsMemberOf+0x69

081df904 7662fcb4 081df958 00000010 00000064 SHELL32!CFSIconOverlayManager::_GetFileOverlayInfo+0x11a

081df920 76512bae 0062adc0 081df958 00000010 SHELL32!CFSIconOverlayManager::GetFileOverlayInfo+0x1b

081dfb64 76512ab8 040503f8 081dfbe4 00000001 SHELL32!CFSFolder::_GetOverlayInfo+0x10f

081dfb78 7651478b 17dd39c0 040503f8 081dfbe4 SHELL32!CFSFolder::GetOverlayIndex+0x28

081dfba0 765138fe 08db2d04 040503f8 081dfbe4 SHELL32!CDesktopFolder::GetOverlayIndex+0x40

081dfbc0 7014b2d0 08de60f8 040503f8 081dfbe4 SHELL32!CRegFolder::GetOverlayIndex+0x45

081dfbdc 7014b289 ffffffff 040503f8 0f741488 EXPLORERFRAME!CNscOverlayTask::_Extract+0x32

081dfbf4 701108f6 08de60e4 01000000 80000000 EXPLORERFRAME!CNscOverlayTask::InternalResumeRT+0x31

081dfc14 765763bb 0f74149c 7fffffff 08e15488 EXPLORERFRAME!CRunnableTask::Run+0xce

081dfc30 76578c43 081dfc6c 00000000 17a748d0 SHELL32!CShellTask::TT_Run+0x167

081dfc78 76578d77 081dfc90 7627b2b1 08e15488 SHELL32!CShellTaskThread::ThreadProc+0xa3

081dfc80 7627b2b1 08e15488 0902c6f8 081dfd04 SHELL32!CShellTaskThread::s_ThreadProc+0x1b

081dfc90 775ad897 17a748d0 7f78dcce 006287e0 SHLWAPI!ExecuteWorkItemThreadProc+0xe

081dfd04 775b0846 17a748d0 0902c6f8 7f78dfae ntdll!RtlpTpWorkCallback+0x11d

081dfe64 75d6ed6c 006287d8 081dfeb0 775e377b ntdll!TppWorkerThread+0x572

081dfe70 775e377b 006287d8 7f78df7a 00000000 kernel32!BaseThreadInitThunk+0xe

081dfeb0 775e374e 775b03e9 006287d8 00000000 ntdll!__RtlUserThreadStart+0x70

081dfec8 00000000 775b03e9 006287d8 00000000 ntdll!_RtlUserThreadStart+0x1b

 

STACK_COMMAND:  kb

THREAD_SHA1_HASH_MOD_FUNC:  d88f93992d675d51da0b6bb80cf72e33554c1a0c

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  c610c8843f28f8a4ba858fe9abfac4088a42a8e1

THREAD_SHA1_HASH_MOD:  a1e2774180770312a6efc2e8cf99f51b102d2427

FOLLOWUP_IP: 

MyDrv+8e17

95224e17 8945f8          mov     dword ptr [ebp-8],eax

FAULT_INSTR_CODE:  33f84589

SYMBOL_STACK_INDEX:  4

SYMBOL_NAME:  MyDrv+8e17

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: MyDrv

IMAGE_NAME:  MyDrv.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  5281e1ad

FAILURE_BUCKET_ID:  0xC5_2_MyDrv+8e17

BUCKET_ID:  0xC5_2_MyDrv+8e17

PRIMARY_PROBLEM_CLASS:  0xC5_2_MyDrv+8e17

TARGET_TIME:  2014-02-03T12:10:15.000Z

OSBUILD:  7601

OSSERVICEPACK:  1000

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 7

OSEDITION:  Windows 7 WinNt (Service Pack 1) TerminalServer SingleUserTS

OS_LOCALE:  

USER_LCID:  0

OSBUILD_TIMESTAMP:  2013-01-05 11:46:00

BUILDDATESTAMP_STR:  130104-1431

BUILDLAB_STR:  win7sp1_gdr

BUILDOSVER_STR:  6.1.7601.18044.x86fre.win7sp1_gdr.130104-1431

ANALYSIS_SESSION_ELAPSED_TIME: 186c

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:0xc5_2_MyDrv+8e17

FAILURE_ID_HASH:  {9dc76993-d915-564c-f684-d2b01a978dd4}

Followup:     MachineOwner

---------


!analyze 내용의 콜 스택 아래 부분을 보면 FAILURE_BUCKET_ID: 0xC5_2_MyDrv+8e17 내용이 있다.

이 내용만 보면 MyDrv로 인해 문제가 발생한 것으로 오해하기 쉽다. 앞서 설명했지만 WinDbg에서는 nt 커널 모듈을 제외한 마지막 모듈을 보여주기 때문에 범인이 아닐 가능성도 있다.

물론 MyDrv가 범인일 수도 있기 때문에 철저한 분석을 통해 진짜 범인을 찾아보자.

우선 analyze 에서 시키는 대로 .trap 명령을 통해 문제가 발생한 부분으로 컨텍스트를 설정해보자.

kd> .trap 0xffffffffb683f62c

ErrCode = 00000002

eax=00000000 ebx=835706c0 ecx=8b6ac9f0 edx=86b117b0 esi=83570840 edi=835706c4

eip=8355a4c1 esp=b683f6a0 ebp=b683f6e8 iopl=0         nv up ei pl zr na pe nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010246

nt!ExAllocatePoolWithTag+0x4b7:

8355a4c1 897004          mov     dword ptr [eax+4],esi ds:0023:00000004=????????


nt!ExAllocatePoolWithTag+0x4b7 시점에 mov dword ptr [eax+4],esi 명령을 수행하다 eax 0이라 eax+4 00000004 주소에 접근했다.

그리고 00000004는 유효하지 않은 주소여서 문제가 발생했다.

그렇다면 문제 발생 시점의 앞 부분을 분석해서 eax 0이 설정된 원인을 찾아야 한다.

먼저 kv 명령으로 콜 스택을 확인해서 문제 발생 상황을 살펴보자.

kd> kv

  *** Stack trace for last set context - .thread/.cxr resets it

 # ChildEBP RetAddr  Args to Child              

 00 b683f6e8 83ba6aa1 00000000 00000040 7843464d nt!ExAllocatePoolWithTag+0x4b7// 2) BSOD 발생

01 b683f700 83ba8091 8ae62bf8 8bbdc820 869f1530 fltmgr!ExAllocateFromPagedLookasideList+0x27 (FPO: [Non-Fpo])

02 b683f718 95224e17 86a2aaf8 00000010 00000010 fltmgr!FltAllocateContext+0xa9 (FPO: [Non-Fpo])

03 b683f73c 952251b6 8bbdc820 b683f79c 86e48320 MyDrv+0x8e17  // 1) 필터매니저에게 컨텍스트 메모리 할당 요청

04 b683f758 952252cc 8bbdc820 b683f79c 86e48320 MyDrv+0x91b6

05 b683f77c 83ba7aeb 8bbdc820 b683f79c b683f7c8 MyDrv+0x92cc

06 b683f7e8 83baa9f0 b683f82c d39a3dd8 00000000 fltmgr!FltpPerformPreCallbacks+0x34d (FPO: [Non-Fpo])

07 b683f800 83bbe1fe b683f82c 83bc1f3c 00000000 fltmgr!FltpPassThroughInternal+0x40 (FPO: [Non-Fpo])

08 b683f814 83bbe8b7 b683f82c d39a3dd8 86b76f80 fltmgr!FltpCreateInternal+0x24 (FPO: [Non-Fpo])

09 b683f858 83470c0e 86e1c590 86e0f7e8 8b67de58 fltmgr!FltpCreate+0x2c9 (FPO: [Non-Fpo])

0a b683f870 b65ddffa 86b76f80 8b67de58 86b76f80 nt!IofCallDriver+0x63

0b b683f900 83470c0e 8b67de58 d39a3dd8 86b76fdc SomeDrv+0x5ffa

0c b683f918 836803ee a90c97a7 b683fac0 00000000 nt!IofCallDriver+0x63

0d b683f9f0 8365fc1e 86d4ae20 85ebb9c8 89a08d20 nt!IopParseDevice+0xee6

0e b683fa6c 83670030 00000000 b683fac0 00000040 nt!ObpLookupObjectName+0x4fa

0f b683fac8 83666b0e 081df704 85ebb9c8 00000001 nt!ObOpenObjectByName+0x165

10 b683fb44 8366cc94 179b0980 00020000 081df704 nt!IopCreateFile+0x673

11 b683fb8c b65b9725 179b0980 00020000 081df704 nt!NtOpenFile+0x2a

12 b683fc14 8347789a 179b0980 00020000 081df704 BadDrv+0x1725

13 b683fc14 775c7094 179b0980 00020000 081df704 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ b683fc34)

... ...


1)번에서 미니필터로 동작 중인 MyDrv가 필터매니저(fltmgr)에게 메모리를 할당 받으려는 과정 중에 문제가 발생했다.

문제가 발생한 함수인 ExAllocatePoolWithTag를 디스어셈블링해서 eax가 손상된 원인을 찾아보자.

kd> u ExAllocatePoolWithTag+0x462 L18

nt!ExAllocatePoolWithTag+0x45d:

8355a467 ff742424        push    dword ptr [esp+24h]

8355a46b e8f8efffff      call    nt!MiAllocatePoolPages (83559468)

8355a470 8bf0            mov     esi,eax

8355a472 85f6            test    esi,esi

8355a474 0f854e020000    jne     nt!ExAllocatePoolWithTag+0x6bd (8355a6c8)

8355a47a ff442418        inc     dword ptr [esp+18h]

8355a47e 837c241801      cmp     dword ptr [esp+18h],1

8355a483 0f8504020000    jne     nt!ExAllocatePoolWithTag+0x682 (8355a68d)

8355a489 f705b4305a8300020000 test dword ptr [nt!ExpPoolFlags (835a30b4)],200h

8355a493 0f84f4010000    je      nt!ExAllocatePoolWithTag+0x682 (8355a68d)

8355a499 50              push    eax

8355a49a 53              push    ebx

8355a49b e8c0110000      call    nt!ExDeferredFreePool (8355b660)

8355a4a0 e9e0feffff      jmp     nt!ExAllocatePoolWithTag+0x37b (8355a385)

8355a4a5 8b0e            mov     ecx,dword ptr [esi]    // 4) ecx esi 값을 설정

8355a4a7 8b4104          mov     eax,dword ptr [ecx+4]

8355a4aa 3bc6            cmp     eax,esi

8355a4ac 0f85c8010000    jne     nt!ExAllocatePoolWithTag+0x670 (8355a67a)

8355a4b2 8b5604          mov     edx,dword ptr [esi+4]

8355a4b5 3932            cmp     dword ptr [edx],esi

8355a4b7 0f85bd010000    jne     nt!ExAllocatePoolWithTag+0x670 (8355a67a)

8355a4bd 8b01            mov     eax,dword ptr [ecx]    // 3) eax ecx 값을 설정

8355a4bf 8906            mov     dword ptr [esi],eax    // 2) esi eax 값을 설정

8355a4c1 897004          mov     dword ptr [eax+4],esi  // 1) eax+4 esi 값을 설정하다 문제 발생


1)~4) 순서는 분석 흐름으로 실제 문제가 발생한 부분부터 거꾸로 확인한 순서이다. 문제가 발생한 1)번부터 확인해보면 결국 eax 4)번에서 esi를 통해 ecx로 설정된 값이다.

2)번을 보면 esieax 값을 넣고 있다. 한 번 확인해보자.

kd> r esi

Last set context:

esi=83570840 


esi 값인 83570840도 확인해보자.

kd> dd 83570840 L1

83570840  00000000


esi 0이 설정되어 있다. 3)번에서는 eaxecx 값을 넣고 있으니 ecx도 살펴보자.

kd> r ecx

Last set context:

ecx=8b6ac9f0


ecx 값인 8b6ac9f0도 확인해야 한다.

kd> dd 8b6ac9f0 L1

8b6ac9f0  00000000


ecx 역시 0이 설정되어 있다.

마지막으로 4)번에서 ecx를 설정한 esi를 살펴보자.

kd> r esi

Last set context:

esi=83570840


esi에 있는 83570840 값을 이번에는 ln 명령으로 커널의 특정 주소는 아닌지 확인해보자.

kd> ln 83570840

Browse module

Set bu breakpoint

(835706c0)   nt!NonPagedPoolDescriptor+0x180 


ln 명령으로 확인해보니 esi NonPagedPoolDescriptor 영역으로 확인된다. 그렇다면 풀 디스크립터인 esi에서 가져온 ecx 값은 풀 주소일 가능성이 높고, 이 주소를 누군가 0으로 손상시켰을 가능성이 의심된다.

esi 값인 83570840 NonPagedPoolDescriptor 주소인 835706c0 + 0x180 위치다. 디스크립터 시작 주소인 835706c0 POOL_DESCRIPTOR 구조체에 넣어 확인해보자.

kd> dt _POOL_DESCRIPTOR 835706c0

nt!_POOL_DESCRIPTOR

   +0x000 PoolType         : 0 ( NonPagedPool )

   +0x004 PagedLock        : _KGUARDED_MUTEX

   +0x004 NonPagedLock     : 0xb683f6d0

   +0x040 RunningAllocs    : 0x262521c

   +0x044 RunningDeAllocs  : 0x25e6553

   +0x048 TotalBigPages    : 0x3d8c

   +0x04c ThreadsProcessingDeferrals : 0

   +0x050 TotalBytes       : 0x5a47150

   +0x080 PoolIndex        : 0

   +0x0c0 TotalPages       : 0x2d28

   +0x100 PendingFrees     : 0x89895920  -> 0x8ba5a6e0 Void

   +0x104 PendingFreeDepth : 6

   +0x140 ListHeads        : [512] _LIST_ENTRY [ 0x83570800 - 0x83570800 ] 


0x140 에는 메모리 풀 주소들이 연결되어 있는 LIST_ENTRY 의 배열이 위치한다.

ecx 값이 디스크립터 시작 주소로부터 +0x180 위치라는 말은 0x140 위치의 ListHeads 필드로부터 +0x40 떨어진 위치라는 의미다. 따라서 계산을 통해 주소를 구할 수 있다.

kd> dt _LIST_ENTRY /v

ntdll!_LIST_ENTRY

struct _LIST_ENTRY, 2 elements, 0x8 bytes

   +0x000 Flink            : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes

   +0x004 Blink            : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes


LIST_ENTRY 의 크기를 확인하기 위해 dt 명령의 /v 옵션을 설정하니 8바이트라고 나온다. 0x40 / 8 = 8 이므로, 512개의 배열 중 8번째 인덱스 위치의 LIST_ENTRY ecx.

ListHeads필드에 [n] 을 사용하면 인덱스로 접근 가능하다.

kd> dt _POOL_DESCRIPTOR 835706c0 ListHeads[8]

nt!_POOL_DESCRIPTOR

   +0x140 ListHeads    : [8] _LIST_ENTRY [ 0x0 - 0x86b117b0 ]


8번째 인덱스를 확인해보니 LIST_ENTRY Flink가 정확하게 0 으로 확인된다. 이 값이 ecx에 설정된 것이다. 누군가 Flink 에 설정된 주소 값을 0으로 손상시켰음이 분명하다.

ecx가 문제였음이 확실해졌으니 이제 ecx !pool 명령으로 확인해보자

kd> !pool ecx

Pool page 8b6ac9f0 region is Nonpaged pool

8b6ac808 doesn't look like a valid small pool allocation, checking to see

if the entire page is actually part of a large page allocation...

8b6ac808 is not valid pool. Checking for freed (or corrupt) pool

Bad previous allocation size @8b6ac808, last size was 0

***

*** An error (or corruption) in the pool was detected;

*** Attempting to diagnose the problem.

***

*** Use !poolval 8b6ac000 for more details.

 

Pool page [ 8b6ac000 ] is __inVALID.

Analyzing linked list...

 

Scanning for single bit errors...

None found 


풀 헤더가 손상됐다고 나온다. 이런 경우 손상된 주소의 앞 쪽에서 누군가 메모리를 손상시켰을 가능성이 높다.

이제 1000 바이트 정도 앞 쪽 메모리 영역부터 확인해보자.

kd> !pool ecx-1000

Pool page 8b6ab9f0 region is Nonpaged pool

*8b6a0000 : large page allocation, tag is Ddk , size is 0xc808 bytes

Pooltag Ddk  : Default for driver allocated memory (user's of ntddk.h) 


손상된 풀 헤더의 앞에는 8b6a0000 주소부터 c808 바이트 크기로 할당된 꽤나 큰 라지 풀 영역이 존재한다.

풀 태그는 Ddk로 확인된다. Ddk nt 커널의 기본 풀 태그로 메모리 할당시 풀 태그를 지정하지 않으면 모두 이 태그로 할당된다. 따라서 이런 경우 누가 할당한 메모리인지 찾기 어렵다.

왠지 범인을 찾기가 쉽지 않을 것 같은 예감이 든다. 어쨌든 해당 풀의 마지막 부분부터 확인해보자.

kd> db 8b6a0000+c808-20

8b6ac7e8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6a0000 풀의 위치(8b6ac807) ↓

8b6ac7f8  00 00 00 00 00 00 00 00-5c 00 3f 00 3f 00 5c 00  ........\.?.?.\.

다음 시작 위치(8b6ac808)

8b6ac808  43 00 3a 00 5c 00 57 00-69 00 6e 00 64 00 6f 00  C.:.\.W.i.n.d.o.

8b6ac818  77 00 73 00 5c 00 53 00-79 00 73 00 74 00 65 00  w.s.\.S.y.s.t.e.

8b6ac828  6d 00 33 00 32 00 5c 00-00 00 00 00 00 00 00 00  m.3.2.\.........

8b6ac838  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac848  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac858  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

 

풀 시작 주소인 8b6a0000에 풀 크기인 c808을 더하면 8b6ac808인데, 0x20(32) 바이트 앞부터 확인한 내용이다.

Ddk 풀 태그의 라지 풀은 8b6ac808 앞인 8b6ac807 주소까지만 사용 가능하다. 그런데 할당된 영역을 넘어서 "\??\C:\Windows\System32\" 문자열로 덮어 써 버렸다.

이번에는 문제의 ecx 값인 8b6ac9f0까지 다시 확인해보자.

kd> db 8b6a0000+c808-20 L220

8b6ac7e8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac7f8  00 00 00 00 00 00 00 00-5c 00 3f 00 3f 00 5c 00  ........\.?.?.\.

8b6ac808  43 00 3a 00 5c 00 57 00-69 00 6e 00 64 00 6f 00  C.:.\.W.i.n.d.o.

8b6ac818  77 00 73 00 5c 00 53 00-79 00 73 00 74 00 65 00  w.s.\.S.y.s.t.e.

8b6ac828  6d 00 33 00 32 00 5c 00-00 00 00 00 00 00 00 00  m.3.2.\.........

8b6ac838  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac848  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac858  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac868  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac878  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac888  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac898  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac8a8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac8b8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac8c8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac8d8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac8e8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac8f8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac908  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac918  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac928  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac938  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac948  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac958  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac968  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac978  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac988  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac998  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac9a8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac9b8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac9c8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

8b6ac9d8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

↓ ecx 주소(8b6ac9f0)

8b6ac9e8  00 00 00 00 00 00 00 00-00 00 00 00 40 08 57 83  ............@.W.

8b6ac9f8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................ 


덮어 쓴 문자열 뒷 부분부터 ecx 부분까지는 전부 0으로 설정되어 있다. 이제야 ecx 0이 설정된 상황이 이해가 된다. 앞 쪽 라지 풀을 할당한 모듈에서 문자열을 복사하다 뒤에 있는 디스크립터 관련 풀 헤더 영역까지 손상시킨 것이다.

다행히 메모리가 손상된 원인은 확인했지만 큰 난관이 남아있다. 풀 태그가 기본 풀 태그인 Ddk기 때문에 도대체 어떤 모듈이 범인인지 찾기 어렵다.

풀 태그가 없기 때문에 풀 태그 문자열을 가지고 모듈을 찾는 방법은 사용할 수 없기 때문이다.

정작 중요한 범인을 찾지 못해 난감한 상황이 되었다. 이를 어쩐다.

차분하게 분석했던 내용을 다시 살펴보니 이상한 부분이 하나 보인다.

바로 문제를 일으킨 0xc808 바이트 크기의 라지 풀이다

kd> ?c808

Evaluate expression: 51208 = 0000c808


해당 풀을 할당한 모듈의 입장에서 한 번 생각해 보았다.

무슨 일로 51,208 바이트나 되는 풀을 할당했을까? 이 정도 크기면 뭔가 임시로 할당했다 해제하는 메모리가 아니라 버퍼 같은 용도로 크게 할당해 놓고 주기적으로 사용하는 메모리가 아닐까?

아마도 버퍼 목적의 메모리라면 해당 버퍼 주소를 전역 변수 어딘가에 설정해놓지 않았을까?

이런 생각의 흐름을 통해 다음과 같은 코드를 상상해 보았다.

// 전역 작업 버퍼

PVOID g_pWorkBuffer;

 

VOID

InitBuffer()

{

        // 이렇게 태그를 지정하지 않으면 ‘Ddk’ 할당된다.

g_pWorkBuffer = ExAllocatePool(NonPagedPool, 0xc808);

... ...

}

 

VOID

SaveString(

IN PWSTR pszString,

IN ULONG cbString

)

{

memcpy(g_pWorkbuffer, pszString, cbString);

... ...

}


g_pWorkBuffer 같은 전역 변수가 있다면 라지 풀의 시작 주소인 8b6a0000 을 할당 받아 갖고 있을 가능성이 있다.

이를 검증하기 위해 모듈에서 풀 태그 문자열을 찾을 때 썼던 방법을 응용해보자. 풀 태그 문자열 대신 풀 주소 값인 8b6a0000을 넣어 보는 것이다.

메모리에 있는 문자열이 아닌 바이트 값을 검색하려면 문자열 검색 옵션인 -a는 설정하지 않아야 한다.

또 한 가지는 8b6a0000을 그대로 입력하면 안 된다. 반대로 한 글자 씩 ‘00 00 6a 8b’로 입력해야 한다. DWORD로 표시되는 8b6a0000 값은 실제 메모리에는 반대로 저장되어 있기 때문이다(이를 리틀 엔디언 방식이라고 한다. 이해가 안 된다면 리틀 엔디언과 빅 엔디언의 차이를 찾아보자).

kd> !for_each_module s @#Base @#End 00 00 6a 8b

6cc3ff72  00 00 6a 8b c1 6c ff ff-ff ff 00 00 00 00 ff ff  ..j..l..........

b65be3a4  00 00 6a 8b 26 00 00 00-00 00 00 00 01 00 00 00  ..j.&...........


유저 영역 주소인 6cc3ff72를 제외하면 커널 영역 주소로 b65be3a4가 하나 검색된다. 왠지 모르게 등골이 서늘해진다.

정말 예상대로 어떤 모듈의 주소가 맞을까? lmva 명령으로 확인해보자.

kd> lmva b65be3a4

Browse full module list

start    end        module name

b65b8000 b65c7700   BadDrv     (no symbols)           

    Loaded symbol image file: BadDrv.sys

    Image path: \??\C:\Windows\system32\drivers\BadDrv.sys

    Image name: BadDrv.sys

    Browse all global symbols  functions  data

    Timestamp:        Tue Mar 22 11:33:52 2011 (4D880A90)

    CheckSum:         0001DB7F

    ImageSize:        0000F700

    Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4


와우! 라지 풀의 주소인 8b6a0000이 있는 b65be3a4 값은 b65b8000에 위치한 BadDrv 모듈 내의 주소다. 정말 짜릿한 순간이다.

이번에는 b65be3a4 주소가 정확하게 BadDrv 모듈의 어느 부분인지 살펴보자.

!dh 명령을 모듈 시작 주소와 함께 사용하면 PE 헤더 형식으로 내용을 보여준다.

kd> !dh b65b8000

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES

     14C machine (i386)

       5 number of sections

       4D880A90 time date stamp Tue Mar 22 11:33:52 2011

       0 file pointer to symbol table

       0 number of symbols

      E0 size of optional header

     10E characteristics

            Executable

            Line numbers stripped

            Symbols stripped

            32 bit word machine

            OPTIONAL HEADER VALUES

     10B magic #

    6.00 linker version

    6280 size of code

    9200 size of initialized data

       0 size of uninitialized data

    50B9 address of entry point

     280 base of code

         ----- new -----

         00010000 image base

      80 section alignment

      80 file alignment

       1 subsystem (Native)

    5.00 operating system version

    5.00 image version

    1.10 subsystem version

    F700 size of image

     280 size of headers

   1DB7F checksum

   00040000 size of stack reserve

00001000 size of stack commit

00100000 size of heap reserve

00001000 size of heap commit

       0  DLL characteristics

       0 [       0] address [size] of Export Directory

    E600 [      3C] address [size] of Import Directory

    EB80 [     4A8] address [size] of Resource Directory

       0 [       0] address [size] of Exception Directory

       0 [       0] address [size] of Security Directory

    F080 [     4F8] address [size] of Base Relocation Directory

     370 [      1C] address [size] of Debug Directory

       0 [       0] address [size] of Description Directory

       0 [       0] address [size] of Special Directory

       0 [       0] address [size] of Thread Storage Directory

       0 [       0] address [size] of Load Configuration Directory

       0 [       0] address [size] of Bound Import Directory

     280 [      F0] address [size] of Import Address Table Directory

       0 [       0] address [size] of Delay Import Directory

       0 [       0] address [size] of COR20 Header Directory

       0 [       0] address [size] of Reserved Directory

 

       SECTION HEADER #1

   .text name

    5CFE virtual size

     280 virtual address

    5D00 size of raw data

     280 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

       68000020 flags

         Code

         Not Paged

         (no align specified)

         Execute Read

 

         Debug Directories(1)

Type       Size     Address  Pointer

cv           94           0     f700 [Debug data not mapped]

 

SECTION HEADER #2

   .data name

    8680 virtual size      // 데이터 섹션 크기

    5F80 virtual address   // 데이터 섹션 시작 위치

    8680 size of raw data

    5F80 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

       C8000040 flags

         Initialized Data

         Not Paged

         (no align specified)

         Read Write

         SECTION HEADER #3

    INIT name

     556 virtual size

    E600 virtual address

     580 size of raw data

    E600 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

       E2000020 flags

         Code

         Discardable

         (no align specified)

         Execute Read Write

         SECTION HEADER #4

   .rsrc name

     4A8 virtual size

    EB80 virtual address

     500 size of raw data

    EB80 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

       42000040 flags

         Initialized Data

         Discardable

         (no align specified)

         Read Only

         SECTION HEADER #5

  .reloc name

     61E virtual size

    F080 virtual address

     680 size of raw data

    F080 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

       42000040 flags

         Initialized Data

         Discardable

         (no align specified)

         Read Only


확인된 내용을 보면 전역 변수들이 위치하는 데이터 섹션(.data) b65bdf80부터 b65c6600까지다

// 데이터 섹션 시작 위치(virtual address)

kd> ?b65b8000 + 5f80

Evaluate expression: -1235492992 = b65bdf80

 

// 데이터 섹션 크기(virtual size)

kd> ?b65bdf80 + 8680

Evaluate expression: -1235458560 = b65c6600


데이터 섹션의 범위가 b65bdf80 – b65c6600 이므로 찾은 b65be3a4 값은 정확하게 데이터 섹션 내의 주소다. 예상대로 범인인 라지 풀은 BadDrv 모듈 전역 변수에 설정된 버퍼였던 것이다.

이번 예제도 다른 메모리 손상 이슈와 비슷하게 BadDrv 모듈에서 전역 버퍼로 할당한 메모리를 잘못 관리하여 뒷 부분 메모리까지 손상시킨 것이 원인으로 밝혀졌다.

이런 예제를 분석할 때면 어려운 퍼즐을 푸는 것 같은 스릴을 느낀다.

이번 분석의 교훈은 답이 보이지 않을 때는 범인의 입장에서 생각해보자이다. 숨겨진 퍼즐의 답을 찾을 수 있을 것이다.








'Dump Analysis' 카테고리의 다른 글

[0x133] DPC_WATCHDOG_VIOLATION  (0) 2018.07.29
[0xC5] 해제 리스트 손상  (0) 2018.07.19
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 해제된 핸들  (0) 2018.07.09
[0x50] 숨겨진 콜 스택  (0) 2018.07.07
prev"" #1 next