
不知不觉,距离上次蓝屏分析文章已经过去快要一年的时间,近期收集到了一个比较有意思的dump文件,有一些值得一说的地方——本文将借着这个dump文件,较为深入的分析一下Windows进程退出的一些相关细节。
车祸现场
首先,我们来看一下dump现场:
- Microsoft (R) Windows Debugger Version 10.0.26100.1 AMD64
- Copyright (c) Microsoft Corporation. All rights reserved.
- Loading Dump File [\\VBOXSVR\SharedFolder\MEMORY.DMP]
- Kernel Bitmap Dump File: Kernel address space is available, User address space may not be available.
- ************* Path validation summary **************
- Response Time (ms) Location
- Deferred srv*\\VBOXSVR\Symbols*http://msdl.blackint3.com:88/download/symbols
- Symbol search path is: srv*\\VBOXSVR\Symbols*http://msdl.blackint3.com:88/download/symbols
- Executable search path is:
- Unable to load image \SystemRoot\system32\ntoskrnl.exe, Win32 error 0n2
- Windows 10 Kernel Version 19041 MP (8 procs) Free x64
- Product: WinNt, suite: TerminalServer SingleUserTS
- Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
- Kernel base = 0xfffff806`71c1b000 PsLoadedModuleList = 0xfffff806`72845820
- Debug session time: Sat Sep 14 17:42:23.681 2024 (UTC + 8:00)
- System Uptime: 1 days 2:24:42.212
- Unable to load image \SystemRoot\system32\ntoskrnl.exe, Win32 error 0n2
- Loading Kernel Symbols
- ...............................................................
- ...........Page 6f0a3 not present in the dump file. Type ".hh dbgerr004" for details
- ........Page 6992d not present in the dump file. Type ".hh dbgerr004" for details
- .............................................
- ............................Page 441b5 not present in the dump file. Type ".hh dbgerr004" for details
- ....................................
- Loading User Symbols
- PEB is paged out (Peb.Ldr = 00000000`05701018). Type ".hh dbgerr001" for details
- Loading unloaded module list
- .................
- For analysis of this file, run !analyze -v
- 3: kd> !analyze -v
- *******************************************************************************
- * *
- * Bugcheck Analysis *
- * *
- *******************************************************************************
- INVALID_PROCESS_ATTACH_ATTEMPT (5)
- Arguments:
- Arg1: ffff860f2b5c0240
- Arg2: ffff860f289f0080
- Arg3: 0000000000000000
- Arg4: 0000000000000000
- Debugging Details:
- ------------------
- Unable to load image \??\C:\Windows\system32\drivers\constantine64.sys, Win32 error 0n2
- BUGCHECK_CODE: 5
- BUGCHECK_P1: ffff860f2b5c0240
- BUGCHECK_P2: ffff860f289f0080
- BUGCHECK_P3: 0
- BUGCHECK_P4: 0
- FILE_IN_CAB: MEMORY.DMP
- BLACKBOXBSD: 1 (!blackboxbsd)
- BLACKBOXNTFS: 1 (!blackboxntfs)
- BLACKBOXWINLOGON: 1
- PROCESS_NAME: QDoctor.exe
- STACK_TEXT:
- fffff90a`6e396188 fffff806`72094f52 : 00000000`00000005 ffff860f`2b5c0240 ffff860f`289f0080 00000000`00000000 : nt!KeBugCheckEx
- fffff90a`6e396190 fffff809`9692b74b : ffff860f`2b5c0240 00000000`00000002 ffff860f`2d1805d0 ffff860f`2c0f981e : nt!KeAttachProcess+0x154482
- fffff90a`6e3961d0 fffff809`969311c0 : ffff860f`2c377680 ffff860f`2b5c0240 ffff860f`2c0f981e ffff860f`00000000 : constantine64+0x3b74b
- fffff90a`6e3962a0 fffff809`9692f1d8 : ffff860f`2c5326e0 ffff860f`2b5c0240 00000000`00000000 00000000`00000000 : constantine64+0x411c0
- fffff90a`6e396360 fffff809`96fd835f : ffff860f`21d16cd0 0000002b`00000000 0000002b`00000053 0c91f070`0000002b : constantine64+0x3f1d8
- fffff90a`6e3963e0 fffff809`96fd6416 : 00000000`00000000 00000000`00000000 00000000`00000000 fffff90a`6e397118 : constantine64+0x6e835f
- fffff90a`6e3970e0 fffff806`71e65d55 : ffff860f`28d9c810 ffff860f`281f6100 fffff90a`6e397500 00000000`00000000 : constantine64+0x6e6416
- fffff90a`6e397170 fffff806`722482fc : 00000000`00000001 00000000`0022e228 ffff860f`2d1805d0 fffff806`00000000 : nt!IofCallDriver+0x55
- fffff90a`6e3971b0 fffff806`72247f4a : ffff860f`00000000 fffff90a`6e397500 00000000`00010000 00000000`0022e228 : nt!IopSynchronousServiceTail+0x34c
- fffff90a`6e397250 fffff806`72247226 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0xd0a
- fffff90a`6e3973a0 fffff806`7202d305 : 00000000`000005ac 00000000`0c81e428 00000000`0c81e438 00000000`00000008 : nt!NtDeviceIoControlFile+0x56
- fffff90a`6e397410 00000000`771a1cfc : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25
- 00000000`0c81ed38 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x771a1cfc
- SYMBOL_NAME: constantine64+3b74b
- MODULE_NAME: constantine64
- IMAGE_NAME: constantine64.sys
- STACK_COMMAND: .cxr; .ecxr ; kb
- BUCKET_ID_FUNC_OFFSET: 3b74b
- FAILURE_BUCKET_ID: 0x5_constantine64!unknown_function
- OS_VERSION: 10.0.19041.1
- BUILDLAB_STR: vb_release
- OSPLATFORM_TYPE: x64
- OSNAME: Windows 10
- FAILURE_ID_HASH: {bf2cf753-0fe1-6d0c-e7f3-1d053b3a225a}
- Followup: MachineOwner
- ---------
其实本次蓝屏的原因很容易分析,通过WinDbg的输出已经直观的给出了答案:INVALID_PROCESS_ATTACH_ATTEMPT——也就是我们Attach到了不可用的进程。通过堆栈的输出可知KeAttachProcess的进程为ffff860f`2b5c0240。
看下这个进程:
- 3: kd> !process ffff860f`2b5c0240
- PROCESS ffff860f2b5c0240
- SessionId: 0 Cid: 1874 Peb: 2502873000 ParentCid: 0d50
- DirBase: 597fb000 ObjectTable: 00000000 HandleCount: 0.
- Image: SearchFilterHo
- VadRoot 0000000000000000 Vads 0 Clone 0 Private 16. Modified 548. Locked 0.
- DeviceMap 0000000000000000
- Token 0000000000000000
- ElapsedTime 00:02:48.123
- UserTime 00:00:00.000
- KernelTime 00:00:00.031
- QuotaPoolUsage[PagedPool] 0
- QuotaPoolUsage[NonPagedPool] 0
- Working Set Sizes (now,min,max) (1, 50, 345) (4KB, 200KB, 1380KB)
- PeakWorkingSetSize 1892
- VirtualSize 0 Mb
- PeakVirtualSize 2101324 Mb
- PageFaultCount 3328
- MemoryPriority BACKGROUND
- BasePriority 8
- CommitCharge 4
- No active threads
关键地方我标红了,从!process命令的输出来看,此进程极度不正常,其内存、句柄、内核对象等相关的数据均为NULL。其实,这种情况是因为该进程已经退出了,但是代表该进程的EPROCESS数据结构依然还残留在内存中。
让我们通过调试器进一步确认一下:
- 3: kd> dt _EPROCESS ffff860f`2b5c0240
- nt!_EPROCESS
- +0x000 Pcb : _KPROCESS
- +0x438 ProcessLock : _EX_PUSH_LOCK
- +0x440 UniqueProcessId : 0x00000000`00001874 Void
- +0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffff860f`286634c8 - 0xffff860f`2b694688 ]
- +0x458 RundownProtect : _EX_RUNDOWN_REF
- +0x460 Flags2 : 0xd81f
- +0x460 JobNotReallyActive : 0y1
- +0x460 AccountingFolded : 0y1
- +0x460 NewProcessReported : 0y1
- +0x460 ExitProcessReported : 0y1
- +0x460 ReportCommitChanges : 0y1
- +0x460 LastReportMemory : 0y0
- +0x460 ForceWakeCharge : 0y0
- +0x460 CrossSessionCreate : 0y0
- +0x460 NeedsHandleRundown : 0y0
- +0x460 RefTraceEnabled : 0y0
- +0x460 PicoCreated : 0y0
- +0x460 EmptyJobEvaluated : 0y1
- +0x460 DefaultPagePriority : 0y101
- +0x460 PrimaryTokenFrozen : 0y1
- +0x460 ProcessVerifierTarget : 0y0
- +0x460 RestrictSetThreadContext : 0y0
- +0x460 AffinityPermanent : 0y0
- +0x460 AffinityUpdateEnable : 0y0
- +0x460 PropagateNode : 0y0
- +0x460 ExplicitAffinity : 0y0
- +0x460 ProcessExecutionState : 0y00
- +0x460 EnableReadVmLogging : 0y0
- +0x460 EnableWriteVmLogging : 0y0
- +0x460 FatalAccessTerminationRequested : 0y0
- +0x460 DisableSystemAllowedCpuSet : 0y0
- +0x460 ProcessStateChangeRequest : 0y00
- +0x460 ProcessStateChangeInProgress : 0y0
- +0x460 InPrivate : 0y0
- +0x464 Flags : 0x524c0c2d
- +0x464 CreateReported : 0y1
- +0x464 NoDebugInherit : 0y0
- +0x464 ProcessExiting : 0y1
- +0x464 ProcessDelete : 0y1
- +0x464 ManageExecutableMemoryWrites : 0y0
- +0x464 VmDeleted : 0y1
- +0x464 OutswapEnabled : 0y0
- +0x464 Outswapped : 0y0
- +0x464 FailFastOnCommitFail : 0y0
- +0x464 Wow64VaSpace4Gb : 0y0
- +0x464 AddressSpaceInitialized : 0y11
- +0x464 SetTimerResolution : 0y0
- +0x464 BreakOnTermination : 0y0
- +0x464 DeprioritizeViews : 0y0
- +0x464 WriteWatch : 0y0
- +0x464 ProcessInSession : 0y0
- +0x464 OverrideAddressSpace : 0y0
- +0x464 HasAddressSpace : 0y1
- +0x464 LaunchPrefetched : 0y1
- +0x464 Background : 0y0
- +0x464 VmTopDown : 0y0
- +0x464 ImageNotifyDone : 0y1
- +0x464 PdeUpdateNeeded : 0y0
- +0x464 VdmAllowed : 0y0
- +0x464 ProcessRundown : 0y1
- +0x464 ProcessInserted : 0y0
- +0x464 DefaultIoPriority : 0y010
- +0x464 ProcessSelfDelete : 0y1
- +0x464 SetTimerResolutionLink : 0y0
- +0x468 CreateTime : _LARGE_INTEGER 0x01db068a`028c7a84
- +0x470 ProcessQuotaUsage : [2] 0
- +0x480 ProcessQuotaPeak : [2] 0x2418
- +0x490 PeakVirtualSize : 0x00000201`04cf7000
- +0x498 VirtualSize : 0
- +0x4a0 SessionProcessLinks : _LIST_ENTRY [ 0xffff860f`2decb7a0 - 0xffff860f`2b6946e0 ]
- +0x4b0 ExceptionPortData : (null)
- +0x4b0 ExceptionPortValue : 0
- +0x4b0 ExceptionPortState : 0y000
- +0x4b8 Token : _EX_FAST_REF
- +0x4c0 MmReserved : 0
- +0x4c8 AddressCreationLock : _EX_PUSH_LOCK
- +0x4d0 PageTableCommitmentLock : _EX_PUSH_LOCK
- +0x4d8 RotateInProgress : (null)
- +0x4e0 ForkInProgress : (null)
- +0x4e8 CommitChargeJob : (null)
- +0x4f0 CloneRoot : _RTL_AVL_TREE
- +0x4f8 NumberOfPrivatePages : 0x10
- +0x500 NumberOfLockedPages : 0
- +0x508 Win32Process : (null)
- +0x510 Job : (null)
- +0x518 SectionObject : (null)
- +0x520 SectionBaseAddress : 0x00007ff6`1fa10000 Void
- +0x528 Cookie : 0xf75d4a2
- +0x530 WorkingSetWatch : (null)
- +0x538 Win32WindowStation : 0x00000000`00000170 Void
- +0x540 InheritedFromUniqueProcessId : 0x00000000`00000d50 Void
- +0x548 OwnerProcessId : 1
- +0x550 Peb : 0x00000025`02873000 _PEB
- +0x558 Session : 0xffffd780`d11fc000 _MM_SESSION_SPACE
- +0x560 Spare1 : (null)
- +0x568 QuotaBlock : 0xfffff806`7286eac0 _EPROCESS_QUOTA_BLOCK
- +0x570 ObjectTable : (null)
- +0x578 DebugPort : (null)
- +0x580 WoW64Process : (null)
- +0x588 DeviceMap : (null)
- +0x590 EtwDataSource : (null)
- +0x598 PageDirectoryPte : 0
- +0x5a0 ImageFilePointer : (null)
- +0x5a8 ImageFileName : [15] "SearchFilterHo"
- +0x5b7 PriorityClass : 0x2 ''
- +0x5b8 SecurityPort : 0x00000000`00000001 Void
- +0x5c0 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
- +0x5c8 JobLinks : _LIST_ENTRY [ 0xffff860f`2749a088 - 0xffff860f`2749a088 ]
- +0x5d8 HighestUserAddress : 0x00007fff`ffff0000 Void
- +0x5e0 ThreadListHead : _LIST_ENTRY [ 0xffff860f`2b5c0820 - 0xffff860f`2b5c0820 ]
- +0x5f0 ActiveThreads : 0
- +0x5f4 ImagePathHash : 0x44162447
- +0x5f8 DefaultHardErrorProcessing : 0
- +0x5fc LastThreadExitStatus : 0n0
- +0x600 PrefetchTrace : _EX_FAST_REF
- +0x608 LockedPagesList : (null)
- +0x610 ReadOperationCount : _LARGE_INTEGER 0x1
- +0x618 WriteOperationCount : _LARGE_INTEGER 0x0
- +0x620 OtherOperationCount : _LARGE_INTEGER 0x34
- +0x628 ReadTransferCount : _LARGE_INTEGER 0x119a
- +0x630 WriteTransferCount : _LARGE_INTEGER 0x0
- +0x638 OtherTransferCount : _LARGE_INTEGER 0xa0
- +0x640 CommitChargeLimit : 0
- +0x648 CommitCharge : 4
- +0x650 CommitChargePeak : 0x1b1
- +0x680 Vm : _MMSUPPORT_FULL
- +0x7c0 MmProcessLinks : _LIST_ENTRY [ 0xffff860f`28663840 - 0xffff860f`2b694a00 ]
- +0x7d0 ModifiedPageCount : 0x224
- +0x7d4 ExitStatus : 0n0
- +0x7d8 VadRoot : _RTL_AVL_TREE
- +0x7e0 VadHint : (null)
- +0x7e8 VadCount : 0
- +0x7f0 VadPhysicalPages : 0
- +0x7f8 VadPhysicalPagesLimit : 0
- +0x800 AlpcContext : _ALPC_PROCESS_CONTEXT
- +0x820 TimerResolutionLink : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
- +0x830 TimerResolutionStackRecord : (null)
- +0x838 RequestedTimerResolution : 0
- +0x83c SmallestTimerResolution : 0
- +0x840 ExitTime : _LARGE_INTEGER 0x01db068a`5311a5fc
- +0x848 InvertedFunctionTable : (null)
- +0x850 InvertedFunctionTableLock : _EX_PUSH_LOCK
- +0x858 ActiveThreadsHighWatermark : 7
- +0x85c LargePrivateVadCount : 0
- +0x860 ThreadListLock : _EX_PUSH_LOCK
- +0x868 WnfContext : 0xffff9c0f`c9fdb870 Void
- +0x870 ServerSilo : 0xffffd780`cc19a000 _EJOB
- +0x878 SignatureLevel : 0 ''
- +0x879 SectionSignatureLevel : 0 ''
- +0x87a Protection : _PS_PROTECTION
- +0x87b HangCount : 0y000
- +0x87b GhostCount : 0y000
- +0x87b PrefilterException : 0y0
- +0x87c Flags3 : 0x40c008
- +0x87c Minimal : 0y0
- +0x87c ReplacingPageRoot : 0y0
- +0x87c Crashed : 0y0
- +0x87c JobVadsAreTracked : 0y1
- +0x87c VadTrackingDisabled : 0y0
- +0x87c AuxiliaryProcess : 0y0
- +0x87c SubsystemProcess : 0y0
- +0x87c IndirectCpuSets : 0y0
- +0x87c RelinquishedCommit : 0y0
- +0x87c HighGraphicsPriority : 0y0
- +0x87c CommitFailLogged : 0y0
- +0x87c ReserveFailLogged : 0y0
- +0x87c SystemProcess : 0y0
- +0x87c HideImageBaseAddresses : 0y0
- +0x87c AddressPolicyFrozen : 0y1
- +0x87c ProcessFirstResume : 0y1
- +0x87c ForegroundExternal : 0y0
- +0x87c ForegroundSystem : 0y0
- +0x87c HighMemoryPriority : 0y0
- +0x87c EnableProcessSuspendResumeLogging : 0y0
- +0x87c EnableThreadSuspendResumeLogging : 0y0
- +0x87c SecurityDomainChanged : 0y0
- +0x87c SecurityFreezeComplete : 0y1
- +0x87c VmProcessorHost : 0y0
- +0x87c VmProcessorHostTransition : 0y0
- +0x87c AltSyscall : 0y0
- +0x87c TimerResolutionIgnore : 0y0
- +0x87c DisallowUserTerminate : 0y0
- +0x880 DeviceAsid : 0n0
- +0x888 SvmData : (null)
- +0x890 SvmProcessLock : _EX_PUSH_LOCK
- +0x898 SvmLock : 0
- +0x8a0 SvmProcessDeviceListHead : _LIST_ENTRY [ 0xffff860f`2b5c0ae0 - 0xffff860f`2b5c0ae0 ]
- +0x8b0 LastFreezeInterruptTime : 0
- +0x8b8 DiskCounters : 0xffff860f`2b5c0c80 _PROCESS_DISK_COUNTERS
- +0x8c0 PicoContext : (null)
- +0x8c8 EnclaveTable : (null)
- +0x8d0 EnclaveNumber : 0
- +0x8d8 EnclaveLock : _EX_PUSH_LOCK
- +0x8e0 HighPriorityFaultsAllowed : 0
- +0x8e8 EnergyContext : 0xffff860f`2b5c0ca8 _PO_PROCESS_ENERGY_CONTEXT
- +0x8f0 VmContext : (null)
- +0x8f8 SequenceNumber : 0x53a
- +0x900 CreateInterruptTime : 0x000000dd`10df825f
- +0x908 CreateUnbiasedInterruptTime : 0x000000dd`10df825f
- +0x910 TotalUnbiasedFrozenTime : 0
- +0x918 LastAppStateUpdateTime : 0x000000dd`61613aec
- +0x920 LastAppStateUptime : 0y0000000000000000000000000000001010000100000011011100010001101 (0x5081b88d)
- +0x920 LastAppState : 0y011
- +0x928 SharedCommitCharge : 0
- +0x930 SharedCommitLock : _EX_PUSH_LOCK
- +0x938 SharedCommitLinks : _LIST_ENTRY [ 0xffff860f`2b5c0b78 - 0xffff860f`2b5c0b78 ]
- +0x948 AllowedCpuSets : 0
- +0x950 DefaultCpuSets : 0
- +0x948 AllowedCpuSetsIndirect : (null)
- +0x950 DefaultCpuSetsIndirect : (null)
- +0x958 DiskIoAttribution : (null)
- +0x960 DxgProcess : (null)
- +0x968 Win32KFilterSet : 0
- +0x970 ProcessTimerDelay : _PS_INTERLOCKED_TIMER_DELAY_VALUES
- +0x978 KTimerSets : 0
- +0x97c KTimer2Sets : 0
- +0x980 ThreadTimerSets : 0
- +0x988 VirtualTimerListLock : 0
- +0x990 VirtualTimerListHead : _LIST_ENTRY [ 0xffff860f`2b5c0bd0 - 0xffff860f`2b5c0bd0 ]
- +0x9a0 WakeChannel : _WNF_STATE_NAME
- +0x9a0 WakeInfo : _PS_PROCESS_WAKE_INFORMATION
- +0x9d0 MitigationFlags : 0xa0021
- +0x9d0 MitigationFlagsValues : <anonymous-tag>
- +0x9d4 MitigationFlags2 : 0x40000000
- +0x9d4 MitigationFlags2Values : <anonymous-tag>
- +0x9d8 PartitionObject : 0xffff860f`222c17a0 Void
- +0x9e0 SecurityDomain : 0x00000001`0000041b
- +0x9e8 ParentSecurityDomain : 0
- +0x9f0 CoverageSamplerContext : (null)
- +0x9f8 MmHotPatchContext : (null)
- +0xa00 DynamicEHContinuationTargetsTree : _RTL_AVL_TREE
- +0xa08 DynamicEHContinuationTargetsLock : _EX_PUSH_LOCK
- +0xa10 DynamicEnforcedCetCompatibleRanges : _PS_DYNAMIC_ENFORCED_ADDRESS_RANGES
- +0xa20 DisabledComponentFlags : 0
- +0xa28 PathRedirectionHashes : (null)
- +0xa30 MitigationFlags3 : 0
- +0xa30 MitigationFlags3Values : <anonymous-tag>
可见该EPROCESS数据结构,除去绝大部分字段已经是NULL或0之外,还算完整。继续观察下面两个字段:
- 3: kd> dt _EPROCESS ffff860f`2b5c0240 RundownProtect
- nt!_EPROCESS
- +0x458 RundownProtect : _EX_RUNDOWN_REF
- 3: kd> dx -id 0,0,ffff860f289f0080 -r1 (*((ntkrnlmp!_EX_RUNDOWN_REF *)0xffff860f2b5c0698))
- (*((ntkrnlmp!_EX_RUNDOWN_REF *)0xffff860f2b5c0698)) [Type: _EX_RUNDOWN_REF]
- [+0x000] Count : 0x1 [Type: unsigned __int64]
- [+0x000] Ptr : 0x1 [Type: void *]
- 3: kd> dt _EPROCESS ffff860f`2b5c0240 Flags
- nt!_EPROCESS
- +0x464 Flags : 0x524c0c2d
- 3: kd> ?0x524c0c2d&0x4
- Evaluate expression: 4 = 00000000`00000004
可以看到,其RundownProtect计数已经为1,Flags & 0x4不为0。
这里先抛结论,后面再做更深入的解析:这两个字段的当前值,表示该进程已经退出。RundownProtect为对象销毁时的锁,Windows在执行进程销毁时会引用该锁使其计数加一,而Flags则为标志位,上文中的值0x4为进程的最后一个线程退出时设置的值。
解决方案
目前我们已经明确了本次蓝屏产生的原因,那么接下来我们讨论一下解决方案。
要想KeAttachProcess函数附加进程不蓝屏,目前来看有如下几个必要条件:
- KeAttachProcess的第一个参数EPROCESS内存地址需要有效
- EPROCESS内存存放的是一个Process类型的对象
- 进程没有退出
由于EPROCESS是个内核的玩意,因此上述条件1可以使用如下关系来约束:
- TargetMemory > MmHighestUserAddress && MmIsAddressValid(TargetMemory)
对于条件2,给出解决方案之前我们要先搞明白一个叫做DISPATCHER_HEADER的东西。
众所周知,Windows的进程是可以通过WaitForXxxObject系列函数进行等待的,实际上不光进程,像EVENT、TIMER等对象都是可以等待的对象类型,我们先来看一下这几种对象的定义:
- typedef struct _DISPATCHER_HEADER {
- union {
- struct {
- UCHAR Type;
- UCHAR Absolute;
- UCHAR Size;
- union {
- UCHAR Inserted;
- BOOLEAN DebugActive;
- };
- };
- volatile LONG Lock;
- };
- LONG SignalState;
- LIST_ENTRY WaitListHead;
- } DISPATCHER_HEADER;
- //
- // Event object
- //
- typedef struct _KEVENT {
- DISPATCHER_HEADER Header;
- } KEVENT, *PKEVENT, *RESTRICTED_POINTER PRKEVENT;
- //
- // Timer object
- //
- typedef struct _KTIMER {
- DISPATCHER_HEADER Header;
- ULARGE_INTEGER DueTime;
- LIST_ENTRY TimerListEntry;
- struct _KDPC *Dpc;
- LONG Period;
- } KTIMER, *PKTIMER, *RESTRICTED_POINTER PRKTIMER;
- //
- // Process object
- //
- typedef struct _KPROCESS {
- //
- // The dispatch header and profile listhead are fairly infrequently
- // referenced.
- //
- DISPATCHER_HEADER Header;
- LIST_ENTRY ProfileListHead;
- …………
- };
- typedef struct _EPROCESS {
- KPROCESS Pcb;
- …………
- };
可以看到其定义都是以DISPATCHER_HEADER开头,这个头就是这些对象可以被Wait的关键所在参考1。而DISPATCHER_HEADER的Type成员则指示了对象类型,可用的类型定义如下:
- typedef enum _KOBJECTS {
- EventNotificationObject = 0,
- EventSynchronizationObject = 1,
- MutantObject = 2,
- ProcessObject = 3,
- QueueObject = 4,
- SemaphoreObject = 5,
- ThreadObject = 6,
- Spare1Object = 7,
- TimerNotificationObject = 8,
- TimerSynchronizationObject = 9,
- Spare2Object = 10,
- Spare3Object = 11,
- Spare4Object = 12,
- Spare5Object = 13,
- Spare6Object = 14,
- Spare7Object = 15,
- Spare8Object = 16,
- Spare9Object = 17,
- ApcObject,
- DpcObject,
- DeviceQueueObject,
- EventPairObject,
- InterruptObject,
- ProfileObject,
- ThreadedDpcObject,
- MaximumKernelObject
- } KOBJECTS;
OK,让我们回到本节一开始的第二个关键问题上——我们可以通过检查DISPATCHER_HEADER->Type字段是否为ProcessObject(0x3)来快速判断某块内存是否为EPROCESS:
- 0x03 == ((DISPATCHER_HEADER *)TargetMemory)->Type
现在,只剩下最后一个问题——我们如何判断进程有没有退出?通过上文的分析可知,可以利用RundownProtect字段或者Flags字段来进行判断,那么新的问题就是如何获取这两个字段的值呢?通过在内核中翻箱倒柜,笔者找到了如下函数:
- #define PS_PROCESS_FLAGS_PROCESS_EXITING 0x00000004UL // PspExitProcess entered
- BOOLEAN PsGetProcessExitProcessCalled(PEPROCESS Process)
- {
- return (BOOLEAN) ((Process->Flags & PS_PROCESS_FLAGS_PROCESS_EXITING) != 0);
- }
而且该函数在各个版本操作系统的内核中均为导出函数:

因此,第三个问题可以使用该导出函数判断进程是否退出。
我们最终附加进程的代码如下:
- BOOLEAN AttachProcessSafe (PEPROCESS TargetMemory)
- {
- if (TargetMemory > MmHighestUserAddress && MmIsAddressValid(TargetMemory))
- {
- if (0x03 == ((DISPATCHER_HEADER *)TargetMemory)->Type)
- {
- if (!PsGetProcessExitProcessCalled((PEPROCESS)TargetMemory))
- {
- KeAttachProcess((PEPROCESS)TargetMemory;
- return TRUE;
- }
- }
- }
- return FALSE;
- }
刨根问底
上文我们分析了蓝屏根因且给出了解决方案,但是写到这里笔者的强迫症犯了,为什么可以用Process->Flags & PS_PROCESS_FLAGS_PROCESS_EXITING来判断进程是否退出?接下来让我们搞清楚这个问题……
在正式开始之前,我们先来看一张图:

众所周知, 虽然内核中提供了各种ExitProcess的函数,但这些函数最终都会调用到NtTerminateProcess。因此,上图中最重要的调用路径为:
- NtTerminateProcess -> PspTerminateThreadByPointer -> PspExitThread -> PspExitProcess
而PspExitProcess函数中一开始就设置了PS_PROCESS_FLAGS_PROCESS_EXITING标志位:
- VOID PspExitProcess(IN BOOLEAN LastThreadExit, IN PEPROCESS Process)
- {
- …………
- PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_EXITING);
- …………
- }
这也是为何我们可以根据PsGetProcessExitProcessCalled判断进程是否退出的最根本原因。
通过快速浏览上述调用链中的各个函数,总结如下:
- NtTerminateProcess: 该函数通过传入的句柄获取需要退出的进程,然后遍历进程中的所有线程,并调用PspTerminateThreadByPointer函数结束这些线程。
- PspTerminateThreadByPointer: 该函数会根据一些条件判断是直接使用PspExitThread退出线程还是通过APC队列退出线程,APC队列退出的方式,最终也会调用PspExitThread,因此该函数的作用可以简化为调用PspExitThread退出传入的线程。
- PspExitThread: 该函数里面有大量的状态检查和事件通知(例如著名的PsSetCreateThreadNotifyRoutine设置的进程线程通知),如果结束的是当前进程的最后一个线程,则会调用PspExitProcess函数。
- PspExitProcess: 此函数会在最后一个线程退出时调用由PsSetCreateProcessNotifyRoutine设置的进程结束回调函数,并做一些最后的通知和清理工作,该函数会在一开始设置进程Flags为PS_PROCESS_FLAGS_PROCESS_EXITING。
上述这些函数,细节非常的有趣。建议从事系统底层开发的人员详细看一下这块的代码;)