이번 덤프는 지속적으로 CPU 사용율이 100%가 유지되는 행(Hang) 증상으로 수집된 덤프다.
이처럼 행과 같은 특수한 상황을 분석하기 위해서는 행 증상이 발생한 상태에서 강제로 BSOD를 발생시켜 덤프를 수집해야 한다.
대체로 행이나 메모리 누수 같은 문제는 당시 시스템 상황을 잘 분석해야 하므로 분석이 까다롭다.
참고로 이번에는 64비트 덤프로 준비해봤다. 혹시 64비트 덤프를 처음 보더라도 분석 방법은 동일하니 긴장하지 말고 시작해보자.
강제로 발생시킨 덤프이기 때문에 !analyze 명령이나 현재 스레드의 콜 스택 정보는 큰 의미가 없다.
행 덤프를 생성할 당시의 시스템 상황을 분석해내는 것이 핵심이다.
우선적으로 !running -it 명령으로 덤프 발생 당시 실행 중이던 스레드와 콜 스택 정보를 확인해보자.
kd> !running -it
System Processors: (000000000000000f)
Idle Processors: (0000000000000000) (0000000000000000) (0000000000000000) (0000000000000000)
Prcbs Current (pri) Next (pri) Idle
0 fffff80002bf8e80 fffffa802b447b60 ( 8) fffff80002c06cc0 ................
# Child-SP RetAddr Call Site
00 fffff880`02d2fcf8 fffff880`02e4d435 nt!KeBugCheckEx
01 fffff880`02d2fd00 fffff800`02d23cce MyDrv+0x1435
02 fffff880`02d2fd40 fffff800`02a77fe6 nt!PspSystemThreadStartup+0x5a
03 fffff880`02d2fd80 00000000`00000000 nt!KiStartSystemThread+0x16
1 fffff880009e8180 fffffa802b3ae060 ( 9) fffff880009f2fc0 ................
# Child-SP RetAddr Call Site
00 fffff880`041a5660 fffff800`02d44578 nt!ExfAcquirePushLockShared+0x21
01 fffff880`041a56e0 fffff800`02d8fef6 nt!ExpGetProcessInformation+0xaf2
02 fffff880`041a5830 fffff800`02d90949 nt!ExpQuerySystemInformation+0xfb4
03 fffff880`041a5be0 fffff800`02a858d3 nt!NtQuerySystemInformation+0x4d
04 fffff880`041a5c20 00000000`7733167a nt!KiSystemServiceCopyEnd+0x13
05 00000000`0504fd58 00000000`00000000 0x7733167a
2 fffff88003763180 fffffa802b3f8b60 ( 9) fffff8800376dfc0 ................
# Child-SP RetAddr Call Site
00 fffff880`057031a8 fffff800`02c7f3e0 kdcom+0x4c5f
01 fffff880`057031b0 00000000`00000002 nt!KdLogBuffer
02 fffff880`057031b8 00000000`00026200 0x2
03 fffff880`057031c0 00000000`00000001 0x26200
04 fffff880`057031c8 fffff800`00b9ca13 0x1
05 fffff880`057031d0 fffff800`00b9be4b kdcom+0x4a13
06 fffff880`05703210 fffff800`00b99699 kdcom+0x3e4b
07 fffff880`05703250 fffff800`00b9cf1b kdcom+0x1699
08 fffff880`057033a0 fffff800`02a9013d kdcom+0x4f1b
09 fffff880`057034d0 fffff800`02a92a3f nt!KdPollBreakIn+0xec
0a fffff880`05703520 fffff800`02a92741 nt!KeUpdateRunTime+0x13f
0b fffff880`05703550 fffff800`02d43f00 nt!KiSecondaryClockInterrupt+0x131
0c fffff880`057036e0 fffff800`02d8fef6 nt!ExpGetProcessInformation+0x472
0d fffff880`05703830 fffff800`02d90949 nt!ExpQuerySystemInformation+0xfb4
0e fffff880`05703be0 fffff800`02a858d3 nt!NtQuerySystemInformation+0x4d
0f fffff880`05703c20 00000000`7733167a nt!KiSystemServiceCopyEnd+0x13
10 00000000`0e30fd58 00000000`00000000 0x7733167a
3 fffff880037d3180 fffffa802b407060 ( 9) fffff880037ddfc0 ................
# Child-SP RetAddr Call Site
00 fffff880`057d46b0 fffff800`02d43f24 nt!ObReferenceObjectSafe+0xf
01 fffff880`057d46e0 fffff800`02d8fef6 nt!ExpGetProcessInformation+0x496
02 fffff880`057d4830 fffff800`02d90949 nt!ExpQuerySystemInformation+0xfb4
03 fffff880`057d4be0 fffff800`02a858d3 nt!NtQuerySystemInformation+0x4d
04 fffff880`057d4c20 00000000`7733167a nt!KiSystemServiceCopyEnd+0x13
05 00000000`0fdffd58 00000000`00000000 0x7733167a
NtQuerySystemInformation 함수의 원형은 다음과 같다.
NTSTATUS WINAPI NtQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
유저 프로세스에서 NtQuerySystemInformation 함수를 사용해서 시스템 정보를 쿼리할 경우 요청한 SystemInformationClass에 맞는 커널 내부 함수가 호출된다.
내부에서 ExpGetProcessInformation 함수가 호출된 것으로 보아 프로세스 정보와 관련된 요청이었을 것이다.
1~3번 프로세서에서 모두 NtQuerySystemInformation 함수가 동작 중이었으므로 이 NtQuerySystemInformation 함수에서 지속적으로 CPU를 사용해서 행을 유발했을 가능성이 매우 높다.
우선 동작 중인 1번 프로세서의 스레드인 fffffa802b3ae060로 컨텍스트를 맞춰 SystemInformationClass가 어떤 값이었는지 확인해보자.
kd> .thread fffffa802b3ae060
Implicit thread is now fffffa80`2b3ae060
kd> kv
*** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr : Args to Child : Call Site
00 fffff880`041a5660 fffff800`02d44578 : fffff880`00000000 00000000`00000000 00000000`00002500 00000000`00000005 : nt!ExfAcquirePushLockShared+0x21
01 fffff880`041a56e0 fffff800`02d8fef6 : 00000000`02170000 00000000`00010000 fffff880`041a5870 00000000`00000000 : nt!ExpGetProcessInformation+0xaf2 // 2) 프로세스 정보 획득 함수 호출
02 fffff880`041a5830 fffff800`02d90949 : 00000000`02170000 00000000`00000000 00000000`0504fed0 00000000`00000000 : nt!ExpQuerySystemInformation+0xfb4 // 1) SystemInformationClass에 따라 분기
03 fffff880`041a5be0 fffff800`02a858d3 : 00000000`00000001 00000000`0504ecb8 00000000`00000001 000007ff`fffdc000 : nt!NtQuerySystemInformation+0x4d
04 fffff880`041a5c20 00000000`7733167a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`041a5c20)
64비트 콜 스택이므로 첫 번째 파라미터를 찾기가 쉽지 않다. 64비트에서는 스택 포인터인 rsp 기준으로 동작하기 때문인데 이로 인해 k 명령으로 보이는 파라미터 값들은 실제 값과는 다르게 표시될 수 있다(5.3.2 숨겨진 콜 스택 케이스 참고).
그래도 최소한 1)번 쯤에서 SystemInformationClass를 비교하는 부분이 있을 것 같다. ExpQuerySystemInformation+0xfb4 부분을 디스어셈블링해서 살펴보자.
kd> u ExpQuerySystemInformation+0xfb4-3a L10
nt!ExpQuerySystemInformation+0xf78:
fffff800`02d8feba 0000 add byte ptr [rax],al
fffff800`02d8febc 89473c mov dword ptr [rdi+3Ch],eax
fffff800`02d8febf eb05 jmp nt!ExpQuerySystemInformation+0xf84 (fffff800`02d8fec6)
fffff800`02d8fec1 e9c5f2ffff jmp nt!ExpQuerySystemInformation+0x249 (fffff800`02d8f18b)
fffff800`02d8fec6 894c2440 mov dword ptr [rsp+40h],ecx
fffff800`02d8feca 8b742444 mov esi,dword ptr [rsp+44h]
fffff800`02d8fece e9b1f2ffff jmp nt!ExpQuerySystemInformation+0x242 (fffff800`02d8f184)
fffff800`02d8fed3 4183fa05 cmp r10d,5 // 1) SystemInformationClass가 5인지 비교
fffff800`02d8fed7 0f85b93e0500 jne nt! ?? ::NNGAKEGL::`string'+0x58c58 (fffff800`02de3d96) // 2) 5가 아니면 다음 위치로 이동
fffff800`02d8fedd 32c0 xor al,al
fffff800`02d8fedf 88442420 mov byte ptr [rsp+20h],al
fffff800`02d8fee3 4533c9 xor r9d,r9d
fffff800`02d8fee6 4c8d442440 lea r8,[rsp+40h]
fffff800`02d8feeb 418bd5 mov edx,r13d
fffff800`02d8feee 488bcf mov rcx,rdi
fffff800`02d8fef1 e89a3bfbff call nt!ExpGetProcessInformation (fffff800`02d43a90) // 3) 5인 경우 ExpGetProcessInformation 함수 호출
한 번에 찾아서 다행이다. 콜 스택을 보면 ExpGetProcessInformation 함수가 호출되었으므로 첫 번째 파라미터인 SystemInformationClass는 5다.
그렇다면 이제 SystemInformationClass 5번이 무엇을 의미하는지 알아야 한다.
typedef enum SYSTEM_INFORMATION_CLASS {
SystemBasicInformation, // 0
SystemProcessorInformation, // 1
SystemPerformanceInformation, // 2
SystemTimeOfDayInformation, // 3
SystemPathInformation, // 4
SystemProcessesAndThreadsInformation, // 5
SYSTEM_INFORMATION_CLASS의 enum 값을 확인해보면 5는 SystemProcessesAndThreadsInformation을 의미한다.
뭔가 프로세스나 스레드 관련된 정보를 얻을 때 사용하는 값이라고 추측해 볼 수 있다.
앞서 3개의 스레드 모두 ExpGetProcessInformation 함수 안에서 동작이 완료되지 않고 있었다. ExpGetProcessInformation 함수가 문제 발생 위치일 가능성이 높은 상황이다.
uf /c 명령으로 ExpGetProcessInformation 내부에서 호출되는 함수를 살펴보자.
kd> uf /c ExpGetProcessInformation
nt!ExpGetProcessInformation (fffff800`02d43a90)
nt!ExpGetProcessInformation+0x7a (fffff800`02d43b0a):
call to nt!KeFlushProcessWriteBuffers (fffff800`02a5bf7c)
nt!ExpGetProcessInformation+0xc9 (fffff800`02d43b58):
call to nt!PsGetProcessSessionId (fffff800`02a4bf94)
nt!ExpGetProcessInformation+0x146 (fffff800`02d43bd5):
call to nt!ExpCopyProcessInfo (fffff800`02d44c04)
nt!ExpGetProcessInformation+0x1d6 (fffff800`02d43c65):
call to nt!PsGetNextProcessThread (fffff800`02d278a8)
nt!ExpGetProcessInformation+0x24a (fffff800`02d43cd8):
call to nt!KeQueryValuesThread (fffff800`02a5bb50)
nt!ExpGetProcessInformation+0x491 (fffff800`02d43f1f):
call to nt!ObReferenceObjectSafe (fffff800`02aa2c00)
nt!ExpGetProcessInformation+0x4ea (fffff800`02d43f78):
call to nt!ObfDereferenceObject (fffff800`02a90440)
nt!ExpGetProcessInformation+0x541 (fffff800`02d43fcf):
call to nt!SeLocateProcessImageName (fffff800`02d279bc)
nt!ExpGetProcessInformation+0x631 (fffff800`02d440be):
call to nt!memmove (fffff800`02a7cff0)
nt!ExpGetProcessInformation+0x6bb (fffff800`02d44148):
call to nt!ExFreePoolWithTag (fffff800`02bb1d90)
nt!ExpGetProcessInformation+0x76f (fffff800`02d441fc):
call to nt!PsGetNextProcess (fffff800`02d44928) // 지연 의심 함수
nt!ExpGetProcessInformation+0x820 (fffff800`02d442ad):
call to nt!ExFreePoolWithTag (fffff800`02bb1d90)
nt!ExpGetProcessInformation+0xaaa (fffff800`02d44530):
call to nt!KeSynchronizeWithThreadInitialization (fffff800`02b25310)
nt!ExpGetProcessInformation+0xaed (fffff800`02d44573):
call to nt!ExfAcquirePushLockShared (fffff800`02ab6f20)
... ...
PETHREAD
PsGetNextProcessThread (
IN PEPROCESS Process,
IN PETHREAD Thread
첫 번째 파라미터가 프로세스 오브젝트인 것을 기억하자. 이제 PsGetNextProcessThread 함수를 디스어셈블링해서 살펴보자.
kd> u PsGetNextProcessThread L20
nt!PsGetNextProcessThread:
fffff800`02d278a8 48895c2408 mov qword ptr [rsp+8],rbx
fffff800`02d278ad 48896c2410 mov qword ptr [rsp+10h],rbp
fffff800`02d278b2 4889742418 mov qword ptr [rsp+18h],rsi
fffff800`02d278b7 57 push rdi
fffff800`02d278b8 4154 push r12
fffff800`02d278ba 4155 push r13
fffff800`02d278bc 4156 push r14
fffff800`02d278be 4157 push r15
fffff800`02d278c0 4883ec20 sub rsp,20h
fffff800`02d278c4 65488b3c2588010000 mov rdi,qword ptr gs:[188h]
fffff800`02d278cd 4533ff xor r15d,r15d
fffff800`02d278d0 488bf2 mov rsi,rdx
fffff800`02d278d3 66ff8fc4010000 dec word ptr [rdi+1C4h]
fffff800`02d278da 4d8bf7 mov r14,r15
fffff800`02d278dd 4c8da108030000 lea r12,[rcx+308h] // 1) 프로세스의 스레드 리스트 획득
fffff800`02d278e4 418bef mov ebp,r15d
fffff800`02d278e7 4c8da960010000 lea r13,[rcx+160h]
fffff800`02d278ee 33c0 xor eax,eax
fffff800`02d278f0 418d4f11 lea ecx,[r15+11h]
fffff800`02d278f4 f0490fb14d00 lock cmpxchg qword ptr [r13],rcx
fffff800`02d278fa 0f8594000000 jne nt!PsGetNextProcessThread+0xec (fffff800`02d27994)
fffff800`02d27900 493bf7 cmp rsi,r15
fffff800`02d27903 7575 jne nt!PsGetNextProcessThread+0xd2 (fffff800`02d2797a)
fffff800`02d27905 498b1c24 mov rbx,qword ptr [r12] // 2) 스레드 리스트의 Flink 획득
fffff800`02d27909 493bdc cmp rbx,r12 // 3) 리스트의 끝이면 종료하고 아니면 계속 루프를 돌게 되는 비교문
fffff800`02d2790c 747f je nt!PsGetNextProcessThread+0xe5 (fffff800`02d2798d)
fffff800`02d2790e 4c8db3e0fbffff lea r14,[rbx-420h]
fffff800`02d27915 498bce mov rcx,r14
fffff800`02d27918 e8e3b2d7ff call nt!ObReferenceObjectSafe (fffff800`02aa2c00)
fffff800`02d2791d 413ac7 cmp al,r15b
fffff800`02d27920 0f8485000000 je nt!PsGetNextProcessThread+0x103 (fffff800`02d279ab)
fffff800`02d27926
bb01000000 mov ebx,1
1)번을 보면 rcx+308을 참조하는 명령이 있는데 rcx 는 첫 번째 파라미터다. 이는 프로세스 오브젝트이므로 EPROCESS구조체의 +308 필드를 확인하면 된다.
kd> dt _EPROCESS ThreadListHead
nt!_EPROCESS
+0x308 ThreadListHead : _LIST_ENTRY
1)~3)번을 보면 EPROCESS 구조체의 +308 위치에 있는 ThreadListHead 리스트의 스레드 목록을 열거하고 있다.
링크드 리스트의 경우 리스트 목록이 증가하면 리스트 열거시 목록과 비례해서 성능 지연이 발생한다. 따라서 앞서 이 함수를 지연 의심 함수로 언급한 것이다.
혹시 시스템에 스레드가 엄청 많이 생성되어 ThreadListHead 리스트가 엄청 증가한 것은 아닐까?
이를 검증하려면 시스템의 모든 프로세스를 확인해서 이상한 부분은 없는지 살펴봐야 한다.
!for_each_process 명령을 사용하면 전체 프로세스의 간략한 정보를 알 수 있다.
kd> !for_each_process
PROCESS fffffa8000cda400
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00187000 ObjectTable: fffff8a0000017e0 HandleCount: 501525.
Image: System
PROCESS fffffa8002243b30
SessionId: none Cid: 0118 Peb: 7fffffd8000 ParentCid: 0004
DirBase: 24f55000 ObjectTable: fffff8a0004cce60 HandleCount: 32.
Image: smss.exe
PROCESS fffffa80021476f0
SessionId: 0 Cid: 017c Peb: 7fffffd7000 ParentCid: 016c
DirBase: 1cc2d000 ObjectTable: fffff8a000113630 HandleCount: 443.
Image: csrss.exe
PROCESS fffffa8002c02060
SessionId: 0 Cid: 01a4 Peb: 7fffffdd000 ParentCid: 016c
DirBase: 1c833000 ObjectTable: fffff8a005cca010 HandleCount: 77.
Image: wininit.exe
PROCESS fffffa80028d1060
SessionId: 1 Cid: 01b0 Peb: 7fffffd7000 ParentCid: 019c
DirBase: 19f96000 ObjectTable: fffff8a005ce5c20 HandleCount: 511.
Image: csrss.exe
PROCESS fffffa8002c08060
SessionId: 1 Cid: 01e8 Peb: 7fffffdf000 ParentCid: 019c
DirBase: 1999c000 ObjectTable: fffff8a005dae700 HandleCount: 121.
Image: winlogon.exe
PROCESS fffffa8002c874a0
SessionId: 0 Cid: 0218 Peb: 7fffffdf000 ParentCid: 01a4
DirBase: 1b6cc000 ObjectTable: fffff8a001242560 HandleCount: 230.
Image: services.exe
PROCESS fffffa8002c95770
SessionId: 0 Cid: 0224 Peb: 7fffffdf000 ParentCid: 01a4
DirBase: 1aaa4000 ObjectTable: fffff8a005d78ec0 HandleCount: 569.
Image: lsass.exe
PROCESS fffffa8002c9ab30
SessionId: 0 Cid: 022c Peb: 7fffffdf000 ParentCid: 01a4
DirBase: 1a72e000 ObjectTable: fffff8a005d7e9f0 HandleCount: 148.
Image: lsm.exe
... ...
이럴수가! 첫 번째 System 프로세스의 HandleCount를 보니 501,525개다. 50만 개가 넘는 핸들이 무지막지하게 열려 있는 상태다(보통의 정상적인 프로세스라면 수백~수천개 정도의 핸들만 유지하므로 비정상적인 수치다).
아무래도 시스템에 핸들 누수(Leak)가 발생하고 있는 심각한 상태로 의심된다.
이제 !handle 명령을 통해 Sysem 프로세스인 fffffa8000cda400의 모든 핸들 내역을 확인해보자.
kd> !handle 0 f fffffa80`075a8990
... ...
3734: Object: fffffa80014da060 GrantedAccess: 001fffff Entry: fffff8a00300bcd0
Object: fffffa80014da060 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014da030 (new version)
HandleCount: 1 PointerCount: 1
3738: Object: fffffa80014dab60 GrantedAccess: 001fffff Entry: fffff8a00300bce0
Object: fffffa80014dab60 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014dab30 (new version)
HandleCount: 1 PointerCount: 1
373c: Object: fffffa80014da660 GrantedAccess: 001fffff Entry: fffff8a00300bcf0
Object: fffffa80014da660 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014da630 (new version)
HandleCount: 1 PointerCount: 1
3740: Object: fffffa80014db060 GrantedAccess: 001fffff Entry: fffff8a00300bd00
Object: fffffa80014db060 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014db030 (new version)
HandleCount: 1 PointerCount: 1
3744: Object: fffffa80014dbb60 GrantedAccess: 001fffff Entry: fffff8a00300bd10
Object: fffffa80014dbb60 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014dbb30 (new version)
HandleCount: 1 PointerCount: 1
3748: Object: fffffa80014db660 GrantedAccess: 001fffff Entry: fffff8a00300bd20
Object: fffffa80014db660 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014db630 (new version)
HandleCount: 1 PointerCount: 1
374c: Object: fffffa80014dc060 GrantedAccess: 001fffff Entry: fffff8a00300bd30
Object: fffffa80014dc060 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014dc030 (new version)
HandleCount: 1 PointerCount: 1
3750: Object: fffffa80014dcb60 GrantedAccess: 001fffff Entry: fffff8a00300bd40
Object: fffffa80014dcb60 Type: (fffffa8000cdac40) Thread
ObjectHeader: fffffa80014dcb30 (new version)
HandleCount: 1 PointerCount: 1
... ...
대부분 스레드 핸들로 확인된다. 참조 카운트인 HandleCount와 PointerCount는 각각 1이다.
50만 개가 넘는 스레드 핸들이 아직 열려 있는 상태이므로 ThreadListHead 리스트는 이미 열거가 불가능할 정도로 수 많은 스레드 목록이 추가된 상태일 것이다. 의심이 확신이 되는 순간이다.
이제 시스템을 이렇게 엉망으로 만든 범인을 찾기 위해 스레드를 자세히 확인해야 한다. 임의의 스레드 하나를 선택해 상태를 살펴보자.
kd> !thread fffffa80014dcb60
THREAD fffffa80014dcb60 Cid 0004.38d4 Teb: 0000000000000000 Win32Thread: 0000000000000000 TERMINATED // 종료된 상태
Not impersonating
DeviceMap fffff8a000008aa0
Owning Process fffffa8000cda400 Image: System
Attached Process N/A Image: N/A
Wait Start TickCount 0 Ticks: 40554 (0:00:10:33.656)
Context Switch Count 1 IdealProcessor: 3
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address MyDrv (0xfffff88002e4d3e0)
Stack Init 0 Current fffff880031a69f0
Base fffff880031a7000 Limit fffff880031a1000 Call 0
Priority
8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
이미 종료된 TERMINATED 상태의 스레드다. 이어 다른 스레드도 몇 개 확인해보니 모두TERMINATED 상태다. 참조 카운트가 아직 남아 있으므로 오픈된 핸들을 해제하지 않고 스레드가 종료된 상황으로 보인다.
왜냐하면 스레드가 종료될 때 참조 카운트가 0이 아니면 스레드가 제거되지 않고 TERMINATED 상태로 남기 때문이다.
50만개의 스레드가 시스템을 재부팅하기 전까지는 영원히 죽지 못하는 좀비 상태가 돼버렸다.
스레드 핸들이 모두 System 프로세스의 핸들 테이블에 있는 부분에 집중할 필요가 있다.
System 프로세스의 핸들 테이블은 커널 핸들의 테이블로 보통 커널 드라이버에서 nt!PsCreateSystemThread 함수를 통해 시스템 스레드를 생성할 경우 여기에 핸들이 생성된다.
이런 상황이면 커널 핸들을 생성하고 해제하지 않는 커널 드라이버가 범인일 가능성이 매우 높다.
스레드의 Win32 Start Address 정보를 보면 좀비 상태의 스레드 시작 주소가 MyDrv 모듈의 주소로 확인된다. MyDrv 모듈에서 생성한 스레드가 해제되고 있지 않은 상황이다. 따라서 스레드 생성 주체인 MyDrv 모듈이 강력한 용의자다.
최종 확인을 위해 !stacks 명령으로 전체 콜 스택에서 MyDrv의 동작을 살펴보자.
kd> !stacks 2 MyDrv
Proc.Thread .Thread Ticks ThreadState Blocker
[fffff80002c071c0 Idle]
[fffffa8000cda400 System]
4.1eac64 fffffa8003143b60 ffff623d READY nt!KiSwapContext+0x7a
nt!KiQuantumEnd+0x1b4
nt!KiDispatchInterruptContinue+0x16
nt!KiDpcInterruptBypass+0x13
nt!KiSecondaryClockInterrupt+0x1a8
nt!ExfAcquirePushLockExclusive+0xd2
nt!PspInsertThread+0x829
nt!PspCreateThread+0x246
nt!PsCreateSystemThread+0x125
MyDrv+0x1505
nt!PspSystemThreadStartup+0x5a
nt!KiStartSystemThread+0x16
... ...
역시 System 프로세스 중 fffffa8003143b60 스레드에서 지속적으로 시스템 스레드를 생산하고 있다!
이렇게 생성한 스레드를 정상적으로 해제하지 않아 좀비로 만든 것이다.
결국 행이 발생한 원인은 MyDrv 모듈에서 수 많은 시스템 스레드를 생성하면서 해제하지 않았고, 이어 NtQuerySystemInformation 함수 내부에서는 다량의 스레드를 열거하느라 CPU 사용율을 증가시켰기 때문이다.
MyDrv 모듈에 의해 좀비 스레드는 계속 증가하는데 NtQuerySystemInformation 함수는 매우 빈번하게 호출되므로 시스템은 아마 사용이 불가능할 정도로 끔찍하게 느려졌을 것이다.
어쨌든 원인을 찾아 정말 다행이다. 이제 남은 일은 MyDrv 모듈에서 시스템 스레드를 생성하고 해제하지 않은 부분을 찾아 문제를 고치는 것이다.
'Dump Analysis' 카테고리의 다른 글
[0x133] DPC_WATCHDOG_VIOLATION (0) | 2018.07.29 |
---|---|
[0xC5] 해제 리스트 손상 (0) | 2018.07.19 |
[0xC5] 풀 헤더 손상 (0) | 2018.07.16 |
[0x1A] 페이지 손상 (0) | 2018.07.12 |
[0x50] 해제된 핸들 (0) | 2018.07.09 |