Geoff Chappell, Software Analyst
CURRENT WORK ITEM - PREVIEW ONLY
The KPROCESS structure is the Kernel Core’s portion of the EPROCESS structure. The latter is the process object as exposed through the Object Manager. The KPROCESS is the core of it.
The KPROCESS structure is plainly internal to the kernel and its layout changes greatly between Windows versions and even between builds. In the following table of sizes, different builds of the same version are distinguished as early and late because they are known to vary the structure even if they don’t change the size. These descriptions, as early and late, are then used throughout the article as a shorthand.
Version | Size (x86) | Size (x64) |
---|---|---|
3.51 to 4.0 | 0x68 | |
5.0 early 5.1 (before Windows XP SP2) late 5.1 (Windows XP SP2 and higher) early 5.2 (before Windows Server 2003 SP1) |
0x6C | |
late 5.2 (Windows Server 2003 SP1 and higher) | 0x78 | 0xB8 |
early 6.0 (before Windows Vista SP1) late 6.0 (Windows Vista SP1 and higher) |
0x80 | 0xC0 |
6.1 | 0x98 | 0x0160 |
6.2 to 6.3 | 0xA0 | 0x02C8 |
10.0 | 0xA8 | 0x02D8 |
These sizes, and the offsets, types and names in the tables that follow, are from Microsoft’s symbol files for the kernel starting with Windows 2000 SP3. Since symbol files for earlier versions do not contain type information for the KPROCESS, what’s known for them is instead inferred from what use the kernel is seen to make of the KPROCESS. Sizes are relatively straightforward, even without symbol files, but Microsoft’s names and types are something of a guess. Where use of a member corresponds closely with that of a version for which Microsoft’s symbols are available, it seems reasonable to suppose continuity. Some use, however, has no correspondence, the code having changed too much. Even where the use hasn’t changed, tracking it down exhaustively would be difficult, if not impossible, even with source code.
For the detailed layout in the tables that follow, it helps with some processor-dependent definitions if a macro, say MAX_PROC_GROUPS, is presumed for the maximum number of processor groups:
Note that the intention here is not to say that these limits on processor groups are permanent, let alone that they should be depended on when programming, just that they’re what are built in to various members of the KPROCESS in the presently observed Windows versions.
It is well known that the KPROCESS, being a dispatcher object, begins with a DISPATCHER_HEADER in which the Type is 3, i.e., ProcessObject in the KOBJECTS enumeration.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 |
DISPATCHER_HEADER Header; |
3.51 and higher | |
0x10 | 0x18 |
LIST_ENTRY ProfileListHead; |
3.51 and higher | |
0x18 | 0x28 |
ULONG_PTR DirectoryTableBase [2]; |
3.51 to 5.2 | |
ULONG_PTR DirectoryTableBase; |
6.0 and higher | |||
0x1C (6.0) | 0x30 (6.0) |
ULONG_PTR Unused0; |
6.0 only | |
0x20 (3.51 to 6.0); 0x1C |
KGDTENTRY LdtDescriptor; |
3.51 and higher | ||
0x28 (3.51 to 6.0); 0x24 |
KIDTENTRY Int21Descriptor; |
3.51 and higher | ||
0x30 (3.51 to 6.0) | 0x38 (late 5.2 to 6.0) |
USHORT IopmOffset; |
3.51 to 6.0 | moves back (x86) |
0x32 (3.51 to 6.0) |
UCHAR Iopl; |
3.51 to early 6.0 | ||
UCHAR Unused1; |
late 6.0 only | |||
0x33 (3.51 to 6.0) |
UCHAR VdmFlag; |
3.51 to 5.0 | ||
UCHAR Unused; |
5.1 to early 6.0 | |||
UCHAR Unused2; |
late 6.0 only | |||
0x34 (3.51 to 6.0) | 0x40 (late 5.2 to 6.0) |
KAFFINITY ActiveProcessors; |
3.51 to 5.1 | |
KAFFINITY volatile ActiveProcessors; |
5.2 to 6.0 | moves back (changes type) | ||
0x38 (3.51 to 6.0) | 0x48 (5.0 to 6.0) |
ULONG KernelTime; |
3.51 to 6.0 | moves back |
0x3C (3.51 to 6.0) | 0x4C (5.0 to 6.0) |
ULONG UserTime; |
3.51 to 6.0 | moves back |
0x2C | 0x30 |
LIST_ENTRY ThreadListHead; |
6.1 and higher | from behind |
0x34 | 0x40 |
ULONG_PTR ProcessLock; |
6.1 only | from behind |
ULONG ProcessLock; |
6.2 and higher | |||
0x44 |
ULONG Spare0; |
6.2 and higher | ||
0x38 | 0x48 |
ULONGLONG DeepFreezeStartTime; |
10.0 and higher | |
0x38 (6.1 to 6.3); 0x40 |
0x48 (6.1 to 6.3); 0x50 |
KAFFINITY_EX Affinity; |
6.1 and higher | from behind (changed type) |
0x40 (3.51 to 6.0); 0x44 (6.1 to 6.3); 0x4C |
0x50 (late 5.2 to 6.0); 0x70 (6.1); 0xF0 (6.2 to 6.3); 0xF8 |
LIST_ENTRY ReadyListHead; |
3.51 and higher | |
0x48 (3.51 to 6.0); 0x4C (6.1 to 6.3); 0x54 |
LIST_ENTRY SwapListEntry; |
3.51 to 5.0 | ||
0x60 (late 5.2 to 6.0); 0x80 (6.1); 0x0100 (6.2 to 6.3); 0x0108 |
SINGLE_LIST_ENTRY SwapListEntry; |
5.1 and higher | ||
0x50 (6.1 to 6.3); 0x58 |
0x88 (6.1); 0x0108 (6.2 to 6.3); 0x0110 |
KAFFINITY_EX volatile ActiveProcessors; |
6.1 and higher | from ahead (changed type) |
0x4C (5.1 to 6.0) |
PVOID VdmTrapcHandler; |
5.1 to 6.0 | moves back | |
0x68 (late 5.2 to 6.0) |
PVOID Reserved1; |
late 5.2 only | ||
PVOID InstrumentationCallback; |
6.0 only | moves back | ||
0x50 (3.51 to 6.0) | 0x70 (late 5.2 to 6.0) |
LIST_ENTRY ThreadListHead; |
3.51 to 6.0 | moves ahead |
0x58 (3.51 to 6.0) | 0x80 (late 5.2 to 6.0) |
KSPIN_LOCK ProcessLock; |
3.51 to 6.0 | moves ahead |
0x5C (3.51 to 6.0) | 0x88 (late 5.2 to 6.0) |
KAFFINITY Affinity; |
3.51 to 6.0 | moves ahead (changes type) |
0x60 (3.51 to early 5.2) |
USHORT StackCount; |
3.51 to early 5.2 | moves back | |
0x60 (late 5.2 to 6.0); 0x5C (6.1 to 6.3); 0x64 |
0x90 (late 5.2 to 6.0); 0xB0 (6.1); 0x01B0 (6.2 to 6.3); 0x01B8 |
union { struct { /* bit fields, see below */ }; LONG ProcessFlags; }; |
late 5.2 only | |
union { struct { /* bit fields, see below */ }; LONG volatile ProcessFlags; }; |
6.0 and higher | |||
0x62 (3.51 to early 5.2); 0x64 (late 5.2 to 6.0); 0x60 (6.1 to 6.3); 0x68 |
0x94 (late 5.2 to 6.0); 0xB4 (6.1); 0x01B4 (6.2 to 6.3); 0x01BC |
CHAR BasePriority; |
3.51 and higher | |
0x63 (3.51 to early 5.2); 0x65 (late 5.2 to 6.0); 0x61 (6.1 to 6.3); 0x69 |
0x95 (late 5.2 to 6.0); 0xB5 (6.1); 0x01B5 (6.2 to 6.3); 0x01BD |
CHAR ThreadQuantum; |
3.51 to early 5.2 | |
CHAR QuantumReset; |
late 5.2 and higher | |||
0x64 (3.51 to early 5.2) |
BOOLEAN AutoAlignment; |
3.51 to early 5.2 | moves to ProcessFlags | |
0x65 (4.0 to early 5.2); 0x66 (late 5.2 to 6.0) |
0x96 (late 5.2 to 6.0) |
UCHAR State; |
3.51 to 6.0 | moves to StackCount; last member in 3.51 |
Version 4.0 adds just two bytes which anyway fit into the undefined space left for alignment. Neither stays put.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x66 (4.0 to early 5.2); 0x67 (late 5.2 to 6.0) |
0x97 (late 5.2 to 6.0) |
UCHAR ThreadSeed; |
4.0 to 6.0 | moves back (changes type) |
0x67 (4.0 to early 5.2) |
BOOLEAN DisableBoost; |
4.0 to early 5.2 | moves to ProcessFlags; last member in 4.0 |
The ThreadSeed helps to spread the process’s threads over the available processors. It is initialised pseudo-randomly (from the low byte of KeTickCount) when the process is created. As each thread is initialised, the process’s ThreadSeeed (modulo the number of processors) becomes that thread’s IdealProcessor and is then incremented in anticipation of the next thread. When Windows 7 greatly increased the potential number of processors, the single-byte processor number widened to the four-byte processor index and the ThreadSeed was reworked elsewhere in the structure.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x68 (5.0 to early 5.2); 0x68 (late 5.2 to 6.0) |
0x98 (late 5.2 to 6.0) |
UCHAR PowerState; |
5.0 to 6.0 | |
0x69 (5.0 to early 5.2) |
BOOLEAN DisableQuantum; |
5.0 to early 5.2 | moves to ProcessFlags | |
0x6A (5.0) |
UCHAR Spare [2]; |
5.0 only | last member in 5.0 |
Additions for version 5.1 begin with the bytes that version 5.0 left Spare at the end.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x6A (5.1 to early 5.2); 0x69 (late 5.2 to 6.0) |
0x99 (late 5.2 to 6.0) |
UCHAR IdealNode; |
5.1 to 6.0 | moves back (changes type) |
0x6A (late 5.2 to 6.0); 0x62 (6.1 to 6.3); 0x6A |
0x9A (late 5.2 to 6.0); 0xB6 (6.1); 0x01B6 (6.2 to 6.3); 0x01BE |
BOOLEAN Visited; |
late 5.2 and higher | |
0x6B (5.1 to 6.0); 0x63 (6.1 to 6.3); 0x6B |
UCHAR Spare; |
early 5.1 only | last member in early 5.1; last member in early 5.2 |
|
0x9B (late 5.2 to 6.0); 0xB7 (6.1); 0x01B7 (6.2 to 6.3); 0x01BF |
union { KEXECUTE_OPTIONS Flags; UCHAR ExecuteOptions; }; |
late 5.1; late 5.2 to 6.0 |
moves back (changes type); last member in late 5.1 |
|
UCHAR Spare3; |
6.1 and higher | |||
KEXECUTE_OPTIONS Flags; |
6.2 and higher | from behind |
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x64 (6.1 to 6.3); 0x6C |
0xB8 (6.1); 0x01B8 (6.2 to 6.3); 0x01C0 |
ULONG ThreadSeed [MAX_PROC_GROUPS]; |
6.1 and higher | from ahead (changed type) |
0x68 (6.1 to 6.3); 0x70 |
0xC8 (6.1); 0x0208 (6.2 to 6.3); 0x0210 |
USHORT IdealNode [MAX_PROC_GROUPS]; |
6.1 and higher | from ahead (changed type) |
0x6A (6.1 to 6.3); 0x72 |
0xD0 (6.1); 0x0230 (6.2 to 6.3); 0x0238 |
USHORT IdealGlobalNode; |
6.1 and higher | |
0x6C (6.1 to 6.3); 0x74 |
0xD2 (6.1); 0x0232 (6.2 to 6.3); 0x023A |
KEXECUTE_OPTIONS Flags; |
6.1 only | from ahead (changed type); moves ahead |
USHORT Spare1; |
6.2 and higher | |||
0x6D (6.1) | 0xD3 (6.1) |
UCHAR Unused1; |
6.1 only | |
0x6E (6.1 to 6.3); 0x76 |
USHORT IopmOffset; |
6.1 and higher | from ahead | |
0xD4 (6.1) |
ULONG Unused2; |
6.1 only | ||
0x70 (6.1 to 6.3); 0x78 |
0xD8 (6.1) |
ULONG Unused4; |
6.1 only | |
KSCHEDULING_GROUP *SchedulingGroup; |
6.2 and higher |
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x6C (late 5.2 to 6.0); 0x74 (6.1 to 6.3); 0x7C |
0xA0 (late 5.2 to 6.0); 0xDC (6.1); 0x0234 (6.2 to 6.3); 0x023C |
ULONG_PTR StackCount; |
late 5.2 to 6.0 | from ahead |
KSTACK_COUNT StackCount; |
6.1 only | |||
KSTACK_COUNT volatile StackCount; |
6.2 and higher | |||
0x70 (late 5.2 to 6.0); 0x78 (6.1 to 6.3); 0x80 |
0xA8 (late 5.2 to 6.0); 0xE0 (6.1); 0x0238 (6.2 to 6.3); 0x0240 |
LIST_ENTRY ProcessListEntry; |
late 5.2 and higher | last member in late 5.2 |
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x78 (6.0); 0x80 (6.1 to 6.3); 0x88 |
0xB8 (late 5.2 to 6.0); 0xF0 (6.1); 0x0248 (6.2 to 6.3); 0x0250 |
ULONGLONG volatile CycleTime; |
6.0 to 6.1 | last member in 6.0 |
ULONGLONG CycleTime; |
6.2 and higher |
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x88 (6.2 to 6.3); 0x90 |
0x0250 (6.2 to 6.3); 0x0258 |
ULONGLONG ContextSwitches; |
6.2 and higher | |
0x0258 (6.2 to 6.3); 0x0260 |
KSCHEDULING_GROUP *SchedulingGroup; |
6.2 and higher | ||
0x90 (6.2 to 6.3); 0x98 |
0x0260 (6.2 to 6.3); 0x0268 |
ULONG FreezeCount; |
6.2 and higher |
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x88 (6.1); 0x94 (6.2 to 6.3); 0x9C |
0xF8 (6.1); 0x0264 (6.2 to 6.3); 0x026C |
ULONG KernelTime; |
6.1 and higher | from ahead |
0x8C (6.1); 0x98 (6.2 to 6.3); 0xA0 |
0xFC (6.1); 0x0268 (6.2 to 6.3); 0x0270 |
ULONG UserTime; |
6.1 and higher | from ahead |
0x90 (6.1); 0x9C (6.2 to 6.3); 0xA4 |
PVOID VdmTrapcHandler; |
6.1 and higher | from ahead; last member in 6.1 (x86); last member in 6.2 (x86); last member in 6.3 (x86); last member in 10.0 (x86) |
The only truly new members that Windows 7 adds at the end are for 64-bit Windows only.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x0100 (6.1) |
PVOID InstrumentationCallback; |
6.1 only | from ahead; moves back |
|
0x0108 (6.1) |
KDGENTRY64 LdtSystemDescriptor; |
6.1 only | moves back | |
0x0118 (6.1) |
PVOID LdtBaseAddress; |
6.1 only | moves back | |
0x0120 (6.1) |
KGUARDED_MUTEX LdtProcessLock; |
6.1 only | moves back | |
0x0158 (6.1); 0x026C (6.2 to 6.3); 0x0274 |
USHORT LdtFreeSelectorHint; |
6.1 and higher | ||
0x015A (6.1); 0x026E (6.2 to 6.3); 0x0276 |
USHORT LdtTableLength; |
6.1 and higher | last member in 6.1 (x64) |
Because of shifts in preceding members, version 6.2 gets tighter packing just from rearranging what version 6.1 added.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x0270 (6.2 to 6.3); 0x0278 |
KGDTENTRY64 LdtSystemDescriptor; |
6.2 and higher | from ahead | |
0x0280 (6.2 to 6.3); 0x0288 |
PVOID LdtBaseAddress; |
6.2 and higher | from ahead | |
0x0288 (6.2 to 6.3); 0x0290 |
FAST_MUTEX LdtProcessLock; |
6.2 and higher | from ahead | |
0x02C0 (6.2 to 6.3); 0x02C8 |
PVOID InstrumentationCallback; |
6.2 and higher | from ahead; last member in 6.2 (x64); last member in 6.3 (x64) |
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x02D0 |
ULONGLONG SecurePid; |
10.0 and higher | last member in 10.0 (x64) |
The 32-bit bit fields in union with the ProcessFlags member have a complicated history that seems better presented separately from the structure. Notably, new versions bring not just new fields but redefinitions and even changes of type. The original, for the version 5.2 from Windows Server 2003 SP1, was a straightforward tidying up that collected what had been three byte-sized booleans, but then someone got exercised about volatility and perhaps someone else later decided it didn’t matter:
Mask | Definition | Versions |
---|---|---|
0x00000001 |
LONG AutoAlignment : 1; |
late 5.2 only |
LONG volatile AutoAlignment : 1; |
6.0 to 6.1 | |
LONG AutoAlignment : 1; |
6.2 and higher | |
0x00000002 |
LONG DisableBoost : 1; |
late 5.2 only |
LONG volatile DisableBoost : 1; |
6.0 to 6.1 | |
LONG DisableBoost : 1; |
6.2 and higher | |
0x00000004 |
LONG DisableQuantum : 1; |
late 5.2 only |
LONG volatile DisableQuantum : 1; |
6.0 to 6.1 | |
LONG DisableQuantum : 1; |
6.2 and higher |
Additions are instead complicated by insertion and deletion. How version 6.2 added one bit as signed and two as unsigned may forever be anyone’s guess.
Mask | Definition | Versions |
---|---|---|
0x00000008 (6.2 to 6.3) |
LONG AffinitySet : 1; |
6.2 to 6.3 |
0x00000010 (6.2 to 6.3); 0x00000008 |
ULONG DeepFreeze : 1; |
6.2 and higher |
0x00000020 (6.2 to 6.3); 0x00000010 |
ULONG TimeVirtualization : 1; |
6.2 and higher |
0x00000040 (6.3); 0x00000020 |
ULONG CheckStackExtents : 1; |
6.3 and higher |
0x000000C0 |
ULONG SpareFlags0 : 2; |
10.0 and higher |
An ActiveGroupsMask is kept as the last of the defined fields, perhaps so that its processor-dependent width does not affect other fields. Windows 10 perhaps inserts the SpareFlags0 to align ActiveGroupMasks while leaving space both before for more bits and after for more processor groups. All versions have reserved bits at the end.
Mask (x86) | Mask (x64) | Definition | Versions |
---|---|---|---|
0x00000008 (6.1); 0x00000040 (6.2); 0x00000080 (6.3); 0x00000100 |
0x00000078 (6.1); 0x03FFFFC0 (6.2); 0x07FFFF80 (6.3); 0x0FFFFF00 |
ULONG volatile ActiveGroupsMask : MAX_PROC_GROUPS; |
6.1 only |
ULONG ActiveGroupsMask : MAX_PROC_GROUPS; |
6.2 and higher | ||
LONG ReservedFlags : 29; |
late 5.2 only | ||
LONG volatile ReservedFlags : 29; |
6.0 only | ||
LONG volatile ReservedFlags : 29 - MAX_PROC_GROUPS; |
6.1 only | ||
LONG ReservedFlags : 26 - MAX_PROC_GROUPS; |
6.2 only | ||
LONG ReservedFlags : 25 - MAX_PROC_GROUPS; |
6.3 only | ||
LONG ReservedFlags : 24 - MAC_PROC_GROUPS; |
10.0 and higher |
The KEXECUTE_OPTIONS were introduced simultaneously for version 5.1 in Windows XP SP2 and version 5.2 in Windows Server 2003 SP1. While they seem to have no purpose outside the KPROCESS, they may as well be listed here. They were originally presented as a simple structure of bit fields:
typedef struct _KEXECUTE_OPTIONS { // late 5.2 to 6.0 /* bit fields, see below */ } KEXECUTE_OPTIONS;
Then Windows 7 wrapped the structure into a union with an integral member for accessing all bits together:
typedef union _KEXECUTE_OPTIONS { // 6.1 and higher struct { /* bit fields, see below */ }; UCHAR volatile ExecuteOptions; UCHAR ExceptionOptionsNV; // 6.2 and higher } KEXECUTE_OPTIONS;
Either way, the bit fields themselves remain the same except that one was added for Windows Vista SP1:
Mask | Definition | Versions |
---|---|---|
0x01 |
UCHAR ExecuteDisable : 1; |
late 5.1; late 5.2 and higher |
0x02 |
UCHAR ExecuteEnable : 1; |
late 5.1; late 5.2 and higher |
0x04 |
UCHAR DisableThunkEmulation : 1; |
late 5.1; late 5.2 and higher |
0x08 |
UCHAR Permanent : 1; |
late 5.1; late 5.2 and higher |
0x10 |
UCHAR ExecuteDispatchEnable : 1; |
late 5.1; late 5.2 and higher |
0x20 |
UCHAR ImageDispatchEnable : 1; |
late 5.1; late 5.2 and higher |
0x40 |
UCHAR DisableExceptionChainValidation : 1; |
late 6.0 and higher |
UCHAR Spare : 2; |
late 5.1; late 5.2 to early 6.0 |
|
UCHAR Spare : 1; |
late 6.0 and higher |
The KSTACK_COUNT also seems to have no purpose outside the KPROCESS:
typedef union _KSTACK_COUNT { // 6.1 only LONG volatile Value; struct { ULONG volatile State : 3; ULONG StackCount : 29; }; } KSTACK_COUNT;
It is not known what explains the mixtures of sign and volatility. Windows 8 shifts the volatility to where the structure appears in the KPROCESS as the StackCount member:
typedef union _KSTACK_COUNT { // 6.2 and higher LONG Value; struct { ULONG State : 3; ULONG StackCount : 29; }; } KSTACK_COUNT;