TL;DR
Windowsカーネル内部構造体(ThreadListHead, PointerCount, TableCode, ApcState, VadRoot)をデバッガから直接破壊・改変し、OSがどの整合性チェック・防衛ラインでBSODを発動させるかを実証解析した。
-
ThreadListHead破壊: 0埋めでリスト断絶。プロセス通知を検知したWindows Defender(mpengine.dll内部のLua)がプロセス・スレッド列挙(NtQuerySystemInformation)を走らせた際にNULL参照でクラッシュ(PAGE_FAULT_IN_NONPAGED_AREA)。 - 参照カウント操作:
PointerCount加算でゾンビスレッド化(プロセスが完全終了不能に)。減算ではハンドルクローズ(ObfDereferenceObject)時にアンダーフローを検知し即座にBSOD(REFERENCE_BY_POINTER)。 -
TableCode階層偽装: 3段階検索(Level 2)へフラグ書き換え。WaitForMultipleObjects時のインデックス計算と実構造の不一致でアドレス参照違反(SYSTEM_SERVICE_EXCEPTION)。 -
ApcState.Process偽装: 他プロセスへアタッチ状態を再現。デタッチせず終了を試みたため、カーネルの「絶対ルール」違反として検知(INVALID_PROCESS_ATTACH_ATTEMPT)。 -
VadRootスワップ: プロセス間でAVL Treeを置換。クリーンアップ時(MmCleanProcessAddressSpace)にPTE/CR3とVADツリーの不整合を引き起こしBSOD。
結論: カーネル構造体の直接改変は、多層化された検証メカニズム(ObfDereferenceObject,PspExitThread,MmCleanProcessAddressSpace等)によって確実に検知される。APIを介さない状態操作の不整合をOSがどのように防衛するか、その整合性チェックの確認を実施。
本記事の検証環境
- OS: Windows 11 Enterprise Evaluation 25H2 (Build 26200.6584)
- Kernel: ntoskrnl.exe 10.0.26100.6584 (x64)
- Debugger: WinDbg Engine 10.0.29617.1000
- Hypervisor: Hyper-V
1. ThreadListHead の破棄と Windows Defender による検知
_EPROCESS 内の ThreadListHead は、プロセスに属するスレッドを双方向循環リストで管理している。このリスト構造を直接ゼロクリアして動作を検証。
正常時の確認
!process および dt nt!_EPROCESS でリスト構造を確認します。
2: kd> !process -1 7
PROCESS ffffaa85f92850c0
SessionId: none Cid: 212c Peb: cc7516f000 ParentCid: 1e34
DirBase: 9e5d9000 ObjectTable: ffff808669b10e00 HandleCount: 55.
Image: processA.exe
THREAD ffffaa85f7fd20c0 Cid 212c.0e1c Teb: 000000cc75170000 Win32Thread: 0000000000000000 RUNNING on processor 2
Not impersonating
DeviceMap ffff8086664e5b50
Owning Process ffffaa85f92850c0 Image: processA.exe
THREAD ffffaa85f95b10c0 Cid 212c.2134 Teb: 000000cc75172000 Win32Thread: 0000000000000000 RUNNING on processor 4
Not impersonating
DeviceMap ffff8086664e5b50
Owning Process ffffaa85f92850c0 Image: processA.exe
THREAD ffffaa85f7b680c0 Cid 212c.1600 Teb: 000000cc75174000 Win32Thread: 0000000000000000 STANDBY
Not impersonating
DeviceMap ffff8086664e5b50
Owning Process ffffaa85f92850c0 Image: processA.exe
THREAD ffffaa85f73e50c0 Cid 212c.1604 Teb: 000000cc75176000 Win32Thread: 0000000000000000 READY on processor 4 (Shared Ready Queue)
Not impersonating
DeviceMap ffff8086664e5b50
Owning Process ffffaa85f92850c0 Image: processA.exe
THREAD ffffaa85f99ac0c0 Cid 212c.12d8 Teb: 000000cc75178000 Win32Thread: 0000000000000000 READY on processor 0 (Shared Ready Queue)
Not impersonating
DeviceMap ffff8086664e5b50
Owning Process ffffaa85f92850c0 Image: processA.exe
2: kd> dt nt!_EPROCESS ffffaa85f92850c0
+0x1d8 ActiveProcessLinks : _LIST_ENTRY [ 0xfffff807`b33054d0 - 0xffffaa85`f93f6258 ]
+0x370 ThreadListHead : _LIST_ENTRY [ 0xffffaa85`f7fd2638 - 0xffffaa85`f99ac638 ]
2: kd> dl ffffaa85f92850c0+370 10 1
ffffaa85`f9285430 ffffaa85`f7fd2638
ffffaa85`f7fd2638 ffffaa85`f95b1638
ffffaa85`f95b1638 ffffaa85`f7b68638
ffffaa85`f7b68638 ffffaa85`f73e5638
ffffaa85`f73e5638 ffffaa85`f99ac638
ffffaa85`f99ac638 ffffaa85`f9285430
!processで見たアドレスとThreadListHeadのアドレスがズレているのはリストのポインタがThreadListHeadには入っているから。そのアドレスは先頭アドレスにThreadListEntryのオフセットを足した値となる
1: kd> dt nt!_ETHREAD
+0x578 ThreadListEntry : _LIST_ENTRY
リストのゼロクリアと結果
f コマンドで ThreadListHead を 0 埋めして処理を再開(g)
2: kd> f ffffaa85f92850c0+370 L10 00
Filled 0x10 bytes
2: kd> dl ffffaa85f92850c0+370 10 1
ffffaa85`f9285430 00000000`00000000
発生したBSOD
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: fffffffffffffa58, memory referenced.
Arg2: 0000000000000000, X64: bit 0 set if the fault was due to a not-present PTE.
bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the processor decided the fault was due to a corrupted PTE.
bit 4 is set if the fault was due to attempted execute of a no-execute PTE.
- ARM64: bit 1 is set if the fault was due to a write, clear if a read.
bit 3 is set if the fault was due to attempted execute of a no-execute PTE.
Arg3: fffff807b269910c, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000002, (reserved)
STACK_TEXT:
ffffa406`9bb262b8 fffff807`b29af3f2 : ffffa406`9bb26338 00000000`00000001 00000000`00000100 fffff807`b2ab9501 : nt!DbgBreakPointWithStatus
ffffa406`9bb262c0 fffff807`b29ae91c : 00000000`00000003 ffffa406`9bb26420 fffff807`b2ab9680 00000000`00000050 : nt!KiBugCheckDebugBreak+0x12
ffffa406`9bb26320 fffff807`b28f9387 : ffffaa85`00000000 fffff807`b27e299b ffffcf80`b4502250 ffffaa85`f6711400 : nt!KeBugCheck2+0xb2c
ffffa406`9bb26ab0 fffff807`b27e265c : 00000000`00000050 ffffffff`fffffa58 00000000`00000000 ffffa406`9bb26d50 : nt!KeBugCheckEx+0x107
ffffa406`9bb26af0 fffff807`b26b5eb0 : 00000001`00000000 ffff8000`00000000 ffffffff`fffffa58 0000007f`fffffff8 : nt!MiSystemFault+0x7a0
ffffa406`9bb26be0 fffff807`b2aaebcb : ffffaa85`f191d080 00000000`00000000 ffffffff`fffffa88 00000000`00000001 : nt!MmAccessFault+0x630
ffffa406`9bb26d50 fffff807`b269910c : ffffaa85`f93f6080 ffffaa85`f92850c0 ffffaa85`f9285288 00000000`00000000 : nt!KiPageFault+0x38b
ffffa406`9bb26ee0 fffff807`b2d78abb : 00000000`00000000 000001fd`00000000 ffffaa85`f93f6080 00000000`00000000 : nt!ObReferenceObjectSafeWithTag+0xc
ffffa406`9bb26f10 fffff807`b2ed08eb : 00000000`00000016 000001fd`0d85e9b8 00000000`00000000 00000000`00000000 : nt!ExpGetNextProcessThread+0xbb
ffffa406`9bb26f60 fffff807`b2ddc365 : 00000000`00000000 00000000`00010000 00000000`00000000 00000000`00000000 : nt!ExpGetProcessInformation+0x6db
ffffa406`9bb27580 fffff807`b2ddb44e : 0000003c`d8afd570 00000000`00000000 00000000`00000000 00000000`00000104 : nt!ExpQuerySystemInformation+0xdb5
ffffa406`9bb27a60 fffff807`b2ab3055 : ffffaa85`f63e0000 ffffaa85`f63ef040 ffffaa85`f63ef040 00000000`00000000 : nt!NtQuerySystemInformation+0x3e
ffffa406`9bb27aa0 00007ffa`12aa38f4 : 00007ff9`fcaebe05 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25
0000003c`d8afd6e8 00007ff9`fcaebe05 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!NtQuerySystemInformation+0x14
0000003c`d8afd6f0 00007ff9`fc37aabf : 00000000`00000000 0000003c`d8afd898 000001fc`e76cae00 00000000`00000000 : mpengine!MemScanEnumProcesses+0xb1
0000003c`d8afd7a0 00007ff9`fc2edb44 : 000001fd`0c08d388 000001fc`00000000 000001fd`0b92dc90 00000000`00013300 : mpengine!LsaSysIoLib::GetProcessFromFileName+0xff
0000003c`d8afd890 00007ff9`fc2ee217 : 00000000`000035cc 0000003c`d8afda00 000001fd`0d497138 000001fd`0d744e40 : mpengine!luaD_precall+0x204
0000003c`d8afd900 00007ff9`fc2ed8e5 : 00000000`00000001 000001fd`0c08d388 00000000`00000000 000001fd`0c08d388 : mpengine!luaV_execute+0x407
0000003c`d8afdaa0 00007ff9`fc2ed59b : 000001fd`0cbbfcb0 00007ff9`fc2ed755 00007ff9`fcefbd88 00000000`00000001 : mpengine!luaD_call+0x35
0000003c`d8afdad0 00007ff9`fc2ec1de : 000001fd`0c08d430 000001fd`0c08d388 00000000`00000020 00000001`0003001b : mpengine!luaD_rawrunprotected+0x5b
0000003c`d8afdb20 00007ff9`fc2e1fdd : 0000003c`d8afe0b0 000015b3`00000001 000001fc`e3000080 00000000`00000001 : mpengine!ExecuteLuaScript+0x20e
0000003c`d8afdcd0 00007ff9`fc2e0e0b : 00000000`00000001 0000003c`d8afe0b0 00000000`00000000 00007ff9`fc2e35b6 : mpengine!ValidateSignatureWithPcodeWorker2+0x1d9
0000003c`d8afde50 00007ff9`fc2a734d : 00000000`00b11e42 00000000`00b11e42 00000000`00b11e42 000001fd`0d6a00c0 : mpengine!ValidateSignatureWithPcode+0x23
0000003c`d8afdec0 00007ff9`fc2b1f01 : 00000000`00000ee7 00000000`8003f0c8 000015b3`fef672df 000001fd`0d744e40 : mpengine!SigtreeHandlerInstance::refresh_cksig_data+0x19ed
0000003c`d8afe260 00007ff9`fc2afed9 : 000001fc`e3000014 000001fd`0d4aa800 000001fc`e3000340 00000001`0003001b : mpengine!SigtreeHandlerInstance::siga_cksig_impl+0xe91
0000003c`d8afe750 00007ff9`fc5cee8b : 000001fd`0bcc16e0 000001fd`0c029730 0000003c`d8afede1 00007ff9`fc3a61ff : mpengine!SigtreeHandlerInstance::siga_cksig+0x39
0000003c`d8afe820 00007ff9`fc3b20c0 : 000001fd`0d744e40 0000003c`d8afeda0 00000000`00000000 0000003c`d8afeda0 : mpengine!SigtreeHelper::TestForDetection+0x7b
0000003c`d8afe8a0 00007ff9`fc3b2439 : 0000003c`d8aff000 000001fd`0cb6ad40 0000003c`d8aff000 000001fc`e7b4d420 : mpengine!SignatureHandler::TestForDetection+0x160
0000003c`d8afed40 00007ff9`fc6cae5b : 000001fd`0cb6ad40 0000003c`d8aff3f8 0000003c`d8aff000 0000003c`d8aff1d0 : mpengine!SignatureHandler::TestForDetectionWithTokenizedPath+0x261
0000003c`d8afee20 00007ff9`fc523a4e : 00000000`00000000 0000003c`d8aff3f8 00000000`00000000 00000000`00000000 : mpengine!SignatureHandler::TestForProcessStart+0xab
0000003c`d8afeea0 00007ff9`fc523170 : 0000003c`d8aff330 00007ff9`fc33cc90 0000003c`d8aff310 00000000`00000000 : mpengine!SignatureHandler::HandleNotification+0x7ce
0000003c`d8aff280 00007ff9`fc2ab2f7 : 000001fd`0d744e40 000001fd`0cb6ad50 0000003c`d8aff3f8 00000000`00000000 : mpengine!ProcessNotification::VisitForScan+0x70
0000003c`d8aff2c0 00007ff9`fc2aae3b : 000001fd`0d3771a0 00000000`00000000 000001fd`0c40f260 000001fd`00002101 : mpengine!ScanHandlerBase::HandleNotification+0x57
0000003c`d8aff300 00007ff9`fc2ab855 : 000001fd`0c40f260 000001fd`0d744e40 000001fd`0c40f200 00000000`00000001 : mpengine!ProcessContext::HandleNotification+0x8f
0000003c`d8aff3e0 00007ff9`fc31e9f6 : 000001fd`0c40f260 000001fc`e31da950 000001fd`0c40f260 000001fd`0c40fbb8 : mpengine!ProcessContext::ConsumeNotification+0x65
0000003c`d8aff440 00007ff9`fc33978a : 000001fd`0c40f260 000001fd`0bcc16e0 000001fd`0d7437f0 000001fc`e31da950 : mpengine!ProcessContext::FirstProcessNotification+0xa2
0000003c`d8aff4a0 00007ff9`fc62006e : 000001fd`0c40f260 00000000`00000000 000001fd`00000001 000001fd`0d7437f0 : mpengine!ProcessContext::ConsumeQueue+0x37e
0000003c`d8aff560 00007ff9`fc61ffce : 000001fc`e7cc84d0 00007ffa`12a16f60 000001fc`e7cc84c0 00007ffa`02d058ef : mpengine!NotificationItem::DispatchJob+0x1e
0000003c`d8aff590 00007ff9`fca12410 : 000001fc`e7cdd1d0 000001fc`e31da988 00000000`00000000 00007ff9`fc373a4c : mpengine!NotificationItem::OnAction+0x1e
0000003c`d8aff5d0 00007ff9`fca12332 : 000001fc`e31da950 000001fc`e31da988 00005c79`eae7e3c0 00000000`00000000 : mpengine!CommonUtil::CMpSimpleThreadPool::Call+0x4c
0000003c`d8aff620 00007ffa`129bd460 : 0000003c`d8aff978 0000003c`d8aff780 00000000`7ffe0386 00005c79`eae7e3c0 : mpengine!CommonUtil::CMpSimpleThreadPool::AsyncDequeue+0xfe
0000003c`d8aff680 00007ffa`129be4b1 : 00000000`00000000 00000000`00000001 00007ffa`12a160d0 000001fc`f4363050 : ntdll!TppWorkpExecuteCallback+0x4d0
0000003c`d8aff7e0 00007ffa`10ace8d7 : 00000000`00000800 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x801
0000003c`d8affb40 00007ffa`12948d9c : 00000000`00000000 00000000`00000000 00000000`00000001 0000003c`d8b7f000 : KERNEL32!BaseThreadInitThunk+0x17
0000003c`d8affb70 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x2c
クラッシュのメカニズム
- デバッガから実行を再開した際、Windows Defender(
mpengine.dll)がプロセス通知を検知。 - Defender内部のLuaスクリプトが全プロセス・スレッド列挙のため
NtQuerySystemInformationを発行。 - カーネル(
ExpGetNextProcessThread)がprocessA.exeのThreadListHeadを参照しようとしたが、0に破壊されていたため NULLポインタ参照 が発生してBSOD。
どうでもいいですけど噂では聞いていたもののDefenderって本当にLua使ってるんですね
2. 終了中スレッドの PointerCount 改変実験
スレッドオブジェクトのヘッダー(_OBJECT_HEADER)にある参照カウント PointerCount を手動操作した場合の挙動を検証
2-1. PointerCount を加算した場合(ゾンビスレッド化)
スレッド終了直前に PointerCount を加算します。おなじみのnt!PspExitThreadが動作していることを確認
2: kd> !thread ffffad8f6d6ec0c0
THREAD ffffad8f6d6ec0c0 Cid 21ac.1240 Teb: 000000aabeadd000 Win32Thread: 0000000000000000 RUNNING on processor 2
Not impersonating
DeviceMap ffff920260217250
Owning Process ffffad8f6dbdb0c0 Image: processA.exe
Attached Process N/A Image: N/A
Wait Start TickCount 7390 Ticks: 640 (0:00:00:10.000)
Context Switch Count 43 IdealProcessor: 2
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x00007ff6b5d81720
Stack Init ffff9102d9867c30 Current ffff9102d98676f0
Base ffff9102d9868000 Limit ffff9102d9861000 Call 0000000000000000
Priority 8 BasePriority 8 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
ffff9102`d9867a08 fffff804`d4ef493f : ffff9102`d9867a28 00000000`00000000 00000000`00000000 ffffad8f`6b3942e0 : nt!PspExitThread
ffff9102`d9867a10 fffff804`d4ef4856 : 00000000`00000000 00000000`00000000 ffffad8f`6d6ec0c0 fffff804`d4f9533c : nt!PspTerminateThreadByPointer+0x4f
ffff9102`d9867a50 fffff804`d4cb3055 : 00000000`00000000 ffffad8f`6d6ec0c0 ffff9102`d9867b20 000000aa`beade7ee : nt!NtTerminateThread+0x46
ffff9102`d9867aa0 00007ffe`3e643c94 : 00007ffe`3e4e8e36 00000000`00000000 000000aa`beada000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffff9102`d9867aa0)
000000aa`bedff8f8 00007ffe`3e4e8e36 : 00000000`00000000 000000aa`beada000 00000000`00000000 00000000`00000000 : 0x00007ffe`3e643c94
000000aa`bedff900 00000000`00000000 : 000000aa`beada000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ffe`3e4e8e36
2: kd> !object ffffad8f6d6ec0c0
Object: ffffad8f6d6ec0c0 Type: (ffffad8f676a8bb0) Thread
ObjectHeader: ffffad8f6d6ec090 (new version)
HandleCount: 1 PointerCount: 32770
2: kd> dx ((nt!_OBJECT_HEADER*)0xffffad8f6d6ec090)->PointerCount = 0x800c
((nt!_OBJECT_HEADER*)0xffffad8f6d6ec090)->PointerCount = 0x800c : 32780 [Type: __int64]
2: kd> !object ffffad8f6d6ec0c0
Object: ffffad8f6d6ec0c0 Type: (ffffad8f676a8bb0) Thread
ObjectHeader: ffffad8f6d6ec090 (new version)
HandleCount: 1 PointerCount: 32780
結果
- VAD(仮想アドレス記述子)や TEB(スレッド環境ブロック)は完全に解放され、ハンドルテーブル数も
0、状態もTERMINATEDに変化。
1: kd> !process ffffad8f6dbdb0c0 7
PROCESS ffffad8f6dbdb0c0
SessionId: none Cid: 21ac Peb: aabeada000 ParentCid: 1f4c
DirBase: 54f29000 ObjectTable: 00000000 HandleCount: 0.
Image: processA.exe
VadRoot 0000000000000000 Vads 0 Clone 0 Private 15. Modified 2. Locked 0.
DeviceMap ffff920260217250
Token ffff920263abc0a0
ElapsedTime 00:28:17.364
UserTime 00:00:00.000
KernelTime 00:00:00.015
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (16, 50, 345) (64KB, 200KB, 1380KB)
PeakWorkingSetSize 6192
VirtualSize 0 Mb
PeakVirtualSize 4165 Mb
PageFaultCount 6359
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 19
No active threads
THREAD ffffad8f6d6ec0c0 Cid 21ac.1240 Teb: 0000000000000000 Win32Thread: 0000000000000000 TERMINATED
- しかし、増やす操作をした分の参照カウントが残るため、オブジェクト自体がカーネルメモリから抹消されず、親プロセスが完全に終了できない状態 に陥る。メモリも微妙に使っている状態
1: kd> !object ffffad8f6d6ec0c0
HandleCount: 0 PointerCount: 10
1: kd> !vm
21ac processA.exe 76 Kb 0 Kb 0 Kb
2-2. PointerCount を減算した場合(アンダーフロー)
同様のタイミングで PointerCount を減算
2: kd> !thread ffffb00f193d0080
THREAD ffffb00f193d0080 Cid 290c.2918 Teb: 0000005b2e582000 Win32Thread: 0000000000000000 RUNNING on processor 2
Not impersonating
DeviceMap ffff988bc72398d0
Owning Process ffffb00f1b643080 Image: processA.exe
Attached Process N/A Image: N/A
Wait Start TickCount 3225 Ticks: 640 (0:00:00:10.000)
Context Switch Count 9 IdealProcessor: 3
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x00007ff7bcce1720
Stack Init ffffaf066d747c30 Current ffffaf066d7476f0
Base ffffaf066d748000 Limit ffffaf066d741000 Call 0000000000000000
Priority 8 BasePriority 8 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
ffffaf06`6d747a08 fffff801`a44f493f : ffffaf06`6d747a28 00000000`00000000 00000000`00000000 ffffb00f`194618e0 : nt!PspExitThread
ffffaf06`6d747a10 fffff801`a44f4856 : 00000000`00000000 00000000`00000000 ffffb00f`193d0080 fffff801`a459533c : nt!PspTerminateThreadByPointer+0x4f
ffffaf06`6d747a50 fffff801`a42b3055 : 00000000`00000000 ffffb00f`193d0080 ffffaf06`6d747b20 0000005b`2e5837ee : nt!NtTerminateThread+0x46
ffffaf06`6d747aa0 00007ff8`7f7e3c94 : 00007ff8`7f688e36 00000000`00000000 0000005b`2e57d000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffffaf06`6d747aa0)
0000005b`2e7ff8b8 00007ff8`7f688e36 : 00000000`00000000 0000005b`2e57d000 00000000`00000000 00000000`00000000 : 0x00007ff8`7f7e3c94
0000005b`2e7ff8c0 00000000`00000000 : 0000005b`2e57d000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ff8`7f688e36
2: kd> !object ffffb00f193d0080
Object: ffffb00f193d0080 Type: (ffffb00f12687cf0) Thread
ObjectHeader: ffffb00f193d0050 (new version)
HandleCount: 1 PointerCount: 32770
2: kd> dx ((nt!_OBJECT_HEADER*)0xffffb00f193d0050)->PointerCount -= 5
((nt!_OBJECT_HEADER*)0xffffb00f193d0050)->PointerCount -= 5 : 32765 [Type: __int64]
2: kd> !object ffffb00f193d0080
Object: ffffb00f193d0080 Type: (ffffb00f12687cf0) Thread
ObjectHeader: ffffb00f193d0050 (new version)
HandleCount: 1 PointerCount: 32765
発生したBSOD
REFERENCE_BY_POINTER (18)
Arguments:
Arg1: 0000000000000000, Object type of the object whose reference count is being lowered
Arg2: ffffb00f193d0080, Object whose reference count is being lowered
Arg3: 0000000000000002, Reserved
Arg4: fffffffffffffffb, Reserved
The reference count of an object is illegal for the current state of the object.
Each time a driver uses a pointer to an object the driver calls a kernel routine
to increment the reference count of the object. When the driver is done with the
pointer the driver calls another kernel routine to decrement the reference count.
Drivers must match calls to the increment and decrement routines. This BugCheck
can occur because an object's reference count goes to zero while there are still
open handles to the object, in which case the fourth parameter indicates the number
of opened handles. It may also occur when the object's reference count drops below zero
whether or not there are open handles to the object, and in that case the fourth parameter
contains the actual value of the pointer references count.
STACK_TEXT:
* WARNING: Unable to verify checksum for processA.exe
ffffaf06`6ba77068 fffff801`a41af3f2 : ffffaf06`6ba770e8 00000000`00000001 00000000`00000100 fffff801`a42b9501 : nt!DbgBreakPointWithStatus
ffffaf06`6ba77070 fffff801`a41ae91c : 00000000`00000003 ffffaf06`6ba771d0 fffff801`a42b9680 00000000`00000018 : nt!KiBugCheckDebugBreak+0x12
ffffaf06`6ba770d0 fffff801`a40f9387 : ffffcd89`00120101 00000000`78e34701 ffffb00f`00000001 ffffb00f`1287b000 : nt!KeBugCheck2+0xb2c
ffffaf06`6ba77860 fffff801`a3e5789a : 00000000`00000018 00000000`00000000 ffffb00f`193d0080 00000000`00000002 : nt!KeBugCheckEx+0x107
ffffaf06`6ba778a0 fffff801`a44505c9 : 00000000`00000001 00000000`00000000 00000000`00000000 00000000`00000000 : nt!ObfDereferenceObjectWithTag+0x7a
ffffaf06`6ba778e0 fffff801`a444ed39 : 00000000`00000001 0000005b`2e3fe230 ffffaf06`6ba77b20 00000000`00000000 : nt!ObCloseHandleTableEntry+0x3d9
ffffaf06`6ba77a30 fffff801`a42b3055 : 0000005b`2e3ff600 00000000`000000b4 ffffb00f`1b860080 00000000`00000000 : nt!NtClose+0xe9
ffffaf06`6ba77aa0 00007ff8`7f7e3414 : 00007ff8`7cc331a9 00000000`000000b8 00000000`00000000 0000005b`2e3ffa98 : nt!KiSystemServiceCopyEnd+0x25
0000005b`2e3ff9f8 00007ff8`7cc331a9 : 00000000`000000b8 00000000`00000000 0000005b`2e3ffa98 0000024b`1a12da70 : ntdll!NtClose+0x14
0000005b`2e3ffa00 00007ff7`bcce1c40 : 0000005b`2e3ffa98 00000000`00000001 0000005b`2e3ffa71 0000005b`2e3ffa98 : KERNELBASE!CloseHandle+0x49
0000005b`2e3ffa30 0000005b`2e3ffa98 : 00000000`00000001 0000005b`2e3ffa71 0000005b`2e3ffa98 00000000`00000000 : processA!main+0x430
0000005b`2e3ffa38 00000000`00000001 : 0000005b`2e3ffa71 0000005b`2e3ffa98 00000000`00000000 00000000`00000000 : 0x0000005b`2e3ffa98
0000005b`2e3ffa40 0000005b`2e3ffa71 : 0000005b`2e3ffa98 00000000`00000000 00000000`00000000 00000002`00000002 : 0x1
0000005b`2e3ffa48 0000005b`2e3ffa98 : 00000000`00000000 00000000`00000000 00000002`00000002 00000000`00000001 : 0x0000005b`2e3ffa71
0000005b`2e3ffa50 00000000`00000000 : 00000000`00000000 00000002`00000002 00000000`00000001 0000290c`00000000 : 0x0000005b`2e3ffa98
PointerCountが18446744073709551611(0xFFFFFFFFFFFFFFFB)となる。
1: kd> !object ffffb00f193d0080
Object: ffffb00f193d0080 Type: (ffffb00f12687cf0) Thread
ObjectHeader: ffffb00f193d0050 (new version)
HandleCount: 0 PointerCount: 18446744073709551611
結果
ユーザー空間からの CloseHandle 呼び出しに伴い、カーネルが ObfDereferenceObjectWithTag で参照カウントを減算した際、PointerCount がマイナス(0xFFFFFFFFFFFFFFFB)にアンダーフロー。オブジェクト管理構造の破損と判定され、BSODが発生。
つまりスレッドが削除された後にPointerCountが正常の範囲内に収まっていればスレッドは残存し、マイナスなど異常値になればその下にあるメモリ領域が正常である保証がないためBSODを発動されるものと想定される
3. _HANDLE_TABLE の TableCode 階層フラグ手動書き換え
Windowsのハンドルテーブル(_HANDLE_TABLE)は、登録数に応じてテーブルの階層構造(1段〜3段)が動的に変化する。では手動で拡張するとどうなるか
1: kd> dx -r1 ((nt!_HANDLE_TABLE*)0xffffcd8f`d7536780)
((nt!_HANDLE_TABLE*)0xffffcd8f`d7536780) : 0xffffcd8fd7536780 [Type: _HANDLE_TABLE *]
[+0x000] NextHandleNeedingPool : 0xc00 [Type: unsigned long]
[+0x004] ExtraInfoPages : 0 [Type: long]
[+0x008] TableCode : 0xffffcd8fcebfb001 [Type: unsigned __int64]
[+0x010] QuotaProcess : 0xffffb70194a03080 [Type: _EPROCESS *]
[+0x018] HandleTableList [Type: _LIST_ENTRY]
[+0x028] UniqueProcessId : 0x18bc [Type: unsigned long]
[+0x02c] Flags : 0x8 [Type: unsigned long]
[+0x02c ( 0: 0)] StrictFIFO : 0x0 [Type: unsigned char]
[+0x02c ( 1: 1)] EnableHandleExceptions : 0x0 [Type: unsigned char]
[+0x02c ( 2: 2)] Rundown : 0x0 [Type: unsigned char]
[+0x02c ( 3: 3)] Duplicated : 0x1 [Type: unsigned char]
[+0x02c ( 4: 4)] RaiseUMExceptionOnInvalidHandleClose : 0x0 [Type: unsigned char]
[+0x030] HandleContentionEvent [Type: _EX_PUSH_LOCK]
[+0x038] HandleTableLock [Type: _EX_PUSH_LOCK]
[+0x040] FreeLists [Type: _HANDLE_TABLE_FREE_LIST [1]]
[+0x040] ActualEntry [Type: unsigned char [32]]
[+0x060] DebugInfo : 0x0 [Type: _HANDLE_TRACE_DEBUG_INFO *]
1: kd> dx ((nt!_HANDLE_TABLE*)0xffffcd8fd7536780)->TableCode & 3
((nt!_HANDLE_TABLE*)0xffffcd8fd7536780)->TableCode & 3 : 0x1
1: kd> dx ((nt!_HANDLE_TABLE*)0xffffcd8f`d7536780)->TableCode = 0xffffcd8fcebfb002
((nt!_HANDLE_TABLE*)0xffffcd8f`d7536780)->TableCode = 0xffffcd8fcebfb002 : 0xffffcd8fcebfb002 [Type: unsigned __int64]
1: kd> dx ((nt!_HANDLE_TABLE*)0xffffcd8fd7536780)->TableCode & 3
((nt!_HANDLE_TABLE*)0xffffcd8fd7536780)->TableCode & 3 : 0x2
発生したBSOD
SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the BugCheck
Arg2: fffff8078824f2fe, Address of the instruction which caused the BugCheck
Arg3: ffffca0631241840, Address of the context record for the exception that caused the BugCheck
Arg4: 0000000000000000, zero.
STACK_TEXT:
ffffca06`31242290 fffff807`8839b74a : 00000000`94a03480 00000000`00000000 00000000`00000001 00000000`00000000 : nt!ObWaitForMultipleObjects+0x12e
ffffca06`312427a0 fffff807`880b3055 : 00000000`00000000 00000000`00000016 ffffb701`942ec080 00000032`aedefb48 : nt!NtWaitForMultipleObjects+0x11a
ffffca06`31242a30 00007ffa`90203d84 : 00007ffa`8d79f233 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25
00000032`aedefb28 00007ffa`8d79f233 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!NtWaitForMultipleObjects+0x14
00000032`aedefb30 00007ffa`8d79f101 : 00000032`aedefeb0 00007ff6`ae9c8df6 0000014d`a2f56a30 00000000`00000000 : KERNELBASE!WaitForMultipleObjectsEx+0x123
00000032`aedefe20 00007ff6`ae9c1ae9 : 00000032`aedefec8 00000000`00000000 00000032`aedefea1 00000032`aedefec8 : KERNELBASE!WaitForMultipleObjects+0x11
00000032`aedefe60 00000032`aedefec8 : 00000000`00000000 00000032`aedefea1 00000032`aedefec8 00000000`00000000 : processA+0x1ae9
00000032`aedefe68 00000000`00000000 : 00000032`aedefea1 00000032`aedefec8 00000000`00000000 00000000`00000000 : 0x00000032`aedefec8
結果
-
WaitForMultipleObjects発行時、ObWaitForMultipleObjectsがハンドル解決のために_HANDLE_TABLEを参照。 - 下位ビットが
2だったため、カーネルは Level 2(3段階検索アルゴリズム) でアドレス計算を実施。 - 実際のメモリ構造は拡張されていないため、無効なアドレスを辿ってBSOD
単純にプロセスのハンドルテーブルの階層を拡張したとしても参照先の実データ側を書き換えなければ参照するアドレスを誤るためBSODが発生する。実際に拡張するためにはnt!ExpAllocateHandleTableEntrySlowなどの関数を利用すれば安全に拡張可能
4. 仮想メモリ領域(VAD)の解放挙動
まずはスレッドが確保したものをスレッド内で解放してみる
1: kd> g
[+] Thread 1: Memory allocated at: 0x0000027F76BF0000
Break instruction exception - code 80000003 (first chance)
0033:00007ffe`0995db42 cc int 3
5: kd> !vad 0x0000027F76BF0000
VAD Level Start End Commit
ffffd2024ac0f270 3 27f76bf0 27f775ef 2560 Private READWRITE
5: kd> !pte 0x0000027F76BF0000
VA 0000027f76bf0000
PXE at FFFFFCFE7F3F9020 PPE at FFFFFCFE7F204FE8 PDE at FFFFFCFE409FDDA8 PTE at FFFFFC813FBB5F80
contains 0A000000860C8867 contains 0A000000A60C9867 contains 0A000000AFA48867 contains 80000000A844A867
pfn 860c8 ---DA--UWEV pfn a60c9 ---DA--UWEV pfn afa48 ---DA--UWEV pfn a844a ---DA--UW-V
5: kd> g
[+] Thread 1: VirtualFree executed BEFORE thread exit!
Break instruction exception - code 80000003 (first chance)
0033:00007ffe`0995db42 cc int 3
2: kd> !vad 0x0000027F76BF0000
VAD Level Start End Commit
No VAD describing VA 27f76bf0000 in the current process
2: kd> !pte 0x0000027F76BF0000
VA 0000027f76bf0000
PXE at FFFFFCFE7F3F9020 PPE at FFFFFCFE7F204FE8 PDE at FFFFFCFE409FDDA8 PTE at FFFFFC813FBB5F80
contains 0A000000860C8867 contains 0A000000A60C9867 contains 0A000000867C5867 contains 0000000000000000
pfn 860c8 ---DA--UWEV pfn a60c9 ---DA--UWEV pfn 867c5 ---DA--UWEV not valid
当然のことながら解放は可能
次にサブスレッドで確保したメモリをメインスレッドから解放するとどうなるか。
0: kd> !vad 0x0000026115A00000
VAD Level Start End Commit
No VAD describing VA 26115a00000 in the current process
0: kd> !vad 0x0000026116400000
VAD Level Start End Commit
No VAD describing VA 26116400000 in the current process
0: kd> !pte 0x0000026115A00000
VA 0000026115a00000
PXE at FFFFFCFE7F3F9020 PPE at FFFFFCFE7F204C20 PDE at FFFFFCFE40984568 PTE at FFFFFC81308AD000
contains 0A0000004D479867 contains 0A00000092E7A867 contains 0000000000000000
pfn 4d479 ---DA--UWEV pfn 92e7a ---DA--UWEV contains 0000000000000000
not valid
0: kd> !pte 0x0000026116400000
VA 0000026116400000
PXE at FFFFFCFE7F3F9020 PPE at FFFFFCFE7F204C20 PDE at FFFFFCFE40984590 PTE at FFFFFC81308B2000
contains 0A0000004D479867 contains 0A00000092E7A867 contains 0000000000000000
pfn 4d479 ---DA--UWEV pfn 92e7a ---DA--UWEV contains 0000000000000000
not valid
これも特に問題はない。
ではスレッドが動作しているメモリ領域を解放するとどうなるか。
表面的にはただアプリが終了しただけに見えるが終了コードを確認すると-1073741819 (0xC0000005)となっておりSTATUS_ACCESS_VIOLATION(ユーザーモード例外)が発生し終了することが分かる。
5. スレッドの ApcState.Process 強制書き換え
processA.exe に属するスレッドの ApcState.Process ポインタを、別プロセス(Notepad.exe)の _KPROCESS アドレスへ直接書き換えて「他プロセスにアタッチした状態」を疑似的に作る。
- 変更前
2: kd> !process -1 0
PROCESS ffffd2024d4e5080
SessionId: none Cid: 1ac4 Peb: beadf6b000 ParentCid: 2754
DirBase: d6c21000 ObjectTable: ffffae04d93b1e00 HandleCount: 53.
Image: processA.exe
2: kd> dx ((nt!_ETHREAD*)0xffffd2024d550080)->Tcb.ApcState.Process
((nt!_ETHREAD*)0xffffd2024d550080)->Tcb.ApcState.Process : 0xffffd2024d4e5080 [Type: _KPROCESS *]
2: kd> !process 0 0 notepad.exe
PROCESS ffffd2024cc5c080
SessionId: none Cid: 06d0 Peb: 3c58e0a000 ParentCid: 1ad4
DirBase: bff02000 ObjectTable: ffffae04dc011b40 HandleCount: 655.
Image: Notepad.exe
- 変更後
2: kd> dx ((nt!_ETHREAD*)0xffffd2024d550080)->Tcb.ApcState.Process = (nt!_KPROCESS*)0xffffd2024cc5c080
2: kd> dx (void*)(((nt!_ETHREAD*)0xffffd2024d550080)->Tcb.ApcState.Process)
(void*)(((nt!_ETHREAD*)0xffffd2024d550080)->Tcb.ApcState.Process) : 0xffffd2024cc5c080 [Type: void *]
2: kd> !process -1 7
PROCESS ffffd2024d4e5080
SessionId: none Cid: 1ac4 Peb: beadf6b000 ParentCid: 2754
DirBase: d6c21000 ObjectTable: ffffae04d93b1e00 HandleCount: 53.
Image: processA.exe
THREAD ffffd2024d4e6080 Cid 1ac4.0a64 Teb: 000000beadf6c000 Win32Thread: 0000000000000000 RUNNING on processor 2
Not impersonating
DeviceMap ffffae04d2256950
Owning Process ffffd2024d4e5080 Image: processA.exe
Attached Process N/A Image: N/A
Wait Start TickCount 122221 Ticks: 0
Context Switch Count 27 IdealProcessor: 1
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x00007ff619e9c740
Stack Init fffffe8545fdfc30 Current fffffe8545fdf3d0
Base fffffe8545fe0000 Limit fffffe8545fd9000 Call 0000000000000000
Priority 8 BasePriority 8 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
000000be`ae0ffbd8 00007ff6`19e21ac6 : 000000be`ae0ffc48 00000000`00000001 000000be`ae0ffc21 000000be`ae0ffc48 : 0x00007ffe`0995db42
000000be`ae0ffbe0 000000be`ae0ffc48 : 00000000`00000001 000000be`ae0ffc21 000000be`ae0ffc48 00000000`00000000 : 0x00007ff6`19e21ac6
000000be`ae0ffbe8 00000000`00000001 : 000000be`ae0ffc21 000000be`ae0ffc48 00000000`00000000 00000000`00000000 : 0x000000be`ae0ffc48
000000be`ae0ffbf0 000000be`ae0ffc21 : 000000be`ae0ffc48 00000000`00000000 00000000`00000000 00000002`00000002 : 0x1
000000be`ae0ffbf8 000000be`ae0ffc48 : 00000000`00000000 00000000`00000000 00000002`00000002 00000000`00000000 : 0x000000be`ae0ffc21
000000be`ae0ffc00 00000000`00000000 : 00000000`00000000 00000002`00000002 00000000`00000000 00001ac4`00000000 : 0x000000be`ae0ffc48
THREAD ffffd2024d550080 Cid 1ac4.2268 Teb: 000000beadf70000 Win32Thread: 0000000000000000 READY on processor 3 (Shared Ready Queue)
Not impersonating
DeviceMap ffffae04d22534d0
Owning Process ffffd2024d4e5080 Image: processA.exe
Attached Process ffffd2024cc5c080 Image: Notepad.exe
Wait Start TickCount 122221 Ticks: 0
Context Switch Count 0 IdealProcessor: 3
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x00007ff619e21720
Stack Init fffffe854603fc30 Current fffffe854603f920
Base fffffe8546040000 Limit fffffe8546039000 Call 0000000000000000
Priority 8 BasePriority 8 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
fffffe85`4603f960 fffff800`76ea0e90 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiStartUserThread
fffffe85`4603faa0 00007ffe`0c148d70 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiStartUserThreadReturn (TrapFrame @ fffffe85`4603faa0)
000000be`ae2ffc18 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ffe`0c148d70
ffffd2024d550080はprocessA.exe のスレッドでありながら、Notepad.exe のメモリ空間にアタッチしている状態になっている
発生したBSOD
INVALID_PROCESS_ATTACH_ATTEMPT (5)
Arguments:
Arg1: ffffd2024d4e5080
Arg2: ffffd2024cc5c080
Arg3: 0000000000000000
Arg4: ffffd2024d550080
STACK_TEXT:
fffffe85`4603ee08 fffff800`76daf3f2 : fffffe85`4603ee88 00000000`00000001 00000000`00000100 fffff800`76eb9501 : nt!DbgBreakPointWithStatus
fffffe85`4603ee10 fffff800`76dae91c : 00000000`00000003 fffffe85`4603ef70 fffff800`76eb9680 00000000`00000005 : nt!KiBugCheckDebugBreak+0x12
fffffe85`4603ee70 fffff800`76cf9387 : ffffd202`4d550080 fffffe85`4603f7e8 ffffd202`4d550080 00000000`00000000 : nt!KeBugCheck2+0xb2c
fffffe85`4603f600 fffff800`770f50c3 : 00000000`00000005 ffffd202`4d4e5080 ffffd202`4cc5c080 00000000`00000000 : nt!KeBugCheckEx+0x107
fffffe85`4603f640 fffff800`770f47e4 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!PspExitThread+0x6f3
fffffe85`4603f740 fffff800`76a86937 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSchedulerApcTerminate+0x34
fffffe85`4603f780 fffff800`76ea4600 : 00000000`00000000 fffffe85`4603f820 00000000`00000000 00000000`00000000 : nt!KiDeliverApc+0x4a7
fffffe85`4603f820 fffff800`76ea0fbb : ffff9d81`349e1180 00000000`00000000 ffffd202`4d550080 ffffd202`4cc5c67c : nt!KiInitiateUserApc+0x70
fffffe85`4603f960 fffff800`76ea0e90 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiStartUserThread+0xbb
fffffe85`4603faa0 00007ffe`0c148d70 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiStartUserThreadReturn
000000be`ae2ffc18 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart
結果
- KiStartUserThread でスレッドが動き始めましたが、直後にスレッドを終了(Terminate)しようとする。
- OS がスレッドを安全に破棄するために PspExitThread を呼び出すが、スレッドがNotepad.exe にアタッチされていることをカーネルが検知
- 「別プロセスのコンテキストにアタッチしたままスレッドを終了・破棄させると、メモリ空間やハンドルテーブルが破壊されてシステム崩壊につながる」ため、OS は安全のために即座に KeBugCheckEx を呼んで停止
つまりWindowsカーネルにとって、「アタッチ(KeStackAttachProcess)したら、作業が終わったら必ずデタッチ(KeUnstackDetachProcess)して元のプロセスに戻ってから死ぬ(終了する)」 というのは絶対。今回手動で書き換えたことで、その「絶対的な掟を守らせるための安全装置(INVALID_PROCESS_ATTACH_ATTEMPT)」が作動した
6. VadRoot (AVL Tree) のプロセス間スワップ
プロセスの仮想アドレス空間の管理情報である VadRoot(_RTL_AVL_TREE)を、processA.exe と powershell.exe の間で相互に書き換える。
1: kd> !process -1 0
PROCESS ffffd9052e2fa080
SessionId: none Cid: 08e0 Peb: 51669be000 ParentCid: 1ba0
DirBase: 98c5c000 ObjectTable: ffffc4862d635e00 HandleCount: 52.
Image: processA.exe
1: kd> !process 0 0 powershell.exe
PROCESS ffffd9052d8e3080
SessionId: none Cid: 1ba0 Peb: 720cb40000 ParentCid: 0c98
DirBase: b1ceb000 ObjectTable: ffffc48628f89ac0 HandleCount: 683.
Image: powershell.exe
1: kd> dt nt!_EPROCESS ffffd9052e2fa080
+0x558 VadRoot : _RTL_AVL_TREE
1: kd> dx -id 0,0,ffffd9052e2fa080 -r1 (*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052e2fa5d8))
(*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052e2fa5d8)) [Type: _RTL_AVL_TREE]
[+0x000] Root : 0xffffd9052e6d6200 [Type: _RTL_BALANCED_NODE *]
1: kd> dx -id 0,0,ffffd9052e2fa080 -r1 (*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052d8e35d8))
(*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052d8e35d8)) [Type: _RTL_AVL_TREE]
[+0x000] Root : 0xffffd9052e6bfa00 [Type: _RTL_BALANCED_NODE *]
eq ffffd9052d8e3080+0x558 ffffd9052e6d6200
eq ffffd9052e2fa080+0x558 ffffd9052e6bfa00
1: kd> dx -id 0,0,ffffd9052e2fa080 -r1 (*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052e2fa5d8))
(*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052e2fa5d8)) [Type: _RTL_AVL_TREE]
[+0x000] Root : 0xffffd9052e6bfa00 [Type: _RTL_BALANCED_NODE *]
1: kd> dx -id 0,0,ffffd9052e2fa080 -r1 (*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052d8e35d8))
(*((ntkrnlmp!_RTL_AVL_TREE *)0xffffd9052d8e35d8)) [Type: _RTL_AVL_TREE]
[+0x000] Root : 0xffffd9052e6d6200 [Type: _RTL_BALANCED_NODE *]
発生したBSOD
SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the BugCheck
Arg2: fffff8058b4b5627, Address of the instruction which caused the BugCheck
Arg3: ffffc2021d1d69b0, Address of the context record for the exception that caused the BugCheck
Arg4: 0000000000000000, zero.
STACK_TEXT:
ffffc202`1d1d7400 fffff805`8b4b5273 : ffffd905`2e6ac9a0 ffffd905`2e23c080 ffffd905`00000000 ffffc486`2d5b1300 : nt!MiRemoveSharedCommitNode+0x107
ffffc202`1d1d7460 fffff805`8b4b4e95 : 000001a8`80000000 ffffd905`2e6ac9a0 ffffd905`2e23c080 ffffd905`2c6a8c00 : nt!MiDeleteVad+0x31f
ffffc202`1d1d7510 fffff805`8b4b4e1b : ffffd905`2e6ac9a0 00000000`00000000 ffffd905`2e23c080 00000000`00000000 : nt!MiUnmapVad+0x49
ffffc202`1d1d7540 fffff805`8b604e43 : ffffd905`2e6ae3e0 ffffd905`2e23c080 ffffd905`2e23c080 ffffd905`2e23c080 : nt!MiCleanVad+0x2b
ffffc202`1d1d7570 fffff805`8b4e050a : ffffd905`00000000 ffffd905`2e2fa268 ffffc202`1d1d76e0 00000000`00000000 : nt!MmCleanProcessAddressSpace+0xfb
ffffc202`1d1d75f0 fffff805`8b51edae : ffffd905`2e2fa080 ffffd905`2e2fa080 ffffc202`1d1d76e0 00000000`00000000 : nt!PspRundownSingleProcess+0xc2
ffffc202`1d1d7680 fffff805`8b4f509d : 00000051`669bf000 00000000`00000000 ffffc202`1d1d78b8 ffffd905`2e23c080 : nt!PspExitLastThread+0xe6
ffffc202`1d1d7710 fffff805`8b4f47e4 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : nt!PspExitThread+0x6cd
ffffc202`1d1d7810 fffff805`8ae86937 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSchedulerApcTerminate+0x34
ffffc202`1d1d7850 fffff805`8b2a4600 : 00000000`00000000 ffffc202`1d1d78f0 00000000`00000000 00000000`00000000 : nt!KiDeliverApc+0x4a7
ffffc202`1d1d78f0 fffff805`8b2b310d : 00000000`00000000 00000000`0000000d ffffd905`2e23c080 00000051`6670f8f8 : nt!KiInitiateUserApc+0x70
ffffc202`1d1d7a30 00007fff`6f8e3d84 : 00007fff`6cf3f233 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceExit+0xad
00000051`6670f8d8 00007fff`6cf3f233 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!NtWaitForMultipleObjects+0x14
00000051`6670f8e0 00007fff`6cf3f101 : 00000051`6670fc60 00007ff6`86cf8df6 0000020c`a9406e70 00000000`00000000 : KERNELBASE!WaitForMultipleObjectsEx+0x123
00000051`6670fbd0 00007ff6`86cf1ae9 : 00000051`6670fc78 00000000`00000000 00000051`6670fc51 00000051`6670fc78 : KERNELBASE!WaitForMultipleObjects+0x11
00000051`6670fc10 00000051`6670fc78 : 00000000`00000000 00000051`6670fc51 00000051`6670fc78 00000000`00000000 : processA+0x1ae9
00000051`6670fc18 00000000`00000000 : 00000051`6670fc51 00000051`6670fc78 00000000`00000000 00000000`00000000 : 0x00000051`6670fc78
この時点でのページテーブルを確認するとこうなっている
0: kd> .cxr ffffc2021d1d69b0
rax=ffffd9052e23ca18 rbx=ffffd9052e2fa080 rcx=ffffc4862d5b1348
rdx=0000000000000000 rsi=ffffc4862d5b1300 rdi=0000000000000000
rip=fffff8058b4b5627 rsp=ffffc2021d1d7400 rbp=0000000000000000
r8=0000000000000000 r9=0000000000000001 r10=ffffd9052e23c9b0
r11=ffffd9052e23c080 r12=0000000000000000 r13=ffffd9052e2fa080
r14=ffffd9052c6a8b80 r15=0000000000000000
iopl=0 nv up ei pl zr na pe nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00050246
nt!MiRemoveSharedCommitNode+0x107:
fffff805`8b4b5627 48836f2001 sub qword ptr [rdi+20h],1 ds:002b:00000000`00000020=????????????????
0: kd> r cr3
Last set context:
cr3=0000000098c5c000
0: kd> !process ffffd9052e2fa080 0
PROCESS ffffd9052e2fa080
SessionId: none Cid: 08e0 Peb: 51669be000 ParentCid: 1ba0
DirBase: 98c5c000 ObjectTable: 00000000 HandleCount: 0.
Image: processA.exe
このことから例外発生時に使われていたページテーブル(CR3)は、processA.exe の DirBase と完全に一致しているのでコンテキストスイッチ/アドレス空間切り替えそのものは正常と言える
結果
プロセス終了処理(PspExitLastThread -> MmCleanProcessAddressSpace)において、カーネルがVADツリーを破棄・解放しようとした際、実際のページテーブル(PTE/CR3)が保持するページ割り当て情報とVADツリーの不整合 が発生し、メモリ管理サブシステム(nt!MiDeleteVad)内でクラッシュが発生。変更する場合はAPI関数 (KeStackAttachProcess)を呼ぶこと
まとめ
今回の実験を通じて、Windowsカーネルの堅牢性と整合性チェックの仕組みが明確になった
| 実験対象 | 操作内容 | 発生した現象 / BSOD | 主な原因 |
|---|---|---|---|
ThreadListHead |
ゼロクリア | PAGE_FAULT_IN_NONPAGED_AREA (50) |
Defenderの列挙処理によるNULL参照 |
PointerCount |
加算 (+10) | ゾンビスレッド化(リーク) | オブジェクトの参照カウントが残存 |
PointerCount |
減算 (-5) | REFERENCE_BY_POINTER (18) |
デレファレンス時のアンダーフロー |
_HANDLE_TABLE |
フラグ改変 | SYSTEM_SERVICE_EXCEPTION (3b) |
検索アルゴリズムと実データの不一致 |
ApcState.Process |
他プロセスへ書き換え | INVALID_PROCESS_ATTACH_ATTEMPT (5) |
アタッチ状態のままスレッドが終了 |
VadRoot |
他プロセスと交換 | SYSTEM_SERVICE_EXCEPTION (3b) |
アドレス空間クリーンアップ時のVAD/PTE不整合 |
カーネル内部の構造体をデバッガで直接改変すると、OSは多層化された検証メカニズム(PspExitThread や ObfDereferenceObject など)によって不正な状態を検知し、システムの破綻を防ぐために即座にBugCheckを発動させることが確認できる。そのため変更をする場合には、すべての項目を抜け漏れなく変更可能なAPIを使うことが推奨される
本検証に関するフィードバックについて
本記事で扱っている挙動は、OSの内部仕様や未定義動作に深く依存しています。環境による挙動の差異や、客観的なログを伴う反証・追考をお持ちの方は、以下のGitHubリポジトリ(Issue)までお寄せください。
※ 技術的整合性を保つため、コメントの投稿には「再現コード」「実行環境の情報」「WinDbg等の出力ログ」が必須となります。客観的なデータのないテキストのみの指摘や、再現性のない感情的なコメントは、予告なくクローズまたは削除いたしますので予めご了承ください。