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 티스토리 가입하기!
'BSoD'에 해당되는 글 7건
2018. 7. 29. 23:26

이번 덤프는 윈도우 8과 서버 2012부터 많은 사람들을 괴롭혔던 BugCheck 0x133: DPC_WATCHDOG_VIOLATION 이라는 특이한 오류다.

이 오류는 BugCheck 0x7F_8에 해당하는 커널 스택 오버 플로우와 매우 유사하다. 커널 스택 오버 플로우가 커널 스택에 대한 사용량 문제라면 0x133은 커널 시간에 대한 사용량 문제다. 커널에서는 일반적인 요청보다 좀 더 중요한 일을 처리해야 할 때 DPC(Deferred Procudure Call)/Dispatch라는 인터럽트 레벨에서 동작하게 된다. 문제는 이 DPC/Dispatch 레벨이 스레드 스케쥴링 등의 운영체제 주요 동작보다도 더 높은 레벨이라 DPC/Dispatch 레벨에서 작업이 지연되면 시스템이 느려지거나 행 증상이 발생할 수도 있다. 물론 운영체제만 사용 가능한 인터럽트 레벨이라면 큰 문제가 없겠지만 불행히도 DPC/Dispatch 레벨은 커널 드라이버라면 스핀락(SpinLock) 함수를 통해 쉽게 사용 가능하다. 상황이 이렇다보니 잘못 작성된 커널 드라이버가 스핀락을 획득하고 오랜 시간 반환하지 않으면 시스템이 멈추는 불상사가 발생한다. 이 문제를 해결하기 위해 새로 추가된 오류 코드가 바로 0x133이다. 커널에서는 별도의 감시자(Watchdog)를 통해 DPC/Dispatch 레벨에서의 동작 시간이 정해진 제한 시간(Time Out)을 넘으면 지체없이 이 오류 코드를 발생시킨다. 여러 모듈이 지연시키는 경우도 각각 동작 시간을 누적하여 제한 시간을 넘기면 이 오류 코드가 발생할 수 있다. 따라서 원인 분석을 위해 성능 지연 구간에 대한 별도의 성능 로그를 수집해야 할 수도 있다. 나는 이런 점에서 0x133을 매우 어려운 커널 스택 오버 플로우 이슈 정도로 생각한다.

물론 여기서는 덤프에서 원인 분석이 가능하도록 단일 모듈에서 성능 지연이 발생한 경우를 살펴볼 것이다.

먼저 !analyze -v 명령을 통해 기본 분석부터 시작해보자.

kd> !analyze -v

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

*                                                                             *

*                        Bugcheck Analysis                                    *

*                                                                             *

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

DPC_WATCHDOG_VIOLATION (133)

The DPC watchdog detected a prolonged run time at an IRQL of DISPATCH_LEVEL

or above.

Arguments:

Arg1: 00000001, The system cumulatively spent an extended period of time at

       DISPATCH_LEVEL or above. The offending component can usually be

       identified with a stack trace.

Arg2: 00001e00, The watchdog period.

Arg3: 00000000, cast to nt!DPC_WATCHDOG_GLOBAL_TRIAGE_BLOCK, which contains

       additional information regarding the cumulative timeout

Arg4: 00000000

Debugging Details:

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

DUMP_CLASS: 1

DUMP_QUALIFIER: 401

BUILD_VERSION_STRING:  14393.693.x86fre.rs1_release.161220-1747

SYSTEM_MANUFACTURER:  SAMSUNG ELECTRONICS CO.,LTD

SYSTEM_PRODUCT_NAME:  Samsung Desktop System

SYSTEM_SKU:  System SKUNumber

SYSTEM_VERSION:  CAAAAAAF

BIOS_VENDOR:  SAMSUNG ELECTRONICS CO.,LTD

BIOS_VERSION:  04NS

BIOS_DATE:  08/16/2011

BASEBOARD_MANUFACTURER:  SAMSUNG ELECTRONICS CO.,LTD

BASEBOARD_PRODUCT:  Samsung DeskTop System

BASEBOARD_VERSION:  CAAAAAAA

DUMP_TYPE:  1

BUGCHECK_P1: 1

BUGCHECK_P2: 1e00

BUGCHECK_P3: 0

BUGCHECK_P4: 0

DPC_TIMEOUT_TYPE:  DPC_QUEUE_EXECUTION_TIMEOUT_EXCEEDED

CPU_COUNT: 4

CPU_MHZ: cdd

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 2a

CPU_STEPPING: 7

CPU_MICROCODE: 6,2a,7,0 (F,M,S,R)  SIG: 29'00000000 (cache) 29'00000000 (init)

DEFAULT_BUCKET_ID:  WIN8_DRIVER_FAULT

BUGCHECK_STR:  0x133

PROCESS_NAME:  SMSS.exe

CURRENT_IRQL:  1c

ANALYSIS_SESSION_HOST:  PAUL-PC

ANALYSIS_SESSION_TIME:  06-11-2018 22:34:11.0900

ANALYSIS_VERSION: 10.0.16299.91 amd64fre

LAST_CONTROL_TRANSFER:  from 81b48d4e to 81b284a8

STACK_TEXT: 

d461b7d4 81b48d4e 00000133 00000001 00001e00 nt!KeBugCheckEx

d461b804 81a3b08c 85dc5120 00000002 00000000 nt! ?? ::FNODOBFM::`string'+0x9eae

d461b8d0 820336b7 81a3d64d 00000000 d461ba14 nt!KeClockInterruptNotify+0x36c

d461b8e0 82044cdb 00000002 000000d1 00000000 hal!HalpTimerClockInterruptCommon+0x3f

d461b8e0 81a3d64d 00000002 000000d1 00000000 hal!HalpTimerClockInterrupt+0x1f7

d461b9f0 81ba85d0 c786f3b8 8833d980 83000100 nt!KxWaitForSpinLockAndAcquire+0x1d

d461ba14 81a54003 00000000 830001c8 883125c7 nt!KiAcquireSpinLockInstrumented+0x53

d461ba20 883125c7 0032a582 d461bae0 8831f149 nt!KfAcquireSpinLock+0x33

d461ba2c 8831f149 8a352a0a c786f3b8 00000000 SomeDrv+0x25c7

d461bae0 81ca719d c786f3b8 00000001 00000000 SomeDrv+0xf149

d461bbb8 81ca6f6a 00000000 00000000 042ffdc8 nt!IopXxxControlFile+0x21d

d461bbe4 81b36987 00000300 00000000 00000000 nt!NtDeviceIoControlFile+0x2a

d461bbe4 77ce4d50 00000300 00000000 00000000 nt!KiSystemServicePostCall

042ffde8 00000000 00000000 00000000 00000000 0x77ce4d50

THREAD_SHA1_HASH_MOD_FUNC:  2a46f69c6139803d83a04cedcad275f72ac07d42

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  6f2b762ab8d468cacab44d7f866169a56cdce50c

THREAD_SHA1_HASH_MOD:  bd912fdd2f42d7d45cc2017c378e8c588d451ef1

FOLLOWUP_IP:

SomeDrv+25c7

883125c7 8845fe          mov     byte ptr [ebp-2],al

FAULT_INSTR_CODE:  81fe4588

SYMBOL_STACK_INDEX:  8

SYMBOL_NAME:  SomeDrv+25c7

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: SomeDrv

IMAGE_NAME:  SomeDrv.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  57fde77c

STACK_COMMAND:  .thread ; .cxr ; kb

BUCKET_ID_FUNC_OFFSET:  25c7

FAILURE_BUCKET_ID:  0x133_ISR_SomeDrv!unknown_function

BUCKET_ID:  0x133_ISR_SomeDrv!unknown_function

PRIMARY_PROBLEM_CLASS:  0x133_ISR_SomeDrv!unknown_function

TARGET_TIME:  2017-02-27T00:48:23.000Z

OSBUILD:  14393

OSSERVICEPACK:  0

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 10

OSEDITION:  Windows 10 WinNt TerminalServer SingleUserTS

OS_LOCALE: 

USER_LCID:  0

OSBUILD_TIMESTAMP:  2016-12-21 13:24:07

BUILDDATESTAMP_STR:  161220-1747

BUILDLAB_STR:  rs1_release

BUILDOSVER_STR:  10.0.14393.693.x86fre.rs1_release.161220-1747

ANALYSIS_SESSION_ELAPSED_TIME:  ece

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:0x133_isr_SomeDrv!unknown_function

FAILURE_ID_HASH:  {c26eac98-dd1d-7af7-3055-fbbff800604a}

Followup:     MachineOwner

---------


출력 결과 중 BugCheck 파라미터 부분을 유심히 살펴보면 대략적인 발생 원인을 이해할 수 있다.

Arg1: 00000001, The system cumulatively spent an extended period of time at

       DISPATCH_LEVEL or above. The offending component can usually be

       identified with a stack trace.

Arg2: 00001e00, The watchdog period.


첫 번째 파라미터는 1이며, 시스템이 DISPATCH_LEVEL 이상에서 장시간 동작한 것이 원인이라고 말한다. 스택 추적을 통해 문제를 일으킨 원인을 확인할 수 있다고도 언급한다.

두 번째 파라미터는 감시 중인 제한 시간으로  Tick 단위다. 하나 혹은 복수의 커널 드라이버가 DISPATCH_LEVEL 이상에서 동작한 누적 시간 값이 제한 시간을 넘길 경우 이 오류가 발생한다.

이를 통해 누군가 DISPATCH_LEVEL 이상에서 0x1e00(7,680) Tick 만큼 장시간 동작한 것이 문제의 원인 임을 짐작할 수 있다. 커널에서 성능 지연이 감지되면 시스템을 멈추는 방식이므로 문제 발생 당시 실행 중인 스레드가 범인일 가능성이 높다. 첫 번째 파라미터 설명에서 실행 중인 콜 스택을 살펴보라는 것도 그런 이유에서다.

그렇다면 !thread 명령으로 현재 실행 중인 스레드 정보를 확인해보자.

kd> !thread

THREAD d8e548c0  Cid 2978.2c68  Teb: 0157e000 Win32Thread: 00000000 RUNNING on processor 0

Not impersonating

DeviceMap                 88a03058

Owning Process            d8bc6800       Image:         SMSS.exe

Attached Process          N/A            Image:         N/A

Wait Start TickCount      39290          Ticks: 7680 (0:00:02:00.000)

Context Switch Count      3651           IdealProcessor: 0            

UserTime                  00:00:00.062

KernelTime                00:02:00.046

Win32 Start Address 0x0fb738e0

Stack Init d461bca0 Current d461ba2c Base d461c000 Limit d4619000 Call 00000000

Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5

ChildEBP RetAddr  Args to Child             

d461b7d4 81b48d4e 00000133 00000001 00001e00 nt!KeBugCheckEx

d461b804 81a3b08c 85dc5120 00000002 00000000 nt! ?? ::FNODOBFM::`string'+0x9eae

d461b8d0 820336b7 81a3d64d 00000000 d461ba14 nt!KeClockInterruptNotify+0x36c (FPO: [Non-Fpo])

d461b8e0 82044cdb 00000002 000000d1 00000000 hal!HalpTimerClockInterruptCommon+0x3f (FPO: [0,0,4])

d461b8e0 81a3d64d 00000002 000000d1 00000000 hal!HalpTimerClockInterrupt+0x1f7 (FPO: [0,2] TrapFrame @ d461b978)

d461b9f0 81ba85d0 c786f3b8 8833d980 83000100 nt!KxWaitForSpinLockAndAcquire+0x1d (FPO: [0,0,0])

d461ba14 81a54003 00000000 830001c8 883125c7 nt!KiAcquireSpinLockInstrumented+0x53 (FPO: [Non-Fpo])

d461ba20 883125c7 0032a582 d461bae0 8831f149 nt!KfAcquireSpinLock+0x33 (FPO: [0,0,0])

d461ba2c 8831f149 8a352a0a c786f3b8 00000000 SomeDrv+0x25c7

d461bae0 81ca719d c786f3b8 00000001 00000000 SomeDrv+0xf149

d461bbb8 81ca6f6a 00000000 00000000 042ffdc8 nt!IopXxxControlFile+0x21d (FPO: [Non-Fpo])

d461bbe4 81b36987 00000300 00000000 00000000 nt!NtDeviceIoControlFile+0x2a (FPO: [Non-Fpo])

d461bbe4 77ce4d50 00000300 00000000 00000000 nt!KiSystemServicePostCall (FPO: [0,3] TrapFrame @ d461bc14)

042ffde8 00000000 00000000 00000000 00000000 0x77ce4d50


스레드 정보의 Ticks 필드를 보면 정확하게 7680(0x1e00) Tick이 경과했음을 알 수 있다. 2분의 시간이 경과한 순간 커널이 제한 시간을 다 사용한 것으로 판단하여 0x133을 발생시킨 것이다.

이번에는 k명령으로 스레드의 콜 스택을 분석하며 문제가 발생한 상황을 좀 더 이해해보자.

kd> k

 # ChildEBP RetAddr 

00 d461b7d4 81b48d4e nt!KeBugCheckEx ç 3) 제한 시간이 경과하여 BSOD 발생

01 d461b804 81a3b08c nt! ?? ::FNODOBFM::`string'+0x9eae

02 d461b8d0 820336b7 nt!KeClockInterruptNotify+0x36c

03 d461b8e0 82044cdb hal!HalpTimerClockInterruptCommon+0x3f

04 d461b8e0 81a3d64d hal!HalpTimerClockInterrupt+0x1f7

05 d461b9f0 81ba85d0 nt!KxWaitForSpinLockAndAcquire+0x1d ç 2) 스핀락 획득 시도

06 d461ba14 81a54003 nt!KiAcquireSpinLockInstrumented+0x53

07 d461ba20 883125c7 nt!KfAcquireSpinLock+0x33

08 d461ba2c 8831f149 SomeDrv+0x25c7     ç 1) SomeDrv 모듈에서 스핀락 획득 함수 호출

09 d461bae0 81ca719d SomeDrv+0xf149

0a d461bbb8 81ca6f6a nt!IopXxxControlFile+0x21d

0b d461bbe4 81b36987 nt!NtDeviceIoControlFile+0x2a

0c d461bbe4 77ce4d50 nt!KiSystemServicePostCall

0d 042ffde8 00000000 0x77ce4d50


콜 스택을 보면 1)번에서 SomeDrv 모듈이 스핀락을 얻으려고 시도하고 있다. 하지만 스핀락을 획득하지 못해 2)번에서 nt! KxWaitForSpinLockAndAcquire 함수를 통해 획득하기를 기다리고 있다. 동작 자체로는 아무 문제가 없지만 락 획득을 빨리 하지 못한 것이 문제의 원인이다. 무려 2분 동안 락을 획득하지 못하고 있는 것은 정상적인 상황이 아니다. 하지만 이 문제를 분석하기에 앞서 확인해야할 부분이 하나 있다.

앞서 0x133이 발생하려면 제한 시간 외에도 인터럽트 레벨이 DISPATCH_LEVEL 이상이라는 조건이 있었다. 제한 시간 조건은 스레드의 Ticks 정보를 통해 만족함을 확인했지만 인터럽트 레벨 조건은 아직 확인하지 못했다.

문제 발생 당시 인터럽트 레벨을 어떻게 알 수 있을까? 문제의 콜 스택에서 스핀락을 얻으려는 부분이 보이는가? 커널에서 락이나 이벤트를 사용할 경우 내부에서는 동기화를 위해 인터럽트 레벨을 변경하는 경우가 많이 있다. 그렇다면 nt!KfAcquireSpinLock 함수를 디스어셈블링해서 인터럽트 레벨을 변경하는 부분이 있는지 살펴보면 답을 알 수 있다.

kd> u nt!KfAcquireSpinLock

nt!KfAcquireSpinLock:

81a53fd0 8bff            mov     edi,edi

81a53fd2 53              push    ebx

81a53fd3 56              push    esi

81a53fd4 8bf1            mov     esi,ecx

81a53fd6 ff153891c381    call    dword ptr [nt!_imp__KeRaiseIrqlToDpcLevel (81c39138)]

81a53fdc f60506b9c48121  test    byte ptr [nt!PerfGlobalGroupMask+0x6 (81c4b906)],21h

81a53fe3 8ad8            mov     bl,al

81a53fe5 7515            jne     nt!KfAcquireSpinLock+0x2c (81a53ffc)


다행히 시작 부분에 KeRaiseIrqlToDpcLevel 함수를 통해 인터럽트 레벨을 Dpc/Dispatch로 상승시키는 코드가 확인된다. 0x133을 발생시키는 인터럽트 레벨과 제한 시간 조건 모두를 만족하는 상황이므로 SomeDrv 모듈에서 스핀락을 획득하는 동작이 문제의 발단이다.

이제 오랜 시간 동안 스핀락을 획득하지 못한 원인을 밝혀내야 한다. 먼저 스핀락을 획득하는 함수인 nt!KfAcquireSpinLock 함수의 원형을 살펴보자.

KIRQL

FASTCALL

KfAcquireSpinLock(

    _Inout_ PKSPIN_LOCK SpinLock

    );

 

<wdm.h>

typedef ULONG_PTR KSPIN_LOCK;

typedef KSPIN_LOCK *PKSPIN_LOCK;


KSPIN_LOCK 구조체의 포인터를 파라미터로 전달받고 있다. 뭔가 특별한 락 구조체인 것처럼 보이지만 wdm.h 헤더 파일을 보면 실은 단순한 unsigned long 변수다. 스핀락은 바로 이 변수를 통해 테스트 앤 셋(Test And Set) 방식으로 동기화를 수행한다. 이 방식을 단순하게 설명하면 락 변수 값이 0이면 스핀락 획득시 변수를 1로 설정하고 락 반환시 0으로 복원한다. 이미 1인 상태에서 스핀락 획득을 시도하면 0이 되길 지속적으로 기다린다(루프를 돌면서 기다리는 이 동작 때문에 스핀락이라고 부른다).

다시 본론으로 돌아와서 이번에는 nt! KfAcquireSpinLock 함수에 전달된 KSPIN_LOCK 변수가 정상인지 확인해야 한다. 콜 스택에서 nt!KfAcquireSpinLock 함수를 호출한 1)번 부분을 디스어셈블링해보자.

kd> ub SomeDrv+0x25c7

SomeDrv+0x25b1:

883125b1 cc              int     3

883125b2 8bff            mov     edi,edi

883125b4 55              push    ebp

883125b5 8bec            mov     ebp,esp

883125b7 51              push    ecx

883125b8 c645ff00        mov     byte ptr [ebp-1],0

883125bc b980d93388      mov     ecx,offset SomeDrv+0x2d980 (8833d980) ç 1) 파라미터

883125c1 ff1508e03288    call    dword ptr [SomeDrv+0x1e008 (8832e008)] ç 2) nt!KfAcquireSpinLock 함수 호출


nt!KfAcquireSpinLock 함수 원형을 보면 호출 규약이 FASTCALL 이므로 첫 번째 파라미터는 ecx에 설정된다. 1)번을 보면 SomeDrv+0x2d980 위치의 값을 ecx에 설정하고 있다. 바로 SomeDrv+0x2d980KSPIN_LOCK 구조체 변수의 주소다. 2)번에서 호출된 함수는 ln 명령을 사용하면 nt!KfAcquireSpinLock 함수 임을 쉽게 알 수 있다.

kd> ln poi(8832e008)

Browse module

Set bu breakpoint

 

(81a53fd0)   nt!KfAcquireSpinLock   |  (81a54010)   nt!KfReleaseSpinLock

Exact matches:

    nt!KfAcquireSpinLock (<no parameter info>)


KSPIN_LOCK 구조체 위치인 SomeDrv+0x2d980 주소도 dd 명령으로 확인해보자.

kd> dd SomeDrv+0x2d980 L1

8833d980  00490057


어라? KSPIN_LOCK 구조체 주소인 8833d980에 저장된 값이 뭔가 이상하다. 스핀락 획득 유무에 따라 0 아니면 1이 있어야 하는데 00490057이라는 비정상적인 값이 들어 있다. 왠지 불길한 예감이 든다. 이럴 때는 db 명령을 통해 8833d980 주소의 메모리를 확인해보면 의외의 단서를 찾을 수 있다.

kd> db 8833d980

8833d980  57 00 49 00 4e 00 44 00-4f 00 57 00 53 00 5c 00  W.I.N.D.O.W.S.\.

8833d990  53 00 59 00 53 00 54 00-45 00 4d 00 33 00 32 00  S.Y.S.T.E.M.3.2.

8833d9a0  5c 00 44 00 4e 00 53 00-41 00 50 00 49 00 2e 00  \.D.N.S.A.P.I...

8833d9b0  44 00 4c 00 4c 00 00 00-59 00 53 00 54 00 45 00  D.L.L...Y.S.T.E.

8833d9c0  4d 00 33 00 32 00 5c 00-44 00 4e 00 53 00 41 00  M.3.2.\.D.N.S.A.

8833d9d0  50 00 49 00 2e 00 44 00-4c 00 4c 00 43 00 3a 00  P.I...D.L.L.C.:.

8833d9e0  5c 00 57 00 49 00 4e 00-44 00 4f 00 57 00 53 00  \.W.I.N.D.O.W.S.

8833d9f0  5c 00 53 00 59 00 53 00-54 00 45 00 4d 00 33 00  \.S.Y.S.T.E.M.3.


역시 00490057KSPIN_LOCK 구조체의 정상적인 값이 아닌 어떤 문자열 값의 일부분이다. du 명령으로 약간 앞 쪽을 살펴보면 왠지 전체 문자열을 알 수 있을 것 같다.

kd> du 8833d980-6

8833d97a  "C:\WINDOWS\SYSTEM32\DNSAPI.DLL"


아하! "C:\WINDOWS\SYSTEM32\DNSAPI.DLL"이라는 파일 경로 문자열이 들어 있다. 아무래도 누군가 KSPIN_LOCK 구조체 위치에 이 문자열을 덮어 써버려 메모리를 손상시킨 것 같다. 정말 메모리 손상이 문제의 원인이 맞는지 알려면 스핀락을 획득하는 부분을 조금 더 분석할 필요가 있다. kv 명령으로 콜 스택을 다시 확인해보자.

kd> kv

 # ChildEBP RetAddr  Args to Child             

00 d461b7d4 81b48d4e 00000133 00000001 00001e00 nt!KeBugCheckEx

01 d461b804 81a3b08c 85dc5120 00000002 00000000 nt! ?? ::FNODOBFM::`string'+0x9eae

02 d461b8d0 820336b7 81a3d64d 00000000 d461ba14 nt!KeClockInterruptNotify+0x36c (FPO: [Non-Fpo])

03 d461b8e0 82044cdb 00000002 000000d1 00000000 hal!HalpTimerClockInterruptCommon+0x3f (FPO: [0,0,4])

04 d461b8e0 81a3d64d 00000002 000000d1 00000000 hal!HalpTimerClockInterrupt+0x1f7 (FPO: [0,2] TrapFrame @ d461b978) ç 2) 트랩 프레임

05 d461b9f0 81ba85d0 c786f3b8 8833d980 83000100 nt!KxWaitForSpinLockAndAcquire+0x1d (FPO: [0,0,0]) ç 1) 대기 함수

06 d461ba14 81a54003 00000000 830001c8 883125c7 nt!KiAcquireSpinLockInstrumented+0x53 (FPO: [Non-Fpo])

07 d461ba20 883125c7 0032a582 d461bae0 8831f149 nt!KfAcquireSpinLock+0x33 (FPO: [0,0,0])

08 d461ba2c 8831f149 8a352a0a c786f3b8 00000000 SomeDrv+0x25c7

09 d461bae0 81ca719d c786f3b8 00000001 00000000 SomeDrv +0xf149

0a d461bbb8 81ca6f6a 00000000 00000000 042ffdc8 nt!IopXxxControlFile+0x21d (FPO: [Non-Fpo])

0b d461bbe4 81b36987 00000300 00000000 00000000 nt!NtDeviceIoControlFile+0x2a (FPO: [Non-Fpo])

0c d461bbe4 77ce4d50 00000300 00000000 00000000 nt!KiSystemServicePostCall (FPO: [0,3] TrapFrame @ d461bc14)

0d 042ffde8 00000000 00000000 00000000 00000000 0x77ce4d50


1)nt!KxWaitForSpinLockAndAcquire함수는 스핀락을 획득하기 위해 시도하는 함수다. 2)번에는 트랩 프레임(TrapFrame)이 확인된다. 트랩 프레임이 있다는 것은 직전 1)번 함수 동작에 문제가 있어 인터럽트가 발생했다는 의미다. .trap 명령을 사용해 문제 발생 당시 상황으로 컨텍스트를 복원해보자.

kd> .trap d461b978

ErrCode = 00000000

eax=00490057 ebx=83000100 ecx=8833d980 edx=00000000 esi=520f94a9 edi=8833d980

eip=81a3d64d esp=d461b9ec ebp=d461ba14 iopl=0         nv up ei pl nz na pe nc

cs=0008  ss=0010  ds=8c3b  es=01c8  fs=6b68  gs=ba20             efl=00000206

nt!KxWaitForSpinLockAndAcquire+0x1d:

81a3d64d f390            pause


edi 레지스터에 설정된 8833d980 값을 기억해두자(앞서 확인한 KSPIN_LOCK 구조체 주소다). esi 레지스터 값도 봐두면 좋다. 문제가 발생했던1)번 시점의 동작을 확인하기 위해 nt!KxWaitForSpinLockAndAcquire 함수를 처음부터 살펴봐야한다. 함수가 짧으니 너무 걱정하지 말자.

kd> u nt!KxWaitForSpinLockAndAcquire L14

nt!KxWaitForSpinLockAndAcquire:

81a3d630 8bff            mov     edi,edi

81a3d632 56              push    esi

81a3d633 57              push    edi

81a3d634 8bf9            mov     edi,ecx

81a3d636 33f6            xor     esi,esi ç 1) esi = 0

81a3d638 eb06            jmp     nt!KxWaitForSpinLockAndAcquire+0x10 (81a3d640)

81a3d63a 8d9b00000000    lea     ebx,[ebx]

81a3d640 46              inc     esi ç 2) esi + 1(루프 시작)

81a3d641 853530e0c381    test    dword ptr [nt!HvlLongSpinCountMask (81c3e030)],esi

81a3d647 0f84a2bc1000    je      nt! ?? ::FNODOBFM::`string'+0xa44f (81b492ef)

81a3d64d f390            pause ç 3) 인터럽트 발생 위치(트랩 프레임)

81a3d64f 8b07            mov     eax,dword ptr [edi] ç 4) edi 값을 eax 설정

81a3d651 85c0            test    eax,eax ç 5) eax 값이 0인지 확인

81a3d653 75eb            jne     nt!KxWaitForSpinLockAndAcquire+0x10 (81a3d640) ç 6) 0 아니면 2) 루프 시작 위치로 이동

81a3d655 f00fba2f00      lock bts dword ptr [edi],0

81a3d65a 72e4            jb      nt!KxWaitForSpinLockAndAcquire+0x10 (81a3d640)

81a3d65c 5f              pop     edi

81a3d65d 8bc6            mov     eax,esi

81a3d65f 5e              pop     esi

81a3d660 c3              ret


중요 위치마다 주석이 있어 함수 동작을 이해하는 데 무리가 없을 것이다. 1)번부터 살펴보자. 우선 esi 레지스터는 단순한 카운터 변수다. 2)번을 보면 루프 진입시마다 1씩 증가한다. 4)번에는 edi 값을 eax에 설정하고 5)번에서 eax0인지 확인한다. 참고로 ediKSPIN_LOCK 구조체 변수의 주소였다.

eax에 설정한 edi 값을 확인해보자.

kd> dd edi L1

8833d980  00490057


edi00490057 값이 있으므로 eax에도 00490057 값이 설정된다(앞서 00490057 값은 문자열의 일부였다). 6)번에서는 eax 값이 0이 아니므로 루프 시작 위치인 2)번으로 다시 이동한다. eax는 이미 문자열로 덮어 써져 0이 될 수 없으므로 계속 이 루틴이 반복된다. 즉 무한 루프 상태다. 과연 이 루프는 얼마나 수행됐을까? 카운터 변수인 esi 레지스터 값을 확인해보자.

kd> r esi

Last set context:

esi=520f94a9


맙소사! 0x520f94a9(1,376,752,809)라는 엄청나게 큰 값이 설정되어 있다. 무려 13억번이나 루프가 돌고 있었던 것이다! 그리고는 결국 제한 시간이 다 되어 3)번 위치의 pause 명령에서 0x133이 발생했다. 이제야 모든 것이 명확해졌다. 누군가 스핀락 변수를 문자열로 손상시켜 스핀락 획득 함수 내부에서 무한 루프에 빠져 버린 것이 원인이다(물론 대부분의 경우 누군가는 스핀락 사용 중인 SomeDrv 자신이다).

스핀락을 획득하려고 인터럽트 레벨을 상승시키고 무한 루프에 빠졌으니 DPC_WATCHDOG_VIOLATION(0x133) 버그체크가 발생하는 것은 어찌보면 당연하다.

여기서는 메모리 손상에 의한 0x133을 소개했지만 훨씬 까다로운 경우도 있다. 바로 스핀락을 획득하고 수행한 작업들 하나하나가 조금씩 성능 지연을 일으켜 누적에 의해 제한 시간을 넘기는 경우다. 단일 모듈이 아닌 다수의 모듈에서 지연이 발생하는 경우라면 더욱 골치아프다. 이 경우 별도의 성능 측정을 통해 모듈별로 매 작업마다 성능 지연이 발생하는 구간을 찾아내야해서 굉장히 어렵다. 이런 이유로 스핀락을 획득하고 시간 걸리는 작업은 아예 하지 않는 것이 정신 건강에 이롭다. 주로 다량의 엔트리가 삽입된 리스트를 검색할 때 성능 지연이 발생하는데 해시 테이블 등으로 자료 구조를 변경하거나 리스트에 삽입되는 최대 개수를 제한하는 등의 수정이 필요할 수 있다. 물론 가장 좋은 것은 개발 초기부터 이런 문제를 예상하며 성능을 고려한 설계를 하는 것이다.

앞으로 이 오류 코드가 발생한다면 이것 하나만 기억하자. 스핀락과 연관된 코드를 살펴보라!”



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

[Hang] CPU 과점유  (0) 2018.08.13
[0xC5] 해제 리스트 손상  (0) 2018.07.19
[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 해제된 핸들  (0) 2018.07.09
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
2018. 7. 12. 23:14

이번 덤프는 BugCheck 0x1A: MEMORY_MANAGEMENT다.

0x1A는 커널에서 메모리 주소를 관리하는 과정 중에 문제가 생기면 발생한다. 다양한 원인이 있을 수 있는데, 예를 들어 메모리 관리자가 특정 메모리 주소를 변환하려고 페이지 테이블 엔트리(PTE)를 참조할 때 엔트리 손상이 감지되면 이 오류 코드를 발생시킨다.

대부분 커널 내부 코드에서 발생하기 때문에 분석 자체가 굉장히 어렵고 불가능한 경우도 많다. 물론 시작도 안하고 포기할 수는 없으니 일단 시작해보자.

kd> !analyze -v

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

*      *

*      Bugcheck Analysis   *

*      *

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

MEMORY_MANAGEMENT (1a)

# Any other values for parameter 1 must be individually examined.

Arguments:

Arg1: 00041287, An illegal page fault occurred while holding working set

synchronization.

Parameter 2 contains the referenced virtual address.

Arg2: 34333231

Arg3: 00000000

Arg4: 00000000

Debugging Details:

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

BUGCHECK_STR: 0x1a_41287

DEFAULT_BUCKET_ID: WIN7_DRIVER_FAULT

PROCESS_NAME: WerFault.exe

CURRENT_IRQL: 0

ANALYSIS_VERSION: 6.3.9600.16384 (debuggers(dbg).130821-1623) amd64fre

TRAP_FRAME: aa9ff594 -- (.trap 0xffffffffaa9ff594)

ErrCode = 00000000

eax=34333231 ebx=85c5c000 ecx=29a00000 edx=0f349000 esi=00000000 edi=831a7100

eip=830d8f0f esp=aa9ff608 ebp=aa9ff618 iopl=0 nv up ei pl nz na po nc

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

nt!MiAllocateAccessLog+0x54:

830d8f0f 3930 cmp dword ptr [eax],esi

ds:0023:34333231=????????

Resetting default scope

LAST_CONTROL_TRANSFER: from 8307ca78 to 830c99ad

STACK_TEXT:

aa9ff57c 8307ca78 00000000 34333231 00000000 nt!MmAccessFault+0x104

aa9ff57c 830d8f0f 00000000 34333231 00000000 nt!KiTrap0E+0xdc

aa9ff618 830c111e c0699800 84a94e2c 842f4804 nt!MiAllocateAccessLog+0x54

aa9ff664 830c7907 c0699800 00000000 85a9b630 nt!MiLogPageAccess+0x40

aa9ff8d4 8326b0c4 d3300000 cdf693f0 00000000 nt!MmUnmapViewInSystemCache+0x1c1

aa9ff90c 830c97b5 85a9b630 85e8f5a8 00000000 nt!CcUnmapVacb+0x18c

aa9ff94c 830c76ff 01e8f5a8 831a7400 00000001 nt!CcUnmapVacbArray+0x292

aa9ff96c 830c7d9c 831a7400 00000000 85e8f5a8 nt!CcUnmapAndPurge+0x2e

aa9ff988 830c7514 00000001 85de1f80 00000000 nt!CcDeleteSharedCacheMap+0x82

aa9ff9bc 830e74a6 aa9ff9ec 812c5e91 01000000 nt!CcWriteBehind+0x715

aa9ffa1c 83261c3f 87d9af80 00000000 00000000 nt!CcWaitForUninitializeCacheMap+0x15e

aa9ffb3c 832613c9 aa9ffb90 00000007 00000000 nt!MmCreateSection+0x339

aa9ffbb0 936eef93 0010d600 00000007 00000000 nt!NtCreateSection+0x16e

aa9ffc10 83079896 0010d600 00000007 00000000 SomeDrv+0xdf93

aa9ffc10 76df70f4 0010d600 00000007 00000000 nt!KiSystemServicePostCall

0010d720 00000000 00000000 00000000 00000000 0x76df70f4

STACK_COMMAND: kb

FOLLOWUP_IP:

SomeDrv+df93

936eef93 8bf0 mov esi,eax

SYMBOL_STACK_INDEX: d

SYMBOL_NAME: SomeDrv+df93

FOLLOWUP_NAME: MachineOwner

MODULE_NAME: SomeDrv

IMAGE_NAME: SomeDrv.sys

DEBUG_FLR_IMAGE_TIMESTAMP: 53268cfa

FAILURE_BUCKET_ID: 0x1a_41287_SomeDrv+df93

BUCKET_ID: 0x1a_41287_SomeDrv+df93

ANALYSIS_SOURCE: KM

FAILURE_ID_HASH_STRING: km:0x1a_41287_SomeDrv+df93

FAILURE_ID_HASH: {385bcdbd-2174-4449-cfff-dcc030b0a015}

Followup: MachineOwner

---------


오류 코드 0x1A만 보면 마음이 무겁다. 분석도 어렵고 고통스러울 뿐 아니라 들인 시간에 비해 소득이 없는 경우가 많기 때문이다.

경험상 커널 자체적인 문제보다 외부 모듈에서 하필 메모리 주소 관리 영역을 깨 버려 발생한 경우가 대부분인데 커널 내부 깊숙한 곳에서 오류가 발생하기 때문에 메모리 손상에 대한 증거가 남아 있지 않는 경우가 많다.

그래도 분석이 가능한 경우도 있으니 BugCode의 파라미터 정보부터 살펴보자.

Arg1: 00041287, An illegal page fault occurred while holding working set synchronization. Parameter 2 contains the referenced virtual address.

Arg2: 34333231


Arg1 값은 세부 오류 코드로 다양한 값이 존재하는데 도움말(F1)의 검색 탭에서 Bug Check 0x1A 로 검색하면 이 파라미터 값이 의미하는 바를 알 수 있다.

워킹셋 동기화를 잡고 있는 상태에서 접근한 페이지 정보에 문제가 있었는데, Arg2에 문제를 일으킨 주소가 34333231이라고 말해준다.

물론, 분석에 별 도움은 되지 않는다. 단지 34333231이라는 값이 정상적인 주소 값 형태가 아니기 때문에 누군가가 또 메모리를 손상 시켰으리라 의심할 따름이다.

analyze 결과의 아래 FAILURE_BUCKET_ID를 보면 0x1a_41287_SomeDrv+df93이 있다. SomeDrv로 인해 문제가 발생한 것처럼 보여주고 있다.

나는 이 정보도 크게 신뢰하지 않는다. 왜냐하면 WinDbg는 단지 콜 스택에서 nt 커널 모듈을 제외하고 마지막 모듈을 보여주기 때문이다. 당연하게도 메모리 손상 이슈에서는 전혀 다른 모듈이 범인인 경우가 매우 많다.

언제나처럼 .trap 명령을 사용해서 문제가 발생한 부분으로 컨텍스트 정보를 설정해보자.

kd> .trap 0xffffffffaa9ff594

ErrCode = 00000000

eax=34333231 ebx=85c5c000 ecx=29a00000 edx=0f349000 esi=00000000 edi=831a7100

eip=830d8f0f esp=aa9ff608 ebp=aa9ff618 iopl=0 nv up ei pl nz na po nc

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

nt!MiAllocateAccessLog+0x54:

830d8f0f 3930 cmp dword ptr [eax],esi

ds:0023:34333231=????????


eax에 있는 34333231 주소가 접근할 수 없는 영역이라 문제가 발생했다고 한다.

이제 kv 명령어로 파라미터를 포함한 콜 스택을 확인해보자.

kd> kv

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

# ChildEBP RetAddr Args to Child

00 aa9ff618 830c111e c0699800 84a94e2c 842f4804 nt!MiAllocateAccessLog+0x54

01 aa9ff664 830c7907 c0699800 00000000 85a9b630 nt!MiLogPageAccess+0x40

02 aa9ff8d4 8326b0c4 d3300000 cdf693f0 00000000 nt!MmUnmapViewInSystemCache+0x1c1

03 aa9ff90c 830c97b5 85a9b630 85e8f5a8 00000000 nt!CcUnmapVacb+0x18c

04 aa9ff94c 830c76ff 01e8f5a8 831a7400 00000001 nt!CcUnmapVacbArray+0x292

05 aa9ff96c 830c7d9c 831a7400 00000000 85e8f5a8 nt!CcUnmapAndPurge+0x2e

06 aa9ff988 830c7514 00000001 85de1f80 00000000 nt!CcDeleteSharedCacheMap+0x82

07 aa9ff9bc 830e74a6 aa9ff9ec 812c5e91 01000000 nt!CcWriteBehind+0x715

08 aa9ffa1c 83261c3f 87d9af80 00000000 00000000 nt!CcWaitForUninitializeCacheMap+0x15e

09 aa9ffb3c 832613c9 aa9ffb90 00000007 00000000 nt!MmCreateSection+0x339

0a aa9ffbb0 936eef93 0010d600 00000007 00000000 nt!NtCreateSection+0x16e

0b aa9ffc10 83079896 0010d600 00000007 00000000 SomeDrv+0xdf93

0c aa9ffc10 76df70f4 0010d600 00000007 00000000 nt!KiSystemServicePostCall (FPO: [0,3] TrapFrame @ aa9ffc34)

0d 0010d720 00000000 00000000 00000000 00000000 0x76df70f4


정말이지 nt 커널 함수만 잔뜩 있는 이런 콜 스택을 보면 한숨이 절로 나온다.

nt!MiAllocateAccessLog+0x54에서 문제가 발생했으니 여기 안에서 메모리 손상과 관련된 단서가 발견되기를 기대해보자.

다음은 nt!MiAllocateAccessLog+0x54 부분을 디스어셈블링한 내용이다.

kd> u nt!MiAllocateAccessLog L1d

nt!MiAllocateAccessLog:

830d8ebb 8bff mov edi,edi

830d8ebd 55 push ebp

830d8ebe 8bec mov ebp,esp

830d8ec0 51 push ecx

830d8ec1 51 push ecx

830d8ec2 8b0d80b41683 mov ecx,dword ptr [nt!MmAvailablePages(8316b480)]

830d8ec8 b800040000 mov eax,400h

830d8ecd 53 push ebx

830d8ece 56 push esi

830d8ecf 3bc8 cmp ecx,eax

830d8ed1 721d jb nt!MiAllocateAccessLog+0x35 (830d8ef0)

830d8ed3 8b0d00b51683 mov ecx,dword ptr [nt!MmResidentAvailablePages(8316b500)]

830d8ed9 3bc8 cmp ecx,eax

830d8edb 7c13 jl nt!MiAllocateAccessLog+0x35 (830d8ef0)

830d8edd e84badffff call nt!MI_FREE_NON_PAGED_POOL_PAGES_LEFT(830d3c2d)

830d8ee2 c745fc00100000 mov dword ptr [ebp-4],1000h

830d8ee9 3d00080000 cmp eax,800h

830d8eee 7307 jae nt!MiAllocateAccessLog+0x3c (830d8ef7)

830d8ef0 c745fc00020000 mov dword ptr [ebp-4],200h

830d8ef7 8b5f08 mov ebx,dword ptr [edi+8]

830d8efa 33f6 xor esi,esi

830d8efc 3bde cmp ebx,esi

830d8efe 741e je nt!MiAllocateAccessLog+0x63 (830d8f1e)

830d8f00 817dfc00020000 cmp dword ptr [ebp-4],200h

830d8f07 8b03 mov eax,dword ptr [ebx] // 2) ebx eax

830d8f09 7408 je nt!MiAllocateAccessLog+0x58 (830d8f13)

830d8f0b 3bc6 cmp eax,esi

830d8f0d 740f je nt!MiAllocateAccessLog+0x63 (830d8f1e)

830d8f0f 3930 cmp dword ptr [eax],esi // 1) eax 설정된 값과 esi 비교하다 문제 발생


1) eax에 있던 값은 34333231이며 유효하지 않은 주소 값이다. cmp dword ptr [eax],esi 명령으로 eax에 있는 34333231 주소에서 값을 읽으려다 문제가 발생했다. eax 레지스터 내용을 확인해보자.

kd> r eax

Last set context:

eax=34333231

 

kd> dd eax L1

34333231 ????????


34333231??로 접근할 수 없는 영역이다. eax34333231이 설정된 원인을 찾기 위해 약간 앞 쪽 코드를 살펴보면 2)번에서 ebx를 통해 설정하고 있다.

ebx를 한 번 살펴보자.

kd> r ebx

Last set context:

ebx=85c5c000

 

kd> dd 85c5c000 L1

85c5c000 34333231


ebx에는 85c5c000 값이 있고 85c5c000에 찾고 있던 34333231 값이 담겨 있다.

85c5c000은 값만 보면 유효한 주소 값 형태로 보인다. 일반적으로 정상적인 주소 값의 경우 xxxxxxx0, xxxxxxx8로 끝난다.

유효한 주소 값은 스택 상의 주소나 동적 할당된 메모리 혹은 모듈 내의 주소인 경우가 많다.

!thread 명령으로 현재 스레드 정보를 보면 문제가 발생한 프로세스 및 현재 스레드의 스택 정보를 알 수 있다.

kd> !thread

THREAD 88eac540 Cid 0d10.0d14 Teb: 7ffdf000 Win32Thread: fd601700 RUNNING on processor 2

Not impersonating

DeviceMap ae1d7a28

Owning Process 890b22a8 Image: WerFault.exe

Attached Process N/A Image: N/A

Wait Start TickCount 6552 Ticks: 0

Context Switch Count 9853 IdealProcessor: 3

UserTime 00:00:00.015

KernelTime 00:00:05.116

Win32 Start Address 0x006f80c7

Stack Init aa9ffed0 Current aa9ff6a8 Base aaa00000 Limit aa9fd000 Call 0

Priority 8 BasePriority 7 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5

ChildEBP RetAddr Args to Child

aa9ff57c 8307ca78 00000000 34333231 00000000 nt!MmAccessFault+0x104

aa9ff57c 830d8f0f 00000000 34333231 00000000 nt!KiTrap0E+0xdc (FPO: [0,0] TrapFrame @ aa9ff594)

aa9ff618 830c111e c0699800 84a94e2c 842f4804 nt!MiAllocateAccessLog+0x54

aa9ff664 830c7907 c0699800 00000000 85a9b630 nt!MiLogPageAccess+0x40

aa9ff8d4 8326b0c4 d3300000 cdf693f0 00000000 nt!MmUnmapViewInSystemCache+0x1c1

aa9ff90c 830c97b5 85a9b630 85e8f5a8 00000000 nt!CcUnmapVacb+0x18c

aa9ff94c 830c76ff 01e8f5a8 831a7400 00000001 nt!CcUnmapVacbArray+0x292

aa9ff96c 830c7d9c 831a7400 00000000 85e8f5a8 nt!CcUnmapAndPurge+0x2e

aa9ff988 830c7514 00000001 85de1f80 00000000 nt!CcDeleteSharedCacheMap+0x82

aa9ff9bc 830e74a6 aa9ff9ec 812c5e91 01000000 nt!CcWriteBehind+0x715

aa9ffa1c 83261c3f 87d9af80 00000000 00000000 nt!CcWaitForUninitializeCacheMap+0x15e

aa9ffb3c 832613c9 aa9ffb90 00000007 00000000 nt!MmCreateSection+0x339

aa9ffbb0 936eef93 0010d600 00000007 00000000 nt!NtCreateSection+0x16e

aa9ffc10 83079896 0010d600 00000007 00000000 SomeDrv+0xdf93

aa9ffc10 76df70f4 0010d600 00000007 00000000 nt!KiSystemServicePostCall (FPO:[0,3] TrapFrame @ aa9ffc34)

0010d720 00000000 00000000 00000000 00000000 0x76df70f4


Stack 주소 영역은 Limit aa9fd000부터 Base aaa00000까지라고 나온다.

또한 lmva 명령으로 모듈 내의 주소인지도 확인 가능하다.

kd> lmva 85c5c000

Browse full module list

start end module name


아무런 출력 결과가 없다. 일치되는 모듈 주소가 없다는 의미다. 그렇다면 동적으로 할당된 메모리 주소일 가능성이 높다.

!pool 명령을 사용해서 확인해보자.

kd> !pool 85c5c000

Pool page 85c5c000 region is Nonpaged pool

*85c5c000 : large page allocation, tag is MmAc, size is 0x1000 bytes

Pooltag MmAc : Mm access log buffers, Binary : nt!mm


역시나 MmAc 풀 태그로 할당된 Nonpaged 풀이라고 친절하게 알려 준다. 풀 태그 설명을 보니 Mm access log buffers이고, 85c5c000 값은 MiAllocateAccessLog 내에서 사용된 값이니 정상적인 값일 것이다.

그렇다면 ebx에 설정된 85c5c000은 정상이었는데 85c5c000의 값을 누군가 34333231로 덮어 썼을 가능성이 매우 높다.

보통 이런 외부 모듈에 의한 메모리 손상은 주로 다음 2가지 시나리오에 의해 발생한다.

1.    외부 모듈이 메모리 주소 앞쪽 부분부터 문제 발생 위치까지 덮어 썼을 가능성

2.    외부 모듈이 문제 발생 위치만 덮어 썼을 가능성

 

흔히 1번 시나리오가 발생 빈도도 높고 메모리 앞쪽 부분을 살펴보면 원인을 찾을 가능성도 높다. 반면에 2번 같은 경우에는 증거를 찾기가 무척 어려워 분석이 불가능한 경우가 많다.

희망을 가지고 85c5c000의 앞쪽 메모리를 한 번 살펴보자.

100 bytes 정도 앞의 값을 !pool 명령으로 보면 앞 쪽에 할당된 풀 정보를 함께 확인할 수 있다.

kd> !pool 85c5c000-100

Pool page 85c5bf00 region is Nonpaged pool

85c5b000 size: 270 previous size: 0 (Free ) Irp

85c5b270 size: 8 previous size: 270 (Free) .(..

85c5b278 size: 128 previous size: 8 (Allocated) Ntfi

85c5b3a0 size: 140 previous size: 128 (Allocated) Io Process: 887dfd40

85c5b4e0 size: 10 previous size: 140 (Free) Io

85c5b4f0 size: 28 previous size: 10 (Allocated) ABss

85c5b518 size: b8 previous size: 28 (Allocated) File (Protected)

85c5b5d0 size: 298 previous size: b8 (Allocated) AbcH

85c5b868 size: 118 previous size: 298 (Allocated) AbcH

85c5b980 size: 8 previous size: 118 (Free) Ifs

85c5b988 size: 28 previous size: 8 (Allocated) ABss

85c5b9b0 size: a8 previous size: 28 (Allocated) File (Protected)

85c5ba58 size: 8 previous size: a8 (Free) Ifs

85c5ba60 size: 198 previous size: 8 (Free ) Ifs

*85c5bbf8 size: 408 previous size: 198 (Allocated) *BaDr

Owning component : Unknown (update pooltag.txt)


85c5bbf8 BaDr 풀 태그와 함께 408 바이트로 할당된 풀이 보인다. 직감적으로 이 곳에 중요한 단서가 있음이 느껴진다.

db 명령을 통해 BaDr 풀 영역을 확인해보자.

kd> db 85c5bbf8 L408

85c5bbf8 33 00 81 04 42 61 44 72-3b 3b 3b 3b f8 0a 88 01 3...BaDr;;;;....

85c5bc08 f8 0a 88 01 f3 0a 88 01-3b 3b c4 88 3b 88 01 a7 ........;;..;...

85c5bc18 3b 01 d0 ec be 02 3b 3b-02 3b 18 3b 30 3b 04 ee ;.....;;.;.;0;..

85c5bc28 be 02 3b 3b 3b 3b 3b 3b-c8 ed be 02 cc 6f e0 76 ..;;;;;;.....o.v

85c5bc38 3b 3b 3b 3b 3b 3b d0 58-2a c0 65 96 76 d0 58 2a ;;;;;;.X*.e.v.X*

85c5bc48 3b 3b c0 65 96 76 e4 ed-be 02 09 d9 8e 76 c0 65 ;;.e.v.......v.e

85c5bc58 96 76 3b 3b f5 0a 44 76-9d 69 96 a4 58 ee be 02 .v;;..Dv.i..X...

85c5bc68 b5 d5 8e 76 3c 02 3b 1c-ee be 02 34 ee be 02 3b ...v<.;....4...;

85c5bc78 3b 38 ee be 02 30 ee be-02 29 6a 96 a4 fc ee be ;8...0...)j.....

85c5bc88 02 ef be 02 2d 46 3f 76-36 38 b0 0a 88 01 f4 ed ....-F?v68......

85c5bc98 be 02 3b 3b 3b 3b 3b 3b-3b 3b 3b 3b 3b 3b 0c ee ..;;;;;;;;;;;;..

85c5bca8 be 02 30 3b bc ee be 02-22 a0 94 76 51 52 a6 d0 ..0;...."..vQR..

85c5bcb8 fe ff ff ff 3b 3b ab cf-81 01 3c 02 3b b0 0a 88 ....;;....<.;...

85c5bcc8 01 3b 3b 3b 3b 3b 3b 8c-ee be 02 75 45 e5 a6 07 .;;;;;;....uE...

85c5bcd8 3b 20 ef be 02 c0 f7 be-02 3b 3b 3b 3b 3c 02 3b ; .......;;;;<.;

85c5bce8 60 3b e8 ee be 02 20 ef-be 02 c8 96 84 01 5b 76 `;.... .......[v

85c5bcf8 81 01 f8 0a 88 01 6e 3b-c8 96 84 01 60 3b 85 45 ......n;....`;.E

85c5bd08 e5 a6 40 ef be 02 08 e4-83 01 ff ff ff ff 43 d4 ..@...........C.

85c5bd18 81 01 20 ef be 02 dd 45-e5 a6 08 3b 64 ef be 02 .. ....E...;d...

85c5bd28 c0 f7 be 02 0f 3b 3b 3b-20 ef be 02 40 29 88 01 .....;;; ...@)..

85c5bd38 40 29 88 01 30 34 88 01-80 ef be 02 64 ef be 02 @)..04......d...

85c5bd48 b0 0a 88 01 0f 3b 18 89-84 01 e8 83 84 01 1b 3b .....;.........;

85c5bd58 1f 3b 05 3b 01 3b 3c ef-be 02 f8 0a 88 01 f8 89 .;.;.;<.........

85c5bd68 84 01 a0 ef be 02 3b 3b-30 3b 37 3b ed 45 e5 a6 ......;;0;7;.E..

85c5bd78 c4 ff be 02 08 f3 83 01-02 3b 30 53 81 01 80 ef .........;0S....

85c5bd88 be 02 01 3b d0 f0 40 d0-51 81 01 3b 3b b8 ef be ...;..@.Q..;;...

85c5bd98 02 b0 0a 88 01 01 3b 3b-3b d4 ef be 02 0f 3b 0f ......;;;.....;.

85c5bda8 3b 3b 3b 78 0a 88 01 02-3b 10 02 10 02 10 02 10 ;;;x....;.......

85c5bdb8 02 0f 3b 17 3b 3b 8a 76-31 3b 02 03 02 03 20 f4 ..;.;;.v1;.... .

85c5bdc8 be 02 85 9f 20 75 01 3b-07 3b 00 00 00 00 00 00 .... u.;.;......

85c5bdd8 00 00 00 00 00 00 00 00-00 3f 3f 31 3b be 02 3b .........??1;..;

85c5bde8 3b 3b 3b 3b 3b 3b 08 02-0c f2 be 02 3b 08 02 04 ;;;;;;......;...

85c5bdf8 f0 be 02 3b 3b 3b 8a 76-48 02 48 02 48 02 48 02 ...;;;.vH.H.H.H.

85c5be08 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be18 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be28 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be38 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be48 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be58 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be68 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be78 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be88 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5be98 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5bea8 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5beb8 48 02 48 02 48 02 48 02-48 02 48 02 48 02 48 02 H.H.H.H.H.H.H.H.

85c5bec8 48 02 48 02 48 02 02 20-01 02 03 04 05 06 07 08 H.H.H.. ........

85c5bed8 09 0a 0b 0c 0d 0e 0f 10-11 12 13 14 15 16 17 18 ................

85c5bee8 19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28 ....... !"#$%&'(

85c5bef8 29 2a 2b 2c 2d 2e 2f 30-31 32 33 34 35 36 37 38 )*+,-./012345678

85c5bf08 39 3a 3b 3c 3d 3e 3f 40-41 42 43 44 45 46 47 48 9:;<=>?@ABCDEFGH

85c5bf18 49 4a 4b 4c 4d 4e 4f 50-51 52 53 54 55 56 57 58 IJKLMNOPQRSTUVWX

85c5bf28 59 5a 5b 5c 5d 5e 5f 60-41 42 43 44 45 46 47 48 YZ[\]^_`ABCDEFGH

85c5bf38 49 4a 4b 4c 4d 4e 4f 50-51 52 53 54 55 56 57 58 IJKLMNOPQRSTUVWX

85c5bf48 59 5a 7b 7c 7d 7e 7f 80-20 20 20 20 20 20 20 20 YZ{|}~..

85c5bf58 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf68 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf78 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf88 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf98 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bfa8 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bfb8 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bfc8 20 20 20 20 20 20 ff 20-01 02 03 04 05 06 07 08 . ........

85c5bfd8 09 0a 0b 0c 0d 0e 0f 10-11 12 13 14 15 16 17 18 ................

85c5bfe8 19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28 ....... !"#$%&'(

(BaDr)  85c5bfff | 85c5c000  (MmAc)

                        ↓  ↓

85c5bff8 29 2a 2b 2c 2d 2e 2f 30-31 32 33 34 35 36 37 3b )*+,-./01234567;


BaDr 풀은 408바이트 크기인 85c5bfff까지만 사용 가능하다. MmAc 풀의 시작 부분이 문제가 발생한85c5c000주소인데 시작 위치를 보면 31 32 33 34 값이 보인다.

이 값을DWORD 크기인 4바이트로 읽으면 34333231이 된다. 바로 유효하지 않았던 그 값이다. 이해가 안 된다면 85c5c000 주소를 db dd 명령으로 확인해보자.

재미있게도 85c5c000 주소에 담긴 31 32 33 34 값을 ASCII 문자열로 읽으면 "1234".

.format 명령을 사용하면 문자열 값을 확인할 수 있다.

kd> .formats 31323334

Evaluate expression:

... ...

Chars: 1234

... ...


무엇보다 85c5bee8 주소부터 시작된 문자열 패턴과 85c5bfe8 주소부터 시작된 문자열 패턴이 매우 유사하다.

kd> db 85c5bee8 L120

1) 패턴 1 : BaDr 영역

85c5bee8 19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28 ....... !"#$%&'(

85c5bef8 29 2a 2b 2c 2d 2e 2f 30-31 32 33 34 35 36 37 38 )*+,-./012345678

85c5bf08 39 3a 3b 3c 3d 3e 3f 40-41 42 43 44 45 46 47 48 9:;<=>?@ABCDEFGH

85c5bf18 49 4a 4b 4c 4d 4e 4f 50-51 52 53 54 55 56 57 58 IJKLMNOPQRSTUVWX

85c5bf28 59 5a 5b 5c 5d 5e 5f 60-41 42 43 44 45 46 47 48 YZ[\]^_`ABCDEFGH

85c5bf38 49 4a 4b 4c 4d 4e 4f 50-51 52 53 54 55 56 57 58 IJKLMNOPQRSTUVWX

85c5bf48 59 5a 7b 7c 7d 7e 7f 80-20 20 20 20 20 20 20 20 YZ{|}~..

85c5bf58 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf68 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf78 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf88 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bf98 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bfa8 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bfb8 20 20 20 20 20 20 20 20-20 20 20 20 20 20 20 20

85c5bfc8 20 20 20 20 20 20 ff 20-01 02 03 04 05 06 07 08 . ........

85c5bfd8 09 0a 0b 0c 0d 0e 0f 10-11 12 13 14 15 16 17 18 ................

2) 패턴 2 : BaDr 영역

85c5bfe8 19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28 ....... !"#$%&'(

3) BaDr에서 MmAc 바뀌는 부분     | => 여기부터는 MmAc 영역

85c5bff8 29 2a 2b 2c 2d 2e 2f 30-31 32 33 34 35 36 37 3b )*+,-./01234567;


1)번 패턴과 2)번 패턴은 "19 1a 1b 1c"로 시작하는 부분부터 "31 32 33 34 35 36 37(1234567)"까지 완전히 동일하다.

그런데 2)번 패턴의 중간 위치인 3)번은 이미 BaDr이 아닌 커널의 MmAc의 풀 영역이다.

, 누군가 정해진 버퍼 크기를 넘어 문자열 복사를 해서 BaDr 다음 위치인 MmAc 풀 영역을 손상시켰고, 이후 MiAllocateAccessLog 함수에서 손상된 값에 접근하다 문제가 발생한 것이다.

이런 경우 대부분 BaDr 풀 태그로 메모리를 할당한 모듈이 범인이다.

풀 태그 문자열은 모듈 내에 포함되어 있기 때문에 !for_each_module 명령을 통해 메모리에 로드된 모든 모듈에서 풀 태그 문자열을 검색하는 방법으로 찾을 수 있다.

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

936fc159 42 61 44 72 83 c0 02 50-57 ff d3 8b f8 85 ff 74 BaDr...PW......t

936fc19e 42 61 44 72 57 ff 15 18-f0 6f 93 8b 45 08 8d 70 BaDrW....o..E..p

... ...


몇 개의 결과가 확인된다. 앞서 lmva 명령을 사용하면 어떤 모듈 주소 영역인지 확인 가능하다고 배웠다. 제일 먼저 출력된 936fc159 주소가 어떤 모듈인지 확인해보자.

kd> lmva 936fc159

Browse full module list

start end module name

936fb000 93746000 BadDrv (deferred)

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

Image name: BadDrv.SYS

Browse all global symbols functions data

Timestamp: Fri Feb 07 09:42:37 2014 (52F42BFD)

CheckSum: 000139FD

ImageSize: 0004B000

Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4


범인은 BadDrv 모듈이었다. BadDrv 모듈에서 BaDr 풀 태그로 할당된 문자열 버퍼를 할당된 크기 이상으로 사용해서 커널 메모리 영역을 손상시킨 것으로 보인다.

BadDrv 모듈에서 문제의 문자열을 처리하는 곳을 찾아 버그를 수정해주면 이번 문제는 해결될 것이다.

문제 발생 위치는 분석하기 까다로운 곳이었지만 다행히도 메모리 손상 범위나 유형이 복잡하지 않아 원인을 분석할 수 있었다.

이번 예제에서 얻은 교훈은 미리 겁 먹을 필요가 없다는 것이다. 어려워 보이더라도 분석하는 것을 두려워하지 말자.



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

[0xC5] 해제 리스트 손상  (0) 2018.07.19
[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x50] 해제된 핸들  (0) 2018.07.09
[0x50] 숨겨진 콜 스택  (0) 2018.07.07
[0x50] UNICODE_STRING  (0) 2018.07.05
2018. 7. 9. 23:43

이번 덤프 역시 BugCheck 0x50다. 앞서 덤프와 같이 0x50 은 대부분 다른 모듈이 메모리를 손상시켰거나 정말 유효하지 않은 메모리나 해제된 메모리를 접근할 때 주로 발생한다. 원인에 따라 분석이 불가능한 경우도 많다.

이번에는 동기화와 관련된 주제이므로 약간 어려울 수 있다. 심호흡 한 번하고 차분하게 시작해보자.

kd> !analyze -v

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

*      *

*      Bugcheck Analysis   *

*      *

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

PAGE_FAULT_IN_NONPAGED_AREA (50)

Invalid system memory was referenced.  This cannot be protected by try-except.

Typically the address is just plain bad or it is pointing at freed memory.

Arguments:

Arg1: bad0b154, memory referenced.

Arg2: 00000000, value 0 = read operation, 1 = write operation.

Arg3: 8058c7b4, If non-zero, the instruction address which referenced the bad memory

       address.

Arg4: 00000002, (reserved)

Debugging Details:

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

DUMP_CLASS: 1

DUMP_QUALIFIER: 401

BUILD_VERSION_STRING:  2600.xpsp_sp3_gdr.120504-1619

SYSTEM_MANUFACTURER:  SAMSUNG ELECTRONICS CO., LTD.

SYSTEM_PRODUCT_NAME:  400B4B/400B5B/200B4B/200B5B

SYSTEM_SKU:  To be filled by O.E.M.

SYSTEM_VERSION:  04VC

BIOS_VENDOR:  American Megatrends Inc.

BIOS_VERSION:  04VC.M014.20110811.LDG

BIOS_DATE:  08/11/2011

BASEBOARD_MANUFACTURER:  SAMSUNG ELECTRONICS CO., LTD.

BASEBOARD_PRODUCT:  400B4B/400B5B/200B4B/200B5B

BASEBOARD_VERSION:  04VC

DUMP_TYPE:  1

BUGCHECK_P1: ffffffffbad0b154

BUGCHECK_P2: 0

BUGCHECK_P3: ffffffff8058c7b4

BUGCHECK_P4: 2

READ_ADDRESS:  bad0b154

 

FAULTING_IP:

nt!ObQueryNameString+9b

8058c7b4 8b88a4000000    mov     ecx,dword ptr [eax+0A4h]

MM_INTERNAL_CODE:  2

CPU_COUNT: 4

CPU_MHZ: 9be

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 2a

CPU_STEPPING: 7

CPU_MICROCODE: 6,2a,7,0 (F,M,S,R)  SIG: 1A'00000000 (cache) 1A'00000000 (init)

DEFAULT_BUCKET_ID:  DRIVER_FAULT

BUGCHECK_STR:  0x50

PROCESS_NAME:  EXCEL.EXE

ANALYSIS_SESSION_HOST:  PAUL-PC

ANALYSIS_SESSION_TIME:  11-01-2017 11:28:27.0997

ANALYSIS_VERSION: 10.0.10575.567 amd64fre

 

TRAP_FRAME:  a3dd1a04 -- (.trap 0xffffffffa3dd1a04)

ErrCode = 00000000

eax=bad0b0b0 ebx=00000000 ecx=00000000 edx=868edd44 esi=00000000 edi=868edd48

eip=8058c7b4 esp=a3dd1a78 ebp=a3dd1b2c 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!ObQueryNameString+0x9b:

8058c7b4 8b88a4000000    mov     ecx,dword ptr [eax+0A4h] ds:0023:bad0b154=????????

Resetting default scope

 

LAST_CONTROL_TRANSFER:  from 8052b198 to 805396c2

 

STACK_TEXT: 

a3dd199c 8052b198 00000050 bad0b154 00000000 nt!KeBugCheckEx+0x1b

a3dd19ec 804e2956 00000000 bad0b154 00000000 nt!MmAccessFault+0x77e

a3dd19ec 8058c7b4 00000000 bad0b154 00000000 nt!KiTrap0E+0xd0

a3dd1b2c f76487ed 868edd60 86410000 00007fff nt!ObQueryNameString+0x9b

a3dd1b7c f7649841 868edd60 00000000 8639f018 BadDrv+0x17ed

a3dd1bc0 a6728f9c 000009b0 00000000 a3dd1be0 BadDrv+0x2841

a3dd1c00 a672900b 000009b0 a3dd1d64 a50a9740 HookDrv+0x6f9c

a3dd1d58 804df99f 000009b0 00000000 7c93e514 HookDrv+0x700b

a3dd1d58 7c93e514 000009b0 00000000 7c93e514 nt!KiFastCallEntry+0xfc

00000000 00000000 00000000 00000000 00000000 0x7c93e514

 

STACK_COMMAND:  kb

THREAD_SHA1_HASH_MOD_FUNC:  662e78c74f8e63f10fbd8043327f172f7b75bbdc

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  c46449496b9268570a50343287cb531d6153a414

THREAD_SHA1_HASH_MOD:  5ecdf531ba7867e0fd5f6dd6366341b3e5123821

 

FOLLOWUP_IP:

BadDrv+17ed

f76487ed 85c0            test    eax,eax

 

FAULT_INSTR_CODE:  4d7cc085

SYMBOL_STACK_INDEX:  4

SYMBOL_NAME:  BadDrv+17ed

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: BadDrv

IMAGE_NAME:  BadDrv.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  4bb921b6

FAILURE_BUCKET_ID:  0x50_BADMEMREF_BadDrv+17ed

BUCKET_ID:  0x50_BADMEMREF_BadDrv+17ed

PRIMARY_PROBLEM_CLASS:  0x50_BADMEMREF_BadDrv+17ed

TARGET_TIME:  2012-07-13T08:54:52.000Z

OSBUILD:  2600

OSSERVICEPACK:  3000

SERVICEPACK_NUMBER: 3

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x86

OSNAME:  Windows XP

OSEDITION:  Windows XP WinNt (Service Pack 3) TerminalServer SingleUserTS

OS_LOCALE: 

USER_LCID:  0

OSBUILD_TIMESTAMP:  2012-05-04 22:16:02

BUILDOSVER_STR:  5.1.2600.xpsp_sp3_gdr.120504-1619

ANALYSIS_SESSION_ELAPSED_TIME: 8b2

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:0x50_badmemref_BadDrv+17ed

FAILURE_ID_HASH:  {aea5e7cf-9e2c-84a6-3525-f1fe4a8eaf29}

Followup:     MachineOwner

---------


BugCode 의 파라미터 정보를 보면 다음과 같다.

Arg1: bad0b154, memory referenced.

Arg2: 00000000, value 0 = read operation, 1 = write operation.

Arg3: 8058c7b4, If non-zero, the instruction address which referenced the bad memory

       address.


Arg1을 보면 bad0b154 메모리를 참조하는 중에 문제가 발생했는데, Arg2 를 보면 읽기 동작 중 문제가 발생했다고 한다. Arg3에는 문제가 발생한 코드 위치인 8058c7b4 가 담겨 있다.

물론 이것만으로는 더 알 수 없으니 실제 문제가 발생한 부분을 자세히 확인해야 한다.

항상 분석의 시작은 실제 문제가 발생한 부분부터 임을 명심하자.

.trap 명령으로 문제가 발생한 부분을 확인해보자

kd> .trap 0xffffffffa3dd1a04

ErrCode = 00000000

eax=bad0b0b0 ebx=00000000 ecx=00000000 edx=868edd44 esi=00000000 edi=868edd48

eip=8058c7b4 esp=a3dd1a78 ebp=a3dd1b2c 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!ObQueryNameString+0x9b:

8058c7b4 8b88a4000000    mov     ecx,dword ptr [eax+0A4h] ds:0023:bad0b154=????????


nt!ObQueryNameString 함수 수행 중에 mov ecx,dword ptr [eax+0A4h] 코드에서 유효하지 않은 메모리인 bad0b154를 접근해서 문제가 발생했다.

kv 명령어로 파라미터를 포함한 콜 스택을 확인해보자.

kd> kv

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

 # ChildEBP RetAddr  Args to Child             

00 a3dd1b2c f76487ed 868edd60 86410000 00007fff nt!ObQueryNameString+0x9b (FPO: [Non-Fpo])

01 a3dd1b7c f7649841 868edd60 00000000 8639f018 BadDrv+0x17ed

02 a3dd1bc0 a6728f9c 000009b0 00000000 a3dd1be0 BadDrv+0x2841

03 a3dd1c00 a672900b 000009b0 a3dd1d64 a50a9740 HookDrv+0x6f9c

04 a3dd1d58 804df99f 000009b0 00000000 7c93e514 HookDrv+0x700b

05 a3dd1d58 7c93e514 000009b0 00000000 7c93e514 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ a3dd1d64)

06 00000000 00000000 00000000 00000000 00000000 0x7c93e514


BadDrv에서 nt 커널의 ObQueryNameString 함수를 호출하다 문제가 발생했다. 보통은 BadDrv에서 해당 함수 호출시 잘못된 파라미터를 넘겨주었을 가능성이 큰 상황이다.

NTSTATUS ObQueryNameString(

    _In_      PVOID                    Object,

    _Out_opt_ POBJECT_NAME_INFORMATION ObjectNameInfo,

    _In_      ULONG                    Length,

    _Out_     PULONG                   ReturnLength

);


MSDN에서 검색해본 함수 원형이다. 이 함수는 입력 받은 오브젝트를 이용해서 오브젝트 이름을 얻는 함수다(예를 들어 특정 파일 오브젝트를 전달하면 해당 파일 오브젝트가 가리키는 파일 경로 문자열을 얻는다). 첫 번째 파라미터인 오브젝트가 의심스럽지만 아직 확신할 수 있는 단계는 아니다.

BadDrv에서 ObQueryNameString 함수 호출시 전달한 오브젝트는 868edd60 이다. kv 명령의 Args to Child 첫 번째 값을 봐도 되고 함수 호출 규약을 이해한다면 ebp 레지스터의 +8 한 값을 봐도 알 수 있다.

kd> dd ebp+8 L1

a3dd1b34  868edd60


문제가 발생한 지점이 nt 커널 내부인 ObQueryNameString 함수이므로 이제 해당 함수 안으로 들어가야한다. 미안하지만 더 집중해서 살펴보자.

kd> u nt!ObQueryNameString L25

nt!ObQueryNameString:

8058c731 6898000000      push    98h

8058c736 68a8015080      push    offset nt!ObWatchHandles+0xe4 (805001a8)

8058c73b e86387f5ff      call    nt!_SEH_prolog (804e4ea3)

8058c740 c745d4010000c0  mov     dword ptr [ebp-2Ch],0C0000001h

8058c747 8365c800        and     dword ptr [ebp-38h],0

8058c74b 8365d800        and     dword ptr [ebp-28h],0

8058c74f c645de01        mov     byte ptr [ebp-22h],1

8058c753 c645df00        mov     byte ptr [ebp-21h],0

8058c757 8b7d08          mov     edi,dword ptr [ebp+8] // 4) edi=ebp+8

8058c75a 83c7e8          add     edi,0FFFFFFE8h  // 3) edi=edi-0n24

8058c75d 897dc0          mov     dword ptr [ebp-40h],edi

8058c760 8a470c          mov     al,byte ptr [edi+0Ch]

8058c763 84c0            test    al,al

8058c765 0f8486040000    je      nt!ObQueryNameString+0x36 (8058cbf1)

8058c76b 0fb6c0          movzx   eax,al

8058c76e 8bdf            mov     ebx,edi

8058c770 2bd8            sub     ebx,eax

8058c772 85db            test    ebx,ebx

8058c774 0f843c040000    je      nt!ObQueryNameString+0x93 (8058cbb6)

8058c77a 8d530c          lea     edx,[ebx+0Ch]

8058c77d 8b0a            mov     ecx,dword ptr [edx]

8058c77f 85c9            test    ecx,ecx

8058c781 0f841f390000    je      nt!ObQueryNameString+0x63 (805900a6)

8058c787 8d4101          lea     eax,[ecx+1]

8058c78a 8bf0            mov     esi,eax

8058c78c 8bc1            mov     eax,ecx

8058c78e f00fb132        lock    cmpxchg dword ptr [edx],esi

8058c792 3bc1            cmp     eax,ecx

8058c794 0f852c6d0700    jne     nt!ObQueryNameString+0x5d (806034c6)

8058c79a b001            mov     al,1

8058c79c 84c0            test    al,al

8058c79e 0f8412040000    je      nt!ObQueryNameString+0x93 (8058cbb6)

8058c7a4 f6420380        test    byte ptr [edx+3],80h

8058c7a8 0f85276d0700    jne     nt!ObQueryNameString+0x73 (806034d5)

8058c7ae 895de0          mov     dword ptr [ebp-20h],ebx

8058c7b1 8b4708          mov     eax,dword ptr [edi+8] // 2) edi=868edd48

8058c7b4 8b88a4000000    mov     ecx,dword ptr [eax+0A4h] // 1) eax=bad0b0b0


1)이 문제가 발생한 곳으로 어디에서 eax bad0b0b0 값을 채웠는지 함수 시작 지점부터 찾아봐야 한다.

2)에 edi + 8 주소의 값을 eax 에 넣는 코드가 있으므로 edi 값을 찾아보자. 다행히 edi 를 덮어쓰는 코드는 없으므로 r 명령으로 확인 가능하다.

kd> r edi

Last set context:

edi=868edd48


2)에서 mov eax,dowrd ptr [edi+8] 명령을 통해 868edd48 + 8 위치의 값을 eax 에 설정했다.

kd> dd 868edd48+8 L1

868edd50  bad0b0b0


값을 참조하는 dword ptr 명령이 사용됐음을 유의하자. 주소가 아닌 값을 참조하는 명령이므로 868edd50 이 아닌 bad0b0b0을 참조한다. 즉 이 값이 eax에 설정된다.

3) 여기서 원래 edi 값에 ffffffe8을 더한 값(add)으로 edi가 설정됐다. ? 명령으로 알기 쉽게 10진수로 변환해보자.

kd> ? ffffffe8

Evaluate expression: -24 = ffffffe8


아하, edi 10진수 -24 를 더한 것이니 결국 edi = edi – 18(0n24)이다.

여기서 18 10진수 24 16진수 값을 의미한다.

[참고]

WinDbg 에서는 16진수 표현 방식이 기본값이므로 앞에 0x를 붙이지 않고 표기했다. 기본값은 n 명령어로 변경 가능한데 이 경우 16진수 앞에 붙이는 접두사인 0x 는 생략 가능하다. 다른 진수 사용시에는 명시적으로 진수에 맞는 접두사와 함께 사용해야 한다. 자주 혼동되는 부분이기 때문에 유의하자.

- 16진수 : OO / 0xOO (기본값)

- 10진수 0nOO

- 8진수 0tOO

- 2진수 0y00


4) edi ebp + 8 값을 가져와 설정한다. 앞서 ebp + 8(첫 번째 파마리터) 868edd60 였으므로 이를 토대로 재구성해보자. 

4) edi = 868edd60 (ebp+8)

3) edi = 868edd60 - 18

2) eax = poi(868edd48+8)

1) ecx = poi(bad0b0b0+a4)


2) 1) 에서 사용된 poi는 해당 주소의 값을 얻는 명령이다.

<poi 사용 유무에 따른 결과>

kd> dd 868edd48+8 L1

868edd50  bad0b0b0

 

kd> dd poi(868edd48+8) L1

bad0b0b0  ????????


1) 에서 bad0b0b0 + a4 를 하면 바로 문제가 발생했던 bad0b154 !

kd> dd bad0b154 L1

bad0b154  ????????


역시나 bad0b154는 유효하지 않은 메모리 값으로 여기를 접근했으므로 문제가 발생한 것이다.

지금까지 확인된 내용을 쉽게 정리해보면 첫 번째 파라미터 값인 868edd60으로 여러 연산(-18, +8, +a4)을 수행하다 bad0b154 값에 접근한 것이 문제의 원인이다. 그런데 왜 이런 알 수 없는 연산을 한 것일까? 감이 좋은 사람이라면 전달된 값이 어떤 구조체이고 해당 구조체의 필드를 참조하는 연산을 수행한 것임을 눈치 챘을 수도 있다. 보통 저런 숫자를 빼거나 더하는 연산은 구조체를 참조하는 코드인 경우가 많다.

여기까지 잘 따라왔다면 정말 훌륭하다. 자부심을 가져도 좋다. 이제부터는 약간의 윈도우 커널 내부 지식을 필요로 하지만 크게 어렵지는 않다.

앞서 ObQueryNameString 함수 원형을 통해 868edd60 값이 커널의 오브젝트 포인터라는 것은 이미 알고 있다. 윈도우는 프로세스, 스레드, 파일 등 대부분의 자원을 커널 오브젝트라는 것으로 관리하는데 이들 오브젝트에는 공통적으로 오브젝트 헤더가 존재한다. 오브젝트 헤더 구조체는 dt _OBJECT_HEADER 명령으로 확인 가능하다.

kd> dt _OBJECT_HEADER 868edd60-18

nt!_OBJECT_HEADER

   +0x000 PointerCount     : 0

   +0x004 HandleCount      : 0

   +0x004 NextToFree       : (null)

   +0x008 Type             : 0xbad0b0b0 _OBJECT_TYPE

   +0x00c NameInfoOffset   : 0x10 ''

   +0x00d HandleInfoOffset : 0 ''

   +0x00e QuotaInfoOffset  : 0 ''

   +0x00f Flags            : 0xa0 ''

   +0x010 ObjectCreateInfo : (null)

   +0x010 QuotaBlockCharged : (null)

   +0x014 SecurityDescriptor : (null)

   +0x018 Body             : _QUAD


오브젝트 헤더의 +18 한 필드명이 Body 이다. 예상한 대로 868edd60 는 어떤 오브젝트이고 첫 번째 연산인 -18은 오브젝트 헤더 위치를 구하기 위한 것이었다. 어떤 구조체인지 안 이상 다음은 어렵지 않다. 두 번째 연산인 +8 Type 필드를 가져오기 위한 연산이었고 이를 통해 0xbad0b0b0 값을 얻은 것이다. Body 에는 어떤 오브젝트 포인터든 올 수 있기 때문에 커널은 이 Type 필드를 통해 오브젝트 유형을 판단한다.

WinDbg가 친절하게도 Type 값은 _OBJECT_TYPE 구조체라고 알려준다. Type 값인 0xbad0b0b0 dt _OBJECT_TYPE 명령으로 확인하면 마지막 연산인 +a4의 의미를 알 수 있다.

kd> dt _OBJECT_TYPE 0xbad0b0b0

nt!_OBJECT_TYPE

   +0x000 Mutex            : _ERESOURCE

   +0x038 TypeList         : _LIST_ENTRY

   +0x040 Name             : _UNICODE_STRING

   +0x048 DefaultObject    : ????

   +0x04c Index            : ??

   +0x050 TotalNumberOfObjects : ??

   +0x054 TotalNumberOfHandles : ??

   +0x058 HighWaterNumberOfObjects : ??

   +0x05c HighWaterNumberOfHandles : ??

   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER

   +0x0ac Key              : ??

   +0x0b0 ObjectLocks      : [4] _ERESOURCE


0xbad0b0b0 + a4 위치는 TypeInfo 0x060 Key 0x0ac의 사이다. TypeInfo 내부의 어떤 필드일 것이다. 하지만 Type 값인 bad0b0b0 은 유효한 메모리가 아니어서 구조체 내부 값들이 ?? 로 표시된다. 여기서는 TypeInfo 구조체의 어떤 필드였는지만 알면 되니 문제되지는 않는다.

kd> dt _OBJECT_TYPE_INITIALIZER

nt!_OBJECT_TYPE_INITIALIZER

   +0x000 Length           : Uint2B

   +0x002 UseDefaultObject : UChar

   +0x003 CaseInsensitive  : UChar

   +0x004 InvalidAttributes : Uint4B

   +0x008 GenericMapping   : _GENERIC_MAPPING

   +0x018 ValidAccessMask  : Uint4B

   +0x01c SecurityRequired : UChar

   +0x01d MaintainHandleCount : UChar

   +0x01e MaintainTypeList : UChar

   +0x020 PoolType         : _POOL_TYPE

   +0x024 DefaultPagedPoolCharge : Uint4B

   +0x028 DefaultNonPagedPoolCharge : Uint4B

   +0x02c DumpProcedure    : Ptr32     void

   +0x030 OpenProcedure    : Ptr32     long

   +0x034 CloseProcedure   : Ptr32     void

   +0x038 DeleteProcedure  : Ptr32     void

   +0x03c ParseProcedure   : Ptr32     long

   +0x040 SecurityProcedure : Ptr32     long

   +0x044 QueryNameProcedure : Ptr32     long

   +0x048 OkayToCloseProcedure : Ptr32     unsigned char


a4에서 TypeInfo 시작 위치인 60을 빼면 44가 나온다. 44 _OBJECT_TYPE_INITIALIZER QueryNameProcedure에 해당한다. 이제 문제가 발생한 상황이 명확해지는 것 같다.

4) 오브젝트를 가져 옴                 // edi = 868edd60 (ebp+8)

3) 오브젝트 헤더를 구함               // edi = 868edd60 - 18

2) 헤더에서 오브젝트 타입을 구함    // eax = poi(868edd48+8)

1) 오브젝트 타입의 TypeInfo에서 QueryNameProcedure를 참조  // ecx = poi(bad0b0b0+a4)


문제가 발생한 원인이 밝혀졌다. ObQueryNameString 함수에서 입력 받은 오브젝트로 QueryNameProcedure 함수 포인터를 구하려다 2) 시점에 오브젝트 타입 값이 유효하지 않은 값이라 1) 에서 문제가 발생했다.

이쯤에서 잠시 쉬는 것이 좋겠다. 장시간 분석은 정신 건강에 매우 해롭다.

거의 분석이 끝났지만 왜 이런 상황이 발생했는지는 아직 밝혀지지 않았다.

! 휴식이 끝났으면 다음 3가지 시나리오를 살펴 보자.

1. 외부에서 이미 잘못된 값을 BadDrv 에 전달했고 BadDrv 는 그대로 ObQueryNameString 함수에 전달했다.

2. 외부에서 정상적인 값을 BadDrv 에 전달했지만 BadDrv 에서 잘못된 값으로 바꿔 ObQueryNameString 함수에 전달했다.

3. 외부에서 정상적인 값을 BadDrv 에 전달했고 BadDrv 도 그대로 ObQueryNameString 함수에 전달했지만 중간에 잘못된 값으로 바뀌었다.

어떤 시나리오가 마음에 드는가? 분석할 때 가장 경계해야 하는 부분은 결론을 정해 놓고 분석에 임하는 것이다. 그럴 경우 자신이 보고 싶은 부분만 보기 때문에 진실을 놓칠 우려가 있다. 그렇기에 나는 이 3가지 시나리오를 모두 검증해볼 것이다.

kd> kv

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

 # ChildEBP RetAddr  Args to Child             

00 a3dd1b2c f76487ed 868edd60 86410000 00007fff nt!ObQueryNameString+0x9b (FPO: [Non-Fpo])  // 4) nt 커널 영역

01 a3dd1b7c f7649841 868edd60 00000000 8639f018 BadDrv+0x17ed      // 3) BadDrv 영역

02 a3dd1bc0 a6728f9c 000009b0 00000000 a3dd1be0 BadDrv+0x2841      // 2) BadDrv 영역

03 a3dd1c00 a672900b 000009b0 a3dd1d64 a50a9740 HookDrv+0x6f9c     // 1) 여기부터 아래로 외부 영역

04 a3dd1d58 804df99f 000009b0 00000000 7c93e514 HookDrv+0x700b

05 a3dd1d58 7c93e514 000009b0 00000000 7c93e514 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ a3dd1d64)

06 00000000 00000000 00000000 00000000 00000000 0x7c93e514



3)번 콜 스택의 첫 번째 파라미터인 868edd60 는 앞서 분석에서 확인된 오브젝트다. 외부 영역인 1)번과 2)번까지 보이는 9b0 값이 3)번에서 오브젝트로 바뀌었으므로 9b0 값을 먼저 확인해야 한다.

3)번의 BadDrv+0x17ed 위치는 ObQueryNameString 함수를 호출한 위치다. BadDrv+0x17ed 함수의 시작 부분을 확인하려면 2)번에서 마지막 함수를 부른 위치를 확인하면 된다.

ub 명령을 통해 2)번 위치에서 함수 호출하는 부분을 살펴보자.

kd> ub BadDrv+0x2841

BadDrv+0x2825:

f7649825 b89a0000c0      mov     eax,0C000009Ah

f764982a e9f3000000      jmp     BadDrv+0x2922 (f7649922)

f764982f 8d7e18          lea     edi,[esi+18h]

f7649832 66c7070100      mov     word ptr [edi],1

f7649837 57              push    edi

f7649838 53              push    ebx

f7649839 ff7508          push    dword ptr [ebp+8]

f764983c e8a7eeffff      call    BadDrv+0x16e8 (f76486e8)


BadDrv+0x16e8 BadDrv+0x17ed 주소가 수행된 함수의 시작 위치다. 시작 위치부터 ObQueryNameString 함수가 호출된 부분까지 확인해보자.

kd> u BadDrv+0x16e8 L4d

BadDrv+0x16e8:

f76486e8 6a18            push    18h

f76486ea 68e8d164f7      push    offset BadDrv+0x61e8 (f764d1e8)

f76486ef e8cc3a0000      call    BadDrv+0x51c0 (f764c1c0)

f76486f4 33ff            xor     edi,edi

f76486f6 33f6            xor     esi,esi

f76486f8 397d08          cmp     dword ptr [ebp+8],edi

f76486fb 0f844f010000    je      BadDrv+0x1850 (f7648850)

f7648701 7d0e            jge     BadDrv+0x1711 (f7648711)

f7648703 ff155cd064f7    call    dword ptr [BadDrv+0x605c (f764d05c)]

f7648709 84c0            test    al,al

f764870b 0f853f010000    jne     BadDrv+0x1850 (f7648850)

f7648711 57              push    edi

f7648712 8d45e0          lea     eax,[ebp-20h]

f7648715 50              push    eax

f7648716 57              push    edi

f7648717 57              push    edi

f7648718 57              push    edi

f7648719 ff7508          push    dword ptr [ebp+8] // 1) ebp+8 = Handle

f764871c ff1558d064f7    call    dword ptr [BadDrv+0x6058 (f764d058)]     // 2) ObReferenceObjectByHandle 함수 호출

f7648722 85c0            test    eax,eax

f7648724 0f8526010000    jne     BadDrv+0x1850 (f7648850)

f764872a 8b4de0          mov     ecx,dword ptr [ebp-20h]

f764872d 8bd9            mov     ebx,ecx

f764872f 895d08          mov     dword ptr [ebp+8],ebx // 3) ebp+8 = Object 변경

f7648732 3bcf            cmp     ecx,edi

f7648734 0f8416010000    je      BadDrv+0x1850 (f7648850)

f764873a ff1564d064f7    call    dword ptr [BadDrv+0x6064 (f764d064)]      // 4) ObfDereferenceObject 함수 호출

f7648740 ff1538d064f7    call    dword ptr [BadDrv+0x6038 (f764d038)]

f7648746 c1eb02          shr     ebx,2

f7648749 83e37f          and     ebx,7Fh

f764874c 8bc3            mov     eax,ebx

f764874e 6bc038          imul    eax,eax,38h

f7648751 8db880ef64f7    lea     edi,BadDrv+0x7f80 (f764ef80)[eax]

f7648757 6a01            push    1

f7648759 57              push    edi

f764875a ff1534d064f7    call    dword ptr [BadDrv+0x6034 (f764d034)]

f7648760 8b049d000c65f7  mov     eax,dword ptr BadDrv+0x9c00 (f7650c00)[ebx*4]

f7648767 eb0a            jmp     BadDrv+0x1773 (f7648773)

f7648769 8b4d08          mov     ecx,dword ptr [ebp+8]

f764876c 3908            cmp     dword ptr [eax],ecx

f764876e 7407            je      BadDrv+0x1777 (f7648777)

f7648770 8b4004          mov     eax,dword ptr [eax+4]

f7648773 85c0            test    eax,eax

f7648775 75f2            jne     BadDrv+0x1769 (f7648769)

f7648777 85c0            test    eax,eax

f7648779 742a            je      BadDrv+0x17a5 (f76487a5)

f764877b 8b7008          mov     esi,dword ptr [eax+8]

f764877e 8d0c36          lea     ecx,[esi+esi]

f7648781 51              push    ecx

f7648782 83c00e          add     eax,0Eh

f7648785 50              push    eax

f7648786 8b5d10          mov     ebx,dword ptr [ebp+10h]

f7648789 53              push    ebx

f764878a e89b390000      call    BadDrv+0x512a (f764c12a)

f764878f 83c40c          add     esp,0Ch

f7648792 8bcf            mov     ecx,edi

f7648794 ff152cd064f7    call    dword ptr [BadDrv+0x602c (f764d02c)]

f764879a ff1528d064f7    call    dword ptr [BadDrv+0x6028 (f764d028)]

f76487a0 e995000000      jmp     BadDrv+0x183a (f764883a)

f76487a5 8bcf            mov     ecx,edi

f76487a7 ff152cd064f7    call    dword ptr [BadDrv+0x602c (f764d02c)]

f76487ad ff1528d064f7    call    dword ptr [BadDrv+0x6028 (f764d028)]

f76487b3 ff35e0ee64f7    push    dword ptr [BadDrv+0x7ee0 (f764eee0)]

f76487b9 ff35e4ee64f7    push    dword ptr [BadDrv+0x7ee4 (f764eee4)]

f76487bf 6a00            push    0

f76487c1 ff150cd064f7    call    dword ptr [BadDrv+0x600c (f764d00c)]

f76487c7 8bf8            mov     edi,eax

f76487c9 85ff            test    edi,edi

f76487cb 0f8431020000    je      BadDrv+0x1a02 (f7648a02)

f76487d1 662137          and     word ptr [edi],si

f76487d4 66c74702feff    mov     word ptr [edi+2],0FFFEh

f76487da 8d45e4          lea     eax,[ebp-1Ch]

f76487dd 50              push    eax

f76487de 68ff7f0000      push    7FFFh

f76487e3 57              push    edi

f76487e4 ff7508          push    dword ptr [ebp+8]  // 5) ebp+8 = Object

f76487e7 ff1560d064f7    call    dword ptr [BadDrv+0x6060 (f764d060)]      // 6) nt!ObQueryNameString 함수 호출


1)번에서 첫 번째 파라미터인 ebp+8 push 하고 2)번에서 ObReferenceObjectByHandle 함수를 호출하고 있다. ObReferenceObjectByHandle 함수의 원형은 다음과 같다.

NTSTATUS ObReferenceObjectByHandle(

    _In_      HANDLE                     Handle,

    _In_      ACCESS_MASK                DesiredAccess,

    _In_opt_  POBJECT_TYPE               ObjectType,

    _In_      KPROCESSOR_MODE            AccessMode,

    _Out_     PVOID                      *Object,

    _Out_opt_ POBJECT_HANDLE_INFORMATION HandleInformation

    );


첫 번째 파라미터는 Handle 이다. 9b0 값은 Handle 임을 알 수 있다.

3)번에서는 ebp + 8 값을 ObReferenceObjectByHandle 함수를 호출해서 리턴 받은 Object로 변경한다. 5), 6)번에서는 이 Object로 문제 발생 함수인 ObQueryNameString을 호출한다.

ObReferenceObjectByHandle 함수는 Handle을 가지고 Handle이 가리키는 커널 오브젝트를 얻는 함수다. 참조에 성공하면 대상 커널 오브젝트의 참조 카운트는 1 증가하게 된다.

어쨌든 9b0 값을 !handle 명령으로 확인해보자.

kd> !handle 9b0

Failed to get VAD root

PROCESS 89863020  SessionId: 0  Cid: 12b0    Peb: 7ffdf000  ParentCid: 0e6c

    DirBase: 23041000  ObjectTable: e10f9978  HandleCount: 623.

    Image: EXCEL.EXE

Handle table at e10f9978 with 623 entries in use

09b0: free handle, Entry address e1225360, Next Entry 000009d0


역시 9b0은 소유자인 EXCEL.EXE 프로세스의 핸들 테이블에서 해제된 핸들(free handle)이라고 나온다. 해제되어 사용할 수 없는 핸들이라는 의미다. 여기서 1번 시나리오 대로 외부에서 잘못된 핸들을 BadDrv에 전달했군! 이라고 생각했다면 성급한 판단이다.

ObReferenceObjectByHandle 함수 호출이 성공해서 Object 868edd60 값을 얻었음을 잊지 말자. 함수 호출이 성공했다는 것은 당시에는 유효한 핸들이었다는 의미다. 만약 해제되거나 유효하지 않은 핸들이었다면 ObReferenceObjectByHandle 함수 호출시 STASTUS_INVALID_HANDLE 오류가 리턴되었을 것이고 Object 또한 NULL 이 반환되었을 것이다.

2)번에서 ObReferenceObjectByHandle 함수 호출이 성공했으므로 1번 시나리오는 가능성이 없다.

4)번에서는 ObfDereferenceObject 함수로 ObReferenceObjectByHandle 를 통해 증가시킨 참조 카운트를 다시 원래대로 내려주고 있다.

이후 6) ObQueryNameString 함수 호출 부분까지는 Object 값을 변경하는 행위가 보이지 않는다.

“BadDrv는 외부에서 정상적인 Handle 값인 9b0을 받았고 이를 통해 Object 값인 868edd60를 얻어 ObQueryNameString 함수에 전달했다고 판단하는 데 무리가 없어 보인다. 2번 시나리오인 BadDrv가 잘못된 ObjectObQueryNameString 에 전달했을 가능성도 낮다.

혹시 분석 초반에 봤던 오브젝트 헤더의 이상한 값 0xbad0b0b0을 기억하는가?

kd> dt _OBJECT_HEADER 868edd60-18

nt!_OBJECT_HEADER

       ... ...

   +0x008 Type             : 0xbad0b0b0 _OBJECT_TYPE

       ... ...

   +0x018 Body             : _QUAD


Type 에 저장되어 있는 0xbad0b0b0 값은 실은 Object 가 명확하게 해제될 때 커널 내부적으로 기록하는 상태 값이다.

이 부분을 설명하기 위해 현재 분석하고 있는 덤프가 아닌 가상 머신에서 라이브 디버깅을 통해 커널이 0xbad0b0b0 값을 설정하는 순간을 확인해봤다. 분석 중인 덤프에서 확인한 내용이 아니므로 혼동하지 말자.

<라이브 디버깅 환경에서 확인한 0xbad0b0b0 값을 설정하는 부분>

kd> kc

 #

00 nt!ObpFreeObject+0x16c

01 nt!ObpRemoveObjectRoutine+0xe8

02 nt!ObfDereferenceObject+0x4c

03 nt!ObpCloseHandleTableEntry+0x155

04 nt!ObpCloseHandle+0x87

05 nt!NtClose+0x1d

06 nt!KiFastCallEntry+0xfc

 

kd> u nt!ObpFreeObject+a1

nt!ObpFreeObject+0x12f:

8056f68f 3bc3            cmp     eax,ebx

8056f691 5f              pop     edi

8056f692 0f8562680000    jne     nt!ObpFreeObject+0x134 (80575efa)

8056f698 8b45f4          mov     eax,dword ptr [ebp-0Ch]

8056f69b 3bc3            cmp     eax,ebx

8056f69d 0f850f0c0000    jne     nt!ObpFreeObject+0x14e (805702b2)

8056f6a3 8b45f0          mov     eax,dword ptr [ebp-10h]

8056f6a6 c74608b0b0d0ba  mov     dword ptr [esi+8],0BAD0B0B0h


ObFreeObject 라는 함수에서 Object가 해제될 시 오브젝트 헤더의 Type(+8) 위치에 정확하게 bad0b0b0이라는 값을 기록해주고 있다.

따라서 bad0b0b0 값은 해당 Object가 해제되었다는 명백한 증거다.

 

다시 예제 덤프로 돌아와보자. !handle 정보와 오브젝트 헤더 정보를 통해 Handle 이 해제된 것은 명확하다. 하지만 해제 시점이 애매하다.

이제부터 설명은 덤프에서 확인이 어려운 부분도 포함되어 있다. 나는 3번 시나리오인정상적인 Object 값을 ObQueryNameString 함수에 전달했지만 중간에 해제됐을 가능성이 가장 높다고 본다. 여기에는 동기화 문제가 숨어 있다.


1) 외부(EXCEL) Thread 1에서 정상적인 Handle을 BadDrv에 전달

2) BadDrv에서 전달된 Handle로 ObReferenceObjectByHandle로 Object 획득(참조 카운트 +1)

3) BadDrv에서 ObfDereferenceObject로 Object 참조 카운트 복원(참조 카운트 -1)

  a) 외부(EXCEL) Thread 2에서 Handle 해제?

4) BadDrv에서 Object를 이용하여 nt 커널의 ObQueryNameString 함수 호출

  b) 외부(EXCEL) Thread 2에서 Handle 해제?

5) ObQueryNameString 함수 내부에서 잘못된 메모리 접근


아마 3)~5)번 사이 a) b) 시점에 Handle 해제가 발생했을 것이다. 이렇게 특정한 이유는 3)번에서 ObfDereferenceObject Object의 참조 카운트를 돌려놨기 때문이다. 이로 인해 해당 Handle은 언제든 해제될 수 있는 상태가 되었다.

커널에서 Object를 가지고 작업을 수행할 때는 흔히 ObReferenceObjectByHandle 함수를 통해 참조 카운트를 증가시키고 수행한다. 참조 카운트가 증가된 상태에서는 오브젝트가 해제되지 않기 때문이다. 작업이 완료되면 DereferenceObject 류 함수를 통해 참조 카운트를 감소시켜 해제 가능한 상태로 돌려 놓는다. 만약 참조 카운트를 증가시키지 않고 작업할 경우 중간에 작업 중인 오브젝트가 해제되어 문제가 발생할 수 있다.

이렇게 Handle Object로 안전하지 않게 참조할 경우 다른 Thread에 의해 해당 Handle 이 해제되면, 골치 아픈 동기화 문제가 발생할 수 있다. 이런 동기화 문제는 이번처럼 덤프 상에서는 결과만 보이므로 제대로 원인이 확인되지 않는 경우가 많다. 하지만 실제로 심심치 않게 발생한다.

드라이버 개발 시 뭔가 Object로 참조해서 사용할 경우 참조 카운트 해제는 Object 사용이 다 끝난 후에 하는 것이 원칙이다.

만약 ObfDereferenceObject를 ObQueryNameString 다음에 했다면 Object 의 참조 카운트가 증가된 상태라 설령 다른 Thread 에서 중간에 Handle 을 해제하더라도 Object가 해제되지 않는다. Object ObfDereferenceObject가 호출될 때 비로서 참조 카운트가 내려가면서 해제 가능한 상태가 된다. 그러므로 ObQueryNameString에서 문제도 발생하지 않는다.

현업에서 많은 덤프를 분석하다 보면 이렇게 겉으로만 봐서는 논리적으로 설명이 안 되는 상황이 있는데, 동기화 이슈 관점에서 보면 의외로 답을 찾는 경우가 많았다. 역시나 덤프 내면을 볼 수 있는 상상력이 조금은 필요한 것 같다.



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

[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 숨겨진 콜 스택  (0) 2018.07.07
[0x50] UNICODE_STRING  (0) 2018.07.05
필터매니저(fltmgr.sys) 버그? DRAINING ZOMBIED 와 DOE_UNLOAD_PENDING  (0) 2015.01.22
2018. 7. 7. 23:48

이번 덤프의 BugCheck는 0x50이다. 0x50은 대부분 다른 모듈이 메모리를 손상시켰거나 정말 유효하지 않은 메모리나 해제된 메모리를 접근할 때 주로 발생한다. 원인에 따라 분석이 불가능한 경우도 많다.

다행히 이번 덤프는 어렵지 않으면서 재미도 있으니 가벼운 마음으로 시작해보자.

kd> !analyze -v

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

*      *

*      Bugcheck Analysis   *

*      *

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

PAGE_FAULT_IN_NONPAGED_AREA (50)

Invalid system memory was referenced.  This cannot be protected by try-except.

Typically the address is just plain bad or it is pointing at freed memory.

Arguments:

Arg1: ffff0000, memory referenced.

Arg2: 00000000, value 0 = read operation, 1 = write operation.

Arg3: 82ad19fe, If non-zero, the instruction address which referenced the bad memory

       address.

Arg4: 00000000, (reserved)

 

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: ffffffffffff0000

BUGCHECK_P2: 0

BUGCHECK_P3: ffffffff82ad19fe

BUGCHECK_P4: 0

READ_ADDRESS:  ffff0000

 

FAULTING_IP:

nt!strstr+1e

82ad19fe 8a07            mov     al,byte ptr [edi]

 

MM_INTERNAL_CODE:  0

CPU_COUNT: 1

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

BUGCHECK_STR:  0x50

PROCESS_NAME:  MyApp.exe

CURRENT_IRQL:  2

ANALYSIS_SESSION_HOST:  PAUL-PC

ANALYSIS_SESSION_TIME:  11-29-2017 10:35:11.0546

ANALYSIS_VERSION: 10.0.10575.567 amd64fre

 

TRAP_FRAME:  89812c30 -- (.trap 0xffffffff89812c30)

ErrCode = 00000000

eax=ffff0000 ebx=00000000 ecx=9213a600 edx=ffff4d5c esi=ffff0000 edi=ffff0000

eip=82ad19fe esp=89812ca4 ebp=89813bfc 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!strstr+0x1e:

82ad19fe 8a07            mov     al,byte ptr [edi]          ds:0023:ffff0000=??

Resetting default scope

 

LAST_CONTROL_TRANSFER:  from 82a933d8 to 82ae041b

 

STACK_TEXT: 

89812c18 82a933d8 00000000 ffff0000 00000000 nt!MmAccessFault+0x106

89812c18 82ad19fe 00000000 ffff0000 00000000 nt!KiTrap0E+0xdc

89813bfc 82a89593 84525860 843f0858 843f0858 nt!strstr+0x1e

89813c14 82c7d99f 84489e18 843f0858 843f08c8 nt!IofCallDriver+0x63

89813c34 82c80b71 84525860 84489e18 00000000 nt!IopSynchronousServiceTail+0x1f8

89813cd0 82cc73f4 84525860 843f0858 00000000 nt!IopXxxControlFile+0x6aa

89813d04 82a901ea 0000015c 00000000 00000000 nt!NtDeviceIoControlFile+0x2a

89813d04 76fd70b4 0000015c 00000000 00000000 nt!KiFastCallEntry+0x12a

0012f7f0 00000000 00000000 00000000 00000000 0x76fd70b4

 

STACK_COMMAND:  kb

THREAD_SHA1_HASH_MOD_FUNC:  1d162d18111a222172b462becb3845e53e690213

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  d3c548c893cd75b9806e6f8940169c9ae8e41dba

THREAD_SHA1_HASH_MOD:  cb5f414824c2521bcc505eaa03e92fa10922dad8

 

FOLLOWUP_IP:

nt!strstr+1e

82ad19fe 8a07            mov     al,byte ptr [edi]

 

FAULT_INSTR_CODE:  c683078a

SYMBOL_STACK_INDEX:  2

SYMBOL_NAME:  nt!strstr+1e

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: nt

IMAGE_NAME:  ntkrpamp.exe

DEBUG_FLR_IMAGE_TIMESTAMP:  4ce78a09

IMAGE_VERSION:  6.1.7601.17514

FAILURE_BUCKET_ID:  0x50_nt!strstr+1e

BUCKET_ID:  0x50_nt!strstr+1e

PRIMARY_PROBLEM_CLASS:  0x50_nt!strstr+1e

TARGET_TIME:  2017-11-28T10:00:12.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: 83a

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:0x50_nt!strstr+1e

FAILURE_ID_HASH:  {955d5f89-fa0e-8221-ae1a-1f21f03e2986}

Followup:     MachineOwner

---------


가장 먼저 .trap 명령을 사용해서 문제가 발생한 부분으로 컨텍스트를 맞춰야한다.

kd> .trap 0xffffffff89812c30

ErrCode = 00000000

eax=ffff0000 ebx=00000000 ecx=9213a600 edx=ffff4d5c esi=ffff0000 edi=ffff0000

eip=82ad19fe esp=89812ca4 ebp=89813bfc 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!strstr+0x1e:

82ad19fe 8a07            mov     al,byte ptr [edi]          ds:0023:ffff0000=??


nt!strstr+0x1e 명령을 수행하다 ffff0000 주소가 유효하지 않아 문제가 발생했다.

kv 명령으로 파라미터를 포함한 콜 스택을 확인해보자.

kd> kv

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

 # ChildEBP RetAddr  Args to Child             

00 89813bfc 82a89593 84525860 843f0858 843f0858 nt!strstr+0x1e

01 89813c14 82c7d99f 84489e18 843f0858 843f08c8 nt!IofCallDriver+0x63

02 89813c34 82c80b71 84525860 84489e18 00000000 nt!IopSynchronousServiceTail+0x1f8

03 89813cd0 82cc73f4 84525860 843f0858 00000000 nt!IopXxxControlFile+0x6aa

04 89813d04 82a901ea 0000015c 00000000 00000000 nt!NtDeviceIoControlFile+0x2a

05 89813d04 76fd70b4 0000015c 00000000 00000000 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 89813d34)

06 0012f7f0 00000000 00000000 00000000 00000000 0x76fd70b4


콜 스택을 보면 IofCallDriver 함수 내부에서 호출된 strstr 함수를 수행하다 문제가 발생한 것으로 보인다.

얼핏 봐서는 이상한 부분이 전혀 없지만 실은 굉장히 이상한 상황이다. IofCallDriver 함수 내부에는 strstr 함수를 부르는 곳이 없기 때문이다.

이게 도대체 무슨 소리인가? 부르지도 않은 함수가 콜 스택에 표시된다니 정말 말도 안 되는 상황이다. 사실 여부를 확인하기 위해 IofCallDriver 함수의 디스어셈블리 코드를 살펴 보자.

kd> u nt!IofCallDriver L30

nt!IofCallDriver:

82a8952f 8bff            mov     edi,edi

82a89531 55              push    ebp

82a89532 8bec            mov     ebp,esp

82a89534 51              push    ecx

82a89535 a15ccabb82      mov     eax,dword ptr [nt!pIofCallDriver (82bbca5c)]

82a8953a 56              push    esi

82a8953b 8bf1            mov     esi,ecx

82a8953d 33c9            xor     ecx,ecx

82a8953f 3bc1            cmp     eax,ecx

82a89541 7409            je      nt!IofCallDriver+0x1d (82a8954c)

82a89543 ff7504          push    dword ptr [ebp+4]

82a89546 8bce            mov     ecx,esi

82a89548 ffd0            call    eax

82a8954a eb47            jmp     nt!IofCallDriver+0x63 (82a89593)

82a8954c fe4a23          dec     byte ptr [edx+23h]

82a8954f 384a23          cmp     byte ptr [edx+23h],cl

82a89552 7f0c            jg      nt!IofCallDriver+0x30 (82a89560)

82a89554 51              push    ecx

82a89555 51              push    ecx

82a89556 51              push    ecx

82a89557 52              push    edx

82a89558 6a35            push    35h

82a8955a e8a3790a00      call    nt!KeBugCheckEx (82b30f02)

82a8955f cc              int     3

82a89560 8b4260          mov     eax,dword ptr [edx+60h]

82a89563 83e824          sub     eax,24h

82a89566 894260          mov     dword ptr [edx+60h],eax

82a89569 8a08            mov     cl,byte ptr [eax]

82a8956b 897014          mov     dword ptr [eax+14h],esi

82a8956e 80f916          cmp     cl,16h

82a89571 7514            jne     nt!IofCallDriver+0x57 (82a89587)

82a89573 8a4001          mov     al,byte ptr [eax+1]

82a89576 3c02            cmp     al,2

82a89578 7404            je      nt!IofCallDriver+0x4e (82a8957e)

82a8957a 3c03            cmp     al,3

82a8957c 7509            jne     nt!IofCallDriver+0x57 (82a89587)

82a8957e 8bf2            mov     esi,edx

82a89580 e8b6f2fdff      call    nt!IopPoHandleIrp (82a6883b)

82a89585 eb0c            jmp     nt!IofCallDriver+0x63 (82a89593)

82a89587 8b4608          mov     eax,dword ptr [esi+8]

82a8958a 52              push    edx

82a8958b 0fb6c9          movzx   ecx,cl

82a8958e 56              push    esi

82a8958f ff548838        call    dword ptr [eax+ecx*4+38h]

82a89593 5e              pop     esi

82a89594 59              pop     ecx

82a89595 5d              pop     ebp

82a89596 c3              ret


내 눈에는 strstr 함수를 호출하는 부분이 보이지 않는다. call dword ptr [eax+ecx*4+38h] 부분이 조금 의심스럽긴하다. 디스어셈블리 코드가 어려우니 간단한 의사 코드(Pseudo code)로 바꾼 코드를 보자.

NTSTATUS

IofCallDriver(

    IN PDEVICE_OBJECT DeviceObject,

    IN OUT PIRP Irp

    )

{

    PIO_STACK_LOCATION pIoStackLocation;

    PDRIVER_OBJECT pDriverObject;

    NTSTATUS ntStatus;

 

    /* 조건에 따른 pIofCalldriver 함수 호출 */

 

    Irp->CurrentLocation--;

 

    // NO_MORE_IRP_STACK_LOCATIONS(0x35) 에러 처리

    if (Irp->CurrentLocation <= 0)

    {

        KiBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, Irp, 0, 0, 0);

    }

   

    pIoStackLocation = IoGetNextIrpStackLocation( Irp );

    Irp->Tail.Overlay.CurrentStackLocation = pIoStackLocation;

    pIoStackLocation->DeviceObject = DeviceObject;

    pDriverObject = DeviceObject->DriverObject;

 

    // IRP 요청에 따른 드라이버의 디스패치 루틴 함수 호출

    ntStatus = pDriverObject->MajorFunction[pIoStackLocation->MajorFunction](DeviceObject, Irp);

 

    /* 조건에 따른 IopPoHandleIrp 함수 호출 */

 

    return ntStatus;

}


어떤가? 어디에도 strstr 함수는 보이지 않는다. 나도 처음에는 굉장히 당황스럽고 혼란스러웠다.

다행히 예전에 IofCallDriver 함수를 분석해 본 경험이 있어 이상하다고 생각할 수 있었다.

이런 상황이라면 스택 정보에 문제가 있어 WinDbg가 정상적인 콜 스택을 보여주지 못하는 것은 아닌지 의심해볼 필요가 있다.

 

스택 정보를 담고 있는 곳은 esp(64비트는 rsp). dps esp 명령을 사용하면 콜 스택을 보는 k 명령으로 보여주지 않는 원본 스택 내용을 출력할 수 있다.

이 방법을 통해 스택에서 진짜 함수 호출의 흔적을 찾을 수 있다.

kd> dps esp

89812ca4  84525860

89812ca8  00000000

89812cac  84489e18

89812cb0  9213a4d1 MyDrv+0x44d1                // 2) 의심 부분

89812cb4  ffff0000

89812cb8  9213a600 MyDrv+0x4600                // 1) 의심 부분

89812cbc  00000060

89812cc0  ffff0000

89812cc4  00000000

89812cc8  00000000

89812ccc  0000000a

89812cd0  00000000

89812cd4  88173470

89812cd8  00000000

89812cdc  00000000

89812ce0  89812ca0

89812ce4  8454ba60

89812ce8  8d7e19ea

89812cec  9e50224a

89812cf0  88e1bb88

89812cf4  00000006

89812cf8  87148cde Ntfs!NtfsReadMftRecord+0x236

89812cfc  88e1ba98

89812d00  92d7c6a0

89812d04  8d7e1800

89812d08  00080008

89812d0c  89812d3c

89812d10  87163f6d Ntfs!NtfsFileIsEqual+0x56

89812d14  89812d2c

89812d18  89812d34

89812d1c  00000001

89812d20  896d4000


놀랍게도 콜 스택에 보이지 않던 MyDrv모듈의 함수 호출 흔적이 확인된다.

함수가 호출되면 esp에 함수 수행 후 복귀할 리턴 주소인 함수 호출 명령 다음 위치가 저장된다는 사실을 떠올려보자.

esp MyDrv 관련된 주소 두 곳이 보이는데, ub 명령으로 역 디스어셈블링했을 때 strstr 함수를 호출하는 부분이 바로 리턴 주소다.

1)번 위치인 MyDrv+4600 먼저 확인해보자.

kd> ub MyDrv+0x4600

MyDrv+0x45f8:

9213a5f8 cc              int     3

9213a5f9 cc              int     3

9213a5fa cc              int     3

9213a5fb cc              int     3

9213a5fc cc              int     3

9213a5fd cc              int     3

9213a5fe cc              int     3

9213a5ff cc              int     3


strstr 함수 호출 부분이 확인되지 않으니 찾는 부분이 아니다.

다음 2)번 위치인 MyDrv+44d1을 확인해보자.

kd> ub MyDrv+44d1

MyDrv+0x44ab:

9213a4ab c7411c00000000  mov     dword ptr [ecx+1Ch],0

9213a4b2 eb57            jmp     MyDrv+0x450b (9213a50b)

9213a4b4 e857cbffff      call    MyDrv+0x1010 (92137010)

9213a4b9 8985c4f0ffff    mov     dword ptr [ebp-0F3Ch],eax

9213a4bf 6800a61392      push    offset MyDrv+0x4600 (9213a600)

9213a4c4 8b95c4f0ffff    mov     edx,dword ptr [ebp-0F3Ch]

9213a4ca 52              push    edx

9213a4cb ff1558801392    call    dword ptr [MyDrv+0x2058 (92138058)]


의미있는 정보가 나온다. 마지막 시점에 MyDrv+0x2058(92138058)가 가리키는 주소로 함수를 호출한다.

dword ptr 명령으로 92138058 주소를 참조하고 있으니 poi 명령으로 함수 호출한 부분을 확인해보자.

kd> u poi(92138058)

nt!strstr:

82ad19e0 8b4c2408        mov     ecx,dword ptr [esp+8]

82ad19e4 57              push    edi

82ad19e5 53              push    ebx

82ad19e6 56              push    esi

82ad19e7 8a11            mov     dl,byte ptr [ecx]

82ad19e9 8b7c2410        mov     edi,dword ptr [esp+10h]

82ad19ed 84d2            test    dl,dl

82ad19ef 746f            je      nt!strstr+0x80 (82ad1a60)


빙고! strstr 함수가 나온다. 스택의 2)번 위치인 MyDrv+0x44d1에서 strstr 함수를 호출했음이 밝혀졌다.

이제 문제 발생 원인에 초점을 맞춰보자. MyDrv 모듈에서 strstr 함수 호출시 전달한 파라미터에 문제가 있었을 가능성이 높다.

strstr 함수는 원본 문자열에 검색 문자열이 있을 경우 일치하는 원본 문자열의 첫 번째 위치를 리턴해주는데 함수 원형은 다음과 같다.

PTSTR StrStr(

    _In_ PTSTR  pszFirst,

    _In_ PCTSTR pszSrch

);


함수 원형은 확인했으니 다시 dps esp 명령으로 스택 정보를 확인해보자.

kd> dps esp L10

89812ca4  84525860

89812ca8  00000000

89812cac  84489e18

89812cb0  9213a4d1 MyDrv+0x44d1         // esp+c : 리턴 어드레스

89812cb4  ffff0000                      // esp+10 : 번째 파라미터

89812cb8  9213a600 MyDrv+0x4600         // esp+14 : 번째 파라미터

89812cbc  00000060

89812cc0  ffff0000

89812cc4  00000000

89812cc8  00000000

89812ccc  0000000a

89812cd0  00000000

89812cd4  88173470

89812cd8  00000000

89812cdc  00000000

89812ce0  89812ca0


원본 문자열인 pszFirst ffff0000이고, 검색할 문자열인 pszSrch 9213a600이다.

문제가 되는 함수가 문자열 함수여서 da 명령으로 파라미터 문자열을 출력해 보았다.

kd> da ffff0000

ffff0000  "????????????????????????????????"

ffff0020  "????????????????????????????????"

ffff0040  "????????????????????????????????"

ffff0060  "????????????????????????????????"

ffff0080  "????????????????????????????????"

ffff00a0  "????????????????????????????????"

ffff00c0  "????????????????????????????????"

ffff00e0  "????????????????????????????????"

ffff0100  "????????????????????????????????"

ffff0120  "????????????????????????????????"

ffff0140  "????????????????????????????????"

ffff0160  "????????????????????????????????"

 

kd> da 9213a600

9213a600  "\Microsoft\Windows\Burn\"


원본 문자열인 첫 번재 파라미터의 주소는 페이지 아웃되어 ??로 표시된다. 접근할 수 없는 영역이라는 의미다. 검색 문자열인 두 번째 파라미터에는 정상적인 경로 문자열이 확인된다.

잘못된 주소인 ffff0000에서 "\Microsoft\Windows\Burn\" 문자열이 있는지 찾으려고 시도하다 문제가 발생한 것이 원인이다.

그렇다면, 원본 문자열 주소인 ffff0000는 어디서 온 것일까? ffff0000 strstr 함수에 넘긴 첫 번째 파라미터니 strstr 함수를 호출한 MyDrv+44d1 부분을 다시 살펴보자.

kd> ub MyDrv+44d1

MyDrv+0x44ab:

9213a4ab c7411c00000000  mov     dword ptr [ecx+1Ch],0

9213a4b2 eb57            jmp     MyDrv+0x450b (9213a50b)

9213a4b4 e857cbffff      call    MyDrv+0x1010 (92137010)  // 1) 내부 함수 호출

9213a4b9 8985c4f0ffff    mov     dword ptr [ebp-0F3Ch],eax  // 2) eax ebp-f3c 저장

9213a4bf 6800a61392      push    offset MyDrv+0x4600 (9213a600)  // 3) MyDrv+0x4600 번째 파라미터로 push

9213a4c4 8b95c4f0ffff    mov     edx,dword ptr [ebp-0F3Ch]  // 4) ebp-f3c edx 저장

9213a4ca 52              push    edx                      // 5) edx 번째 파라미터로 push

9213a4cb ff1558801392    call    dword ptr [MyDrv+0x2058 (92138058)]       // 6) strstr 함수 호출


2)번을 보면 eax 값이 ebp-f3c에 저장되고, 4)번에서 다시 edx로 옮겨진다. 그리고 5)번을 보면 edx가 첫 번째 파라미터로 넘겨졌음을 알 수 있다.

eax 가 첫 번째 파라미터이므로 2)번 앞 쪽에서 eax를 설정한 곳을 찾아봐야 한다.

1)번에 함수가 하나 호출되는데 그 안에서 eax가 설정되었을 가능성이 높다. 보통 eax는 함수 호출 완료 후 리턴 값을 저장하는 용도로 사용되기 때문이다.

1)번에서 호출된 함수 주소인 92137010을 살펴보자.

kd> u 92137010 L6

MyDrv+0x1010:

92137010 8bff            mov     edi,edi

92137012 55              push    ebp

92137013 8bec            mov     ebp,esp

92137015 b80000ffff      mov     eax,0FFFF0000h         // ffff0000 eax 저장

9213701a 5d              pop     ebp

9213701b c3              ret


이런! 이 함수에서 ffff0000 값을 리턴하고 있다. 이 값이 결국 strstr 함수의 첫 번째 파라미터로 전달되어 문제가 발생한 것이다.

이제 ffff0000을 리턴한 문제의 함수를 확인해서 정상적인 값을 리턴하도록 변경하면 문제가 해결될 것이다.

 

나는 앞서 원인 분석에 집중하기 위해 의도적으로 숨겨진 콜 스택이 발생한 이유에 대해서는 언급하지 않았다. 이제 문제 발생 원인에 대한 분석이 끝났으니 그 얘기를 마저 해보겠다.

콜 스택을 확인하는 k 명령으로 MyDrv의 콜 스택이 제대로 확인되지 않았던 이유는 strstr 함수가 컴파일된 방식에 원인이 있다.

다음은 strstr 함수와 memcpy 함수의 앞 부분을 비교한 내용이다.

<nt!strstr 함수 프롤로그>

kd> u nt!strstr

nt!strstr:

82ac59e0 8b4c2408        mov     ecx,dword ptr [esp+8]

82ac59e4 57              push    edi

82ac59e5 53              push    ebx

82ac59e6 56              push    esi

82ac59e7 8a11            mov     dl,byte ptr [ecx]

82ac59e9 8b7c2410        mov     edi,dword ptr [esp+10h]

82ac59ed 84d2            test    dl,dl

82ac59ef 746f            je      nt!strstr+0x80 (82ac5a60)

 

<nt!memcpy 함수 프롤로그>

kd> u nt!memcpy

nt!memcpy:

82a7f7c0 55              push    ebp

82a7f7c1 8bec            mov     ebp,esp

82a7f7c3 57              push    edi

82a7f7c4 56              push    esi

82a7f7c5 8b750c          mov     esi,dword ptr [ebp+0Ch]

82a7f7c8 8b4d10          mov     ecx,dword ptr [ebp+10h]

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

82a7f7ce 8bc1            mov     eax,ecx


어떤 차이가 보이는가? 32비트 환경에서 함수 호출을 하게 되면 기본적으로 스택의 베이스 포인터인 ebp를 기준으로 스택을 구성하게 된다.

memcpy 함수처럼 "push ebp, mov ebp, esp" 로 원본 ebp를 스택에 저장하고 현재 esp를 기준으로 ebp를 재설정하는 패턴이 함수 시작시 수행된다.

k 명령도 이 ebp 를 기준으로 콜 스택을 보여준다. 하지만 strstr 함수의 경우 재미있게도 ebp를 사용하지 않고 64비트 환경처럼 esp 를 기준으로 함수가 동작하게 컴파일되어 있다.

다음은 CompareString 함수 안에서 strstr 함수를 호출하는 테스트 코드를 만들어 콜 스택을 확인한 결과다.


1. CompareString 함수에서 strstr 함수 호출 전 콜 스택 

kd> kv

 # ChildEBP RetAddr  Args to Child             

00 92237bc8 913da27b 00000388 00000388 82b7d944 MyDrv!CompareString+0x37 (FPO: [Non-Fpo]) (CONV: stdcall)

01 92237d28 82a841ea 00000388 01f4f900 771670b4 MyDrv!TestMain+0x4b (FPO: [Non-Fpo]) (CONV: stdcall)

02 92237d28 771670b4 00000388 01f4f900 771670b4 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 92237d34)

 

kd> ub 913d9d3e

MyDrv!CompareString+0x26

913d9d27 7507            jne     MyDrv!CompareString+0x2f (913d9d30)

913d9d29 32c0            xor     al,al

913d9d2b e9b3000000      jmp     MyDrv!CompareString+0xe2 (913d9de3)

913d9d30 8b55d0          mov     edx,dword ptr [ebp-30h]

913d9d33 52              push    edx

913d9d34 8b45dc          mov     eax,dword ptr [ebp-24h]

913d9d37 50              push    eax

913d9d38 ff15f4103e91    call    dword ptr [MyDrv!_imp__strstr (913e10f4)]  // 현재 strstr 함수를 호출하기 직전 상태


2. CompareString 함수에서 strstr 함수 호출 직후 콜 스택 

kd> kv

 # ChildEBP RetAddr  Args to Child             

00 92237bc8 913da27b 00000388 00000388 82b7d944 nt!strstr

01 92237d28 82a841ea 00000388 01f4f900 771670b4 MyDrv!TestMain+0x4b (FPO: [Non-Fpo]) (CONV: stdcall)

02 92237d28 771670b4 00000388 01f4f900 771670b4 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 92237d34)


예상대로 strstr 함수 내부에서 ebp를 설정하는 부분이 없으므로 CompareString 함수에 대한 콜 스택이 표시되지 않고 사라졌다.

이처럼 함수에 따라 예상과는 다른 코드 패턴으로 컴파일될 수 있다는 사실을 기억하자.

이 외에도 스택 오버플로우나 jmp를 이용한 코드 후킹 기법 등에 의해 스택이나 함수 프롤로그가 망가지면 ebp 기준으로 해석하는 k 명령은 콜 스택을 제대로 표시하지 못할 수 있다.

뭔가 콜 스택이 이상하다고 느껴지면 주저없이 dps esp(64비트는 rsp) 명령으로 스택을 확인하거나 k=address 명령으로 콜 스택을 재구성해보자. 의외의 단서를 얻을 수 있을 것이다.





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

[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 해제된 핸들  (0) 2018.07.09
[0x50] UNICODE_STRING  (0) 2018.07.05
필터매니저(fltmgr.sys) 버그? DRAINING ZOMBIED 와 DOE_UNLOAD_PENDING  (0) 2015.01.22
2015. 1. 22. 23:57

아주 흥미로운 덤프를 분석하게 되서 공유한다.


거의 분석 불가능한 덤프였지만 다행히 분석이 완료되었다.

MyDrv.sys 를 언로드하려고 했지만 알 수 없는 이유로 실패한 상태로 메모리에 남아 있는 이슈여서 강제로 덤프를 생성하여 분석하게 된 Case 다.


참고 : 드라이버 로드시 ERROR_ALREADY_EXISTS(183) 에러



1. 일단 필터매니저에 등록된 필터 정보를 확인해 본다.

kd> !fltkd.frames

Frame List: fffff880013302c0
    FLTP_FRAME: fffffa80039e5010 "Frame 1" "429998.99 to 429999.280700"
    FLT_FILTER: fffffa8006dcc960 "SomeDrv" "[NoName]"
    FLTP_FRAME: fffffa8001b2aac0 "Frame 0" "0 to 429998.99"   
    FLT_FILTER: fffffa800290d900 "MyDrv" "326020"
    FLT_INSTANCE: fffffa80031b4330 "MyDrv Instance" "326020"
    FLT_INSTANCE: fffffa800699d170 "MyDrv Instance" "326020"
    FLT_INSTANCE: fffffa8003658b40 "MyDrv Instance" "326020"



어라, Frame 1 의 SomeDrv.sys 모듈의 상태가 이상하다.
Filter Instance 정보는 없고 Filter Object 정보만 남아 있다.


2. 일단 언로드되지 못하고 있는 불쌍한 MyDrv.sys 를 먼저 확인해 보자.

kd> dt _FLT_FILTER fffffa800290d900
fltmgr!_FLT_FILTER
    +0x000 Base : _FLT_OBJECT
    +0x020 Frame : 0xfffffa80`01b2aac0 _FLTP_FRAME   
    +0x028 Name : _UNICODE_STRING "MyDrv"
    +0x038 DefaultAltitude : _UNICODE_STRING "326020"
    +0x048 Flags : 2 ( FLTFL_FILTERING_INITIATED )
    +0x050 DriverObject : 0xfffffa80`03673c80 _DRIVER_OBJECT
    ... ...


kd> !drvobj 0xfffffa80`03673c80
Driver object (fffffa8003673c80) is for:
    \Driver\MyDrv
Driver Extension List: (id , addr)

Device Object list:
fffffa8002e8f550


kd> !devobj fffffa8002e8f550
Device object (fffffa8002e8f550) is for:
    MyDrv \Driver\MyDrv DriverObject fffffa8003673c80
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00000040
Dacl fffff9a1000846e1 DevExt 00000000 DevObjExt fffffa8002e8f6a0
ExtensionFlags (0x00000801) DOE_UNLOAD_PENDING, DOE_DEFAULT_SD_PRESENT
Characteristics (0000000000)
Device queue is not busy.



역시 언로드되지 못하고 언로드 중인 상태로 남아 있다.
일단 내가 제일 의심스러워 MyDrv.sys 의 언로드 루틴을 검토했지만 버그로 보일만한 부분은 발견되지 않았다.


3. 이제 이상했던 SomeDrv.sys 모듈의 Filter Object 정보를 확인해보자.

kd> !fltkd.filter fffffa8006dcc960

FLT_FILTER: fffffa8006dcc960 "SomeDrv" "[NoName]"
    FLT_OBJECT: fffffa8006dcc960 [02000003] Filter DRAINING ZOMBIED

        RundownRef : 0x0000000000000001 (0) drained
        PointerCount : 0x00000001
        PrimaryLink : [fffffa800b400020-fffffa80039e50c0]
    Frame : fffffa80039e5010 "Frame 1"
    Flags : [00000002] FilteringInitiated
    DriverObject : 0xfffffa8003673c80
    FilterLink : [fffffa800b400020-fffffa80039e50c0]
    PreVolumeMount : 0000000000000000 (null)
    PostVolumeMount : 0000000000000000 (null)
    ... ...



Filte Object 가 "DRAINING ZOMBIED" 상태로 확인된다.
참고로 "DRAINING ZOMBIED" 플래그는 필터매니저가 언로드 과정 중에 내부적으로 설정하는 상태 값이다.
즉, 언로드 과정에서 이 플래그가 설정되는 것 자체는 정상적인 동작이다.


참고로, 미니필터에서 필터매니저에 Context 를 할당(FltAllocateContext )하거나 사용(FltSetStreamContext)하고 해제(FltReleaseContext)하지 않는 경우 필터매니저가 내부적으로 관리하는 ReferenceCount 가 맞지 않게 되어 언로드시 해당 미니필터의 Filter Object 가 "DRAINED" 상태로 언로드에 실패할 수 있다.
(언로드가 완료되지 못한 상태로 메모리에 남아 있게 된다)


다시 본론으로 돌아와서, 문제는 덤프 상의 SomeDrv.sys 는 이미 언로드되어 버린(메모리 상에 없는) 녀석인데도 "DRAINING ZOMBIED" 상태인 Filter Object 가 여전히 남아 있다는 것이다.
(언로드가 완료되면 Filter Object 는 제거되고, Filter Frame 에 더 이상 등록된 Filter 가 없으면 해당 Frame 도 제거되야 한다)

이미 SomeDrv.sys 는 언로드되어 메모리 상에서 없어졌기 때문에 이쯤되면 분석이 거의 불가능한 상태다.

SomeDrv.sys 가 "DRAINING ZOMBIED" 상태인 것과 MyDrv.sys 의 Unload Pending 증상 사이에 어떤 연관성이 있을까?

다행히 다른 시스템에서 MyDrv.sys 가 또 Unload Pending 에 빠진 덤프를 얻을 수 있었다.
SomeDrv.sys 도 있고 SomeDrv.sys 역시 동일하게 "DRAINING ZOMBIED" 상태에 빠져있다.


이쯤되니 다시 덤프를 꺼내 MyDrv.sys, SomeDrv.sys, 필터매니저(fltmgr.sys), 심지어 nt 커널까지 모든 걸 의심의 눈초리로 바라보며 다음과 같은 가설을 세워보기로 했다.


1) SomeDrv.sys 의 버그로 언로드시 "DRAINING ZOMBIED" 상태가 됨

2) SomeDrv.sys 가 "DRAINING ZOMBIED" 가 되는 과정에서 필터매니저나 nt 커널의 데이터가 손상됨

3) 데이터 손상으로 인한 필터매니저나 nt 커널의 오동작으로 MyDrv.sys 가 언로드되지 못함
 


음... 입증하기가 만만치 않아 보인다.
먼저 StopService() 호출 후 MyDrv.sys 의 언로드 루틴이 호출되기까지 언로드 과정을 분석해보니 다음과 같은 콜 스택으로 진행되었다.


08 MyDrv!DriverUnload
07 fltmgr!FltUnregisterFilter
06 MyDrv!MiniFilterUnload
05 fltmgr!FltpDoUnloadFilter
04 fltmgr!FltpMiniFilterDriverUnload
03 nt!IopLoadUnloadDriver                  (System)               
02 nt!IopUnloadDriver                         (Services.exe)
... ...
01 services!StopService



각 함수에서 하는 일을 정리해보면 다음과 같다.


1) 유저모드에서 MyDrv.sys 에 대해 StopService() 호출

2) nt 커널의 언로드 함수인 nt!IopUnloadDriver() 내부에서 언로드할 드라이버의 DeviceObject 에 "DOE_UNLOAD_PENDING"
플래그를 설정하고 WorkItem 으로 nt!IopLoadUnloadDriver() 호출
(이후 과정은 System Thread Context 에서 수행)


nt!IopUnloadDriver+0x37e (fffff800`01a79780):
    call to nt!IopCheckUnloadDriver (fffff800`0175c750)

kd> u nt!IopCheckUnloadDriver L25
nt!IopCheckUnloadDriver:
... ...
fffff800`0175c7bd 8bc3 mov eax,ebx
fffff800`0175c7bf eb49 jmp nt!IopCheckUnloadDriver+0xba (fffff800`0175c80a)
fffff800`0175c7c1 c60601 mov byte ptr [rsi],1
fffff800`0175c7c4 eb1c jmp nt!IopCheckUnloadDriver+0x92 (fffff800`0175c7e2)
fffff800`0175c7c6 488b8138010000 mov rax,qword ptr [rcx+138h]
fffff800`0175c7cd 83482001 or dword ptr [rax+20h],1                 DOE_UNLOAD_PENDING



3) nt!IopLoadUnloadDriver() 에서 필터매니저의 fltmgr!FltpMiniFilterDriverUnload() 호출

4) fltmgr!FltpMiniFilterDriverUnload() 에서 언로드할 드라이버를 찾아 fltmgr!FltpDoUnloadFilter() 호출

5) fltmgr!FltpDoUnloadFilter() 에서 필터매니저가 관리하는 Filter Object 에 "UnloadInProgress" 플래그를 설정하고 MyDrv.sys 의 언로드 함수인 MyDrv!MiniFilterUnload() 호출

kd> u fltmgr!FltpDoUnloadFilter L25
fltmgr!FltpDoUnloadFilter:
... ...
fffff880`01344d7f 41bc10001cc0 mov r12d,0C01C0010h
fffff880`01344d85 e9e7000000 jmp fltmgr!FltpDoUnloadFilter+0x161 (fffff880`01344e71)
fffff880`01344d8a 8bfa mov edi,edx
fffff880`01344d8c 83e701 and edi,1
fffff880`01344d8f 7404 je fltmgr!FltpDoUnloadFilter+0x85 (fffff880`01344d95)
fffff880`01344d91 83494801 or dword ptr [rcx+48h],1                 UnloadInProgress


6) MyDrv.sys 의 언로드 함수인 MyDrv!MiniFilterUnload() 에서 이후 언로드 작업 수행


이제 각 언로드 과정을 하나씩 검증해가며 문제가 없는지 분석해보자.

MyDrv.sys 의 DeviceObject 에 "DOE_UNLOAD_PENDING" 플래그는 이미 확인했으니 1)~2)번은 범인이 아니다.

6)번 과정인 MyDrv.sys 의 MyDrv!MiniFilterUnload() 함수는 MyDrv.sys 의 내부 언로드 관련 플래그를 확인해보니 아직 호출되지 않은 것으로 확인된다.

MyDrv.sys 도 범인이 아니고 가설대로면 nt 커널과 필터매니저 구간인 3)~5)번에 범인이 있을 것이다.


4. MyDrv.sys 의 Filter Object 상태를 확인해보자.

kd> !fltkd.filter fffffa800290d900

FLT_FILTER: fffffa800290d900 "MyDrv" "326020"
    FLT_OBJECT: fffffa800290d900 [02000000] Filter
        RundownRef : 0x0000000000000044 (34)
        PointerCount : 0x00000001
        PrimaryLink : [fffffa8004770020-fffffa8001cb5390]
    Frame : fffffa8001b2aac0 "Frame 0"
    Flags : [00000002] FilteringInitiated
        ... ...



응? Filter Object 에 "FilteringInitiated" 플래그만 있고 "UnloadInProgress" 플래그가 없다.
5)번 과정도 수행되지 않았다고 보는 것이 타당하다.
5)번 과정이 수행되었다면 Filter Object 의 플래그가 "FilteringInitiated UnloadInProgress" 로 변경되었을 것이다.

범인은 3)번이나 4)번일 가능성이 높다.

먼저 3)번의 nt!IopLoadUnloadDriver() 를 분석해보니 특별히 문제될만한 동작을 수행하는 함수가 아니었다.

그래서 4)번 fltmgr!FltpMiniFilterDriverUnload() 함수를 상세 분석해보기로 했다.


IDA 의 Hex-ray 없이 커널 함수를 디스어셈하여 분석하는건 굉장히 지루한 작업이지만 나에게 있어선 분석의 감을 잃지 않게 해주는 습관이기도 하다.


5. 마지막 남은 fltmgr!FltpMiniFilterDriverUnload() 함수를 분석해보자.

fltmgr!FltpMiniFilterDriverUnload:
fffff880`01344fb0 488bc4 mov rax,rsp
fffff880`01344fb3 48895808 mov qword ptr [rax+8],rbx
fffff880`01344fb7 48896810 mov qword ptr [rax+10h],rbp
fffff880`01344fbb 48897018 mov qword ptr [rax+18h],rsi
fffff880`01344fbf 48897820 mov qword ptr [rax+20h],rdi
fffff880`01344fc3 4155 push r13
fffff880`01344fc5 4883ec20 sub rsp,20h
fffff880`01344fc9 488be9 mov rbp,rcx ; rcx : DriverObject
fffff880`01344fcc ff154611feff call qword ptr [fltmgr!_imp_KeEnterCriticalRegion (fffff880`01326118)]
fffff880`01344fd2 488d0d7fb2feff lea rcx,[fltmgr!FltGlobals+0x58 (fffff880`01330258)]
                                                ; 0 FLT_FRAME.FilterUnloadLock (ERESOURCE)
fffff880`01344fd9 b201 mov dl,1
fffff880`01344fdb ff152f11feff call qword ptr [fltmgr!_imp_ExAcquireResourceSharedLite (fffff880`01326110)]
fffff880`01344fe1 488b3dd8b2feff mov rdi,qword ptr [fltmgr!FltGlobals+0xc0 (fffff880`013302c0)]
                                                ; 1 FLT_FRAME.Links
fffff880`01344fe8 4c8d2dd1b2feff lea r13,[fltmgr!FltGlobals+0xc0 (fffff880`013302c0)]
                                                ; 1 FLT_FRAME.Links->FLink (FLT_FRAME 0 .Links)
fffff880`01344fef eb43 jmp fltmgr!FltpMiniFilterDriverUnload+0x84 (fffff880`01345034)
fffff880`01344ff1 ff152111feff call qword ptr [fltmgr!_imp_KeEnterCriticalRegion (fffff880`01326118)]
fffff880`01344ff7 488d4f40 lea rcx,[rdi+40h]
                                                ; 1 FLT_FRAME.RegisteredFilters (ERESOURCE)
fffff880`01344ffb b201 mov dl,1
fffff880`01344ffd ff150d11feff call qword ptr [fltmgr!_imp_ExAcquireResourceSharedLite (fffff880`01326110)]
fffff880`01345003 4c8d9fa8000000 lea r11,[rdi+0A8h]
                                                ; 1 FLT_FRAME.RegisteredFilters.rList (FLT_FILTER.PrimaryLink)
fffff880`0134500a 498b03 mov rax,qword ptr [r11]
                                                ; FLT_FILTER.PrimaryLink.FLink (FLT_FILTER.PrimaryLink)
fffff880`0134500d eb0d jmp fltmgr!FltpMiniFilterDriverUnload+0x6c (fffff880`0134501c)
fffff880`0134500f 488d70f0 lea rsi,[rax-10h]                      ; FLT_FILTER
fffff880`01345013 48396e50 cmp qword ptr [rsi+50h],rbp    ; if( FLT_FILTER.DriverObject == DriverObject )
fffff880`01345017 744e je fltmgr!FltpMiniFilterDriverUnload+0xb7 (fffff880`01345067)        ; Success

fffff880`01345019 488b00 mov rax,qword ptr [rax]
fffff880`0134501c 493bc3 cmp rax,r11                             ; Filter List 끝인지 확인
fffff880`0134501f 75ee jne fltmgr!FltpMiniFilterDriverUnload+0x5f (fffff880`0134500f)
fffff880`01345021 488d4f40 lea rcx,[rdi+40h]                    ; 1 FLT_FRAME.RegisteredFilters (ERESOURCE)
fffff880`01345025 ff15fd10feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`0134502b ff15ef10feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`01345031 488b3f mov rdi,qword ptr [rdi]
fffff880`01345034 493bfd cmp rdi,r13                             ; Frame List 끝인지 확인
fffff880`01345037 75b8 jne fltmgr!FltpMiniFilterDriverUnload+0x41 (fffff880`01344ff1)
fffff880`01345039 488d0d18b2feff lea rcx,[fltmgr!FltGlobals+0x58 (fffff880`01330258)]
                                                                             ; 0 FLT_FRAME.FilterUnloadLock (ERESOURCE)
fffff880`01345040 ff15e210feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`01345046 ff15d410feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`0134504c 488b5c2430 mov rbx,qword ptr [rsp+30h]
fffff880`01345051 488b6c2438 mov rbp,qword ptr [rsp+38h]
fffff880`01345056 488b742440 mov rsi,qword ptr [rsp+40h]
fffff880`0134505b 488b7c2448 mov rdi,qword ptr [rsp+48h]
fffff880`01345060 4883c420 add rsp,20h
fffff880`01345064 415d pop r13
fffff880`01345066 c3 ret

fffff880`01345067 488bce mov rcx,rsi
fffff880`0134506a e8b136fdff call fltmgr!FltObjectReference (fffff880`01318720)
fffff880`0134506f 488d4f40 lea rcx,[rdi+40h]
fffff880`01345073 8bd8 mov ebx,eax
fffff880`01345075 ff15ad10feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`0134507b ff159f10feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`01345081 488d0dd0b1feff lea rcx,[fltmgr!FltGlobals+0x58 (fffff880`01330258)]
fffff880`01345088 ff159a10feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`0134508e ff158c10feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`01345094 85db test ebx,ebx
fffff880`01345096 78b4 js fltmgr!FltpMiniFilterDriverUnload+0x9c (fffff880`0134504c)
fffff880`01345098 41b001 mov r8b,1
fffff880`0134509b ba01000000 mov edx,1
fffff880`013450a0 488bce mov rcx,rsi
fffff880`013450a3 e868fcffff call fltmgr!FltpDoUnloadFilter (fffff880`01344d10)
                                                                            ; Call fltmgr!FltpDoUnloadFilter
fffff880`013450a8 eba2 jmp fltmgr!FltpMiniFilterDriverUnload+0x9c (fffff880`0134504c)
                                                                            ; Go to end of function


fltmgr!FltpMiniFilterDriverUnload() 함수 동작을 정리해보면 다음과 같다.


1) 필터매니저가 관리하는 FLT_FRAME 중 가장 마지막에 생성된 Frame 의 FLT_FILTER 를 검사(Frame 1부터)
    - fltmgr!FltGlobals 전역 변수에서 Frame 에 대한 리스트(FLT_FRAME.Links)를 얻음
    - FLT_FRAME.RegisteredFilters.rList 에서 Frame 에 등록된 Filter 에 대한 리스트(FLT_FILTER.PrimaryLink)를 얻음

2) 언로드 요청된 모듈의 DriverObject 와 FLT_FILTER 의 내부 필드인 DriverObject 의 값이 일치하는지 확인
    - FLT_FILTER.DriverObject

3) 일치할 경우 해당 모듈에 대해 fltmgr!FltpDoUnloadFilter() 를 호출해주고, 다를 경우 마지막 Frame 까지 확인



결론적으로 필터매니저에 등록된 미니필터들은 필터매니저에서 로직상 하나의 Frame 리스트로 관리된다고 볼 수 있다.

하나의 리스트를 순차적으로 검사하며 언로드를 수행하기 때문에 앞 쪽 리스트의 DriverObject 정보가 잘못되면 뒷 쪽 리스트의 미니필터가 언로드되지 못할 가능성이 있다.


이제 마지막 과정만 남았다.

함수 분석 결과 Frame 리스트에는 Frame 0 에 등록된 MyDrv.sys 보다 Frame 1 에 등록된 SomeDrv.sys 가 앞 쪽에 위치하고 있다.
필터매니저가 SomeDrv.sys 에 대해 정말로 잘못된 DriverObject 를 가지고 있었는지만 확인하면 된다.


6. 필터매니저가 보관하고 있는 SomeDrv.sys 의 정보를 확인해보자.

kd> !fltkd.frames

Frame List: fffff880013302c0
FLTP_FRAME: fffffa80039e5010 "Frame 1" "429998.99 to 429999.280700"
FLT_FILTER: fffffa8006dcc960"SomeDrv" "[NoName]"
FLTP_FRAME: fffffa8001b2aac0 "Frame 0" "0 to 429998.99"
FLT_FILTER: fffffa800290d900 "MyDrv" "326020"
FLT_INSTANCE: fffffa80031b4330 "MyDrv Instance" "326020"
FLT_INSTANCE: fffffa800699d170 "MyDrv Instance" "326020"
FLT_INSTANCE: fffffa8003658b40 "MyDrv Instance" "326020"


kd> dt _FLT_FILTER fffffa8006dcc960
fltmgr!_FLT_FILTER
    +0x000 Base : _FLT_OBJECT
    +0x020 Frame : 0xfffffa80`039e5010 _FLTP_FRAME
    +0x028 Name : _UNICODE_STRING "SomeDrv"
    +0x038 DefaultAltitude : _UNICODE_STRING ""
    +0x048 Flags : 2 ( FLTFL_FILTERING_INITIATED )
    +0x050 DriverObject : 0xfffffa80`03673c80 _DRIVER_OBJECT
    ... ...


kd> !drvobj 0xfffffa80`03673c80
Driver object (fffffa8003673c80) is for:
    \Driver\MyDrv
Driver Extension List: (id , addr)

Device Object list:
fffffa8002e8f550



역시, 필터매니저가 어떤 이유로 SomeDrv.sys 의 Filter Object 에 SomeDrv.sys 의 DriverObject 가 아닌 MyDrv.sys 의 DriverObject 를 설정해 놓았다.

결국 MyDrv.sys 를 StopService() 했을 때 필터매니저의 fltmgr!FltpDoUnloadFilter() 에서 언로드를 요청한 MyDrv.sys 가 아닌 SomeDrv.sys 를 언로드한 것이 원인이었다. OTL

언로드 과정 중 4)번 fltmgr!FltpDoUnloadFilter() 부터 잘못되버려 MyDrv.sys 의 언로드 함수인 MyDrv!MiniFilterUnload() 는 호출조차 되지 못한 것이다.


※ 이 필터매니저(fltmgr.sys) 버그는 Microsoft 에 Report 했고 최신 필터매니저 버전에서 fix 되었다.

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

[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 해제된 핸들  (0) 2018.07.09
[0x50] 숨겨진 콜 스택  (0) 2018.07.07
[0x50] UNICODE_STRING  (0) 2018.07.05
prev"" #1 next