
Time flies, nearly a year has passed since the last article analyzing the blue screen issue. Recently, an interesting dump file was collected, which has some noteworthy aspects—this article will use this dump file to delve into some details related to Windows process termination.
Crash Scene
First, let's take a look at the dump scene:
- 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
- ---------
The cause of this blue screen is relatively easy to analyze; the output from WinDbg has already provided a straightforward answer: INVALID_PROCESS_ATTACH_ATTEMPT—which means we attempted to attach to an invalid process. From the stack trace output, we can see that the process attached by KeAttachProcess is ffff860f`2b5c0240.
Let's examine this process:
- 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
The critical parts are highlighted in red. From the output of the !process command, it's evident that this process is highly abnormal, with data related to its memory, handles, kernel objects, etc., all set to NULL. This situation arises because the process has already exited, but the EPROCESS data structure representing the process still lingers in memory.
Let's confirm this further through the debugger:
- 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>
As we can see, apart from most fields being NULL or zero, this EPROCESS data structure remains relatively intact. Let's observe the following two fields:
- 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
We can see that the RundownProtect counter is already at 1, and Flags & 0x4 is not zero.
Let's present the conclusion first before diving deeper into the analysis: these current values of the two fields indicate that the process has already exited. The RundownProtect is a lock used during object destruction, and Windows increments this counter when destroying a process. The Flags field, on the other hand, is a flag that is set to the value 0x4 when the last thread of the process exits.
Solution
Now that we have identified the root cause of this blue screen, let's discuss the solution.
To prevent a blue screen when attaching to a process using the KeAttachProcess function, there appear to be the following necessary conditions:
- The first parameter of KeAttachProcess, the memory address of EPROCESS, must be valid.
- The memory of EPROCESS location contains an object of type Process.
- The process has not exited.
Since EPROCESS is a kernel object, the above condition 1 can be constrained using the following relationship:
- TargetMemory > MmHighestUserAddress && MmIsAddressValid(TargetMemory)
For condition 2, before providing a solution, we need to understand something called DISPATCHER_HEADER.
It's well known that Windows processes can be waited upon using WaitForXxxObject series functions. Not only processes, but also objects like EVENT, TIMER, etc., can be waited upon. Let's first look at the definitions for these types of objects:
- 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;
- …………
- };
As we can see, their definitions all start with DISPATCHER_HEADER, which is the key to why these objects can be waited upon Reference1. The Type member of DISPATCHER_HEADER indicates the object type, with the following defined types:
- 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;
Alright, let's return to the second critical issue at the beginning of this section—we can quickly determine if a block of memory is an EPROCESS by checking the DISPATCHER_HEADER->Type field for ProcessObject (0x3) type.
- 0x03 == ((DISPATCHER_HEADER *)TargetMemory)->Type
Now, only one question remains—how do we determine if a process has exited? Based on the previous analysis, we can use the RundownProtect field or the Flags field to make this determination. The new question is how to obtain the values of these two fields? After rummaging through the kernel, I found the following function:
- #define PS_PROCESS_FLAGS_PROCESS_EXITING 0x00000004UL // PspExitProcess entered
- BOOLEAN PsGetProcessExitProcessCalled(PEPROCESS Process)
- {
- return (BOOLEAN) ((Process->Flags & PS_PROCESS_FLAGS_PROCESS_EXITING) != 0);
- }
And this function is exported across different versions of the operating system's kernel:

Therefore, the third issue can be resolved using this exported function to check if the process has exited.
Here is the final code for attaching to a process:
- 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;
- }
Digging Deeper
In the previous section, we analyzed the root cause of the blue screen and provided a solution. However, the author's compulsion demands an explanation of why we can use Process->Flags & PS_PROCESS_FLAGS_PROCESS_EXITING to determine if a process has exited. Let's clarify this issue...
Before we start, let's take a look at a diagram:

As we know, although various ExitProcess functions are provided in the kernel, they ultimately call NtTerminateProcess. Therefore, the most important call path in the diagram is:
- NtTerminateProcess -> PspTerminateThreadByPointer -> PspExitThread -> PspExitProcess
In the PspExitProcess function, the PS_PROCESS_FLAGS_PROCESS_EXITING flag is set right at the beginning:
- VOID PspExitProcess(IN BOOLEAN LastThreadExit, IN PEPROCESS Process)
- {
- …………
- PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_EXITING);
- …………
- }
This is the fundamental reason why we can determine if a process has exited using PsGetProcessExitProcessCalled.
By quickly reviewing the functions in the aforementioned call chain, we can summarize as follows:
- NtTerminateProcess: This function acquires the process that needs to exit via the passed handle, then iterates through all threads in the process and calls PspTerminateThreadByPointer to end these threads.
- PspTerminateThreadByPointer: This function determines whether to directly use PspExitThread to exit the thread or to exit the thread via the APC queue based on certain conditions. The APC queue method will ultimately also call PspExitThread, so this function can be simplified as calling PspExitThread to exit the passed thread.
- PspExitThread: This function includes extensive state checks and event notifications (such as those set by PsSetCreateThreadNotifyRoutine). If the exiting thread is the last one in the current process, it will call PspExitProcess.
- PspExitProcess: This function is called when the last thread exits and invokes the process termination callback set by PsSetCreateProcessNotifyRoutine, performing final notifications and cleanup. At the beginning, it sets the process flags to PS_PROCESS_FLAGS_PROCESS_EXITING.
These functions contain very interesting details. It is recommended that those involved in low-level system development take a detailed look at this part of the code.