1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windowsカーネル構造体を破壊して学ぶ:ThreadListHead消滅、参照カウント異常、VadRootスワップから見るOS安全装置の深層

1
Posted at

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

クラッシュのメカニズム

  1. デバッガから実行を再開した際、Windows Defender(mpengine.dll)がプロセス通知を検知。
  2. Defender内部のLuaスクリプトが全プロセス・スレッド列挙のため NtQuerySystemInformation を発行。
  3. カーネル(ExpGetNextProcessThread)が processA.exeThreadListHead を参照しようとしたが、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_TABLETableCode 階層フラグ手動書き換え

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

結果

  1. KiStartUserThread でスレッドが動き始めましたが、直後にスレッドを終了(Terminate)しようとする。
  2. OS がスレッドを安全に破棄するために PspExitThread を呼び出すが、スレッドがNotepad.exe にアタッチされていることをカーネルが検知
  3. 「別プロセスのコンテキストにアタッチしたままスレッドを終了・破棄させると、メモリ空間やハンドルテーブルが破壊されてシステム崩壊につながる」ため、OS は安全のために即座に KeBugCheckEx を呼んで停止

つまりWindowsカーネルにとって、「アタッチ(KeStackAttachProcess)したら、作業が終わったら必ずデタッチ(KeUnstackDetachProcess)して元のプロセスに戻ってから死ぬ(終了する)」 というのは絶対。今回手動で書き換えたことで、その「絶対的な掟を守らせるための安全装置(INVALID_PROCESS_ATTACH_ATTEMPT)」が作動した


6. VadRoot (AVL Tree) のプロセス間スワップ

プロセスの仮想アドレス空間の管理情報である VadRoot_RTL_AVL_TREE)を、processA.exepowershell.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は多層化された検証メカニズム(PspExitThreadObfDereferenceObject など)によって不正な状態を検知し、システムの破綻を防ぐために即座にBugCheckを発動させることが確認できる。そのため変更をする場合には、すべての項目を抜け漏れなく変更可能なAPIを使うことが推奨される


本検証に関するフィードバックについて

本記事で扱っている挙動は、OSの内部仕様や未定義動作に深く依存しています。環境による挙動の差異や、客観的なログを伴う反証・追考をお持ちの方は、以下のGitHubリポジトリ(Issue)までお寄せください。

※ 技術的整合性を保つため、コメントの投稿には「再現コード」「実行環境の情報」「WinDbg等の出力ログ」が必須となります。客観的なデータのないテキストのみの指摘や、再現性のない感情的なコメントは、予告なくクローズまたは削除いたしますので予めご了承ください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?