A process on Windows is not a unit of execution — it is a container. It owns a private virtual address space, a handle table listing the kernel objects it may touch, a security context that decides what it is allowed to do, and one or more threads that actually run code. The threads do the work; the process simply holds the resources they share. Keeping that distinction clear is the key to understanding almost everything the kernel does, because the executive tracks a process through a single large structure — EPROCESS — that ties all of those resources together. This article dissects that structure, the user-mode counterpart it points to, and why several fields inside it are prime targets for offensive tooling.
EPROCESS: The Kernel’s View of a Process
Every process is represented in kernel space by an executive process structure, nt!_EPROCESS. It lives in non-paged kernel memory, and user mode never sees it directly. The structure is large and version-dependent, but its role is constant: it is the master record that the memory manager, the object manager, the security reference monitor, and the scheduler all consult when they need to know something about a process.
The very first member of EPROCESS is Pcb, an embedded KPROCESS (the kernel “process control block”). Because it sits at offset 0, a pointer to an EPROCESS is also a valid pointer to its KPROCESS — the scheduler can treat the same address as either view. Surrounding that core are the executive-level fields that give the process its identity and resources:
UniqueProcessId— the PID, the value you see in Task Manager.ActiveProcessLinks— aLIST_ENTRYthat threads every process on the system into one doubly linked list. The list head is the kernel globalPsActiveProcessHead.DirectoryTableBase— the physical address of the process’s top-level page directory, the value loaded intoCR3when the process is scheduled. This is what makes the address space “private.”Token— a reference to the process’s access token, the object that carries its user and group SIDs and privileges.ObjectTable— a pointer to the handle table, the per-process array mapping handle values to kernel objects.Peb— a pointer to the user-mode Process Environment Block (see below).VadRoot— the root of the Virtual Address Descriptor tree, the balanced tree describing every reserved or committed region in the address space.ImageFileName— a short (15-character) cached copy of the executable’s file name, handy for quick identification but not authoritative.InheritedFromUniqueProcessId— the PID of the creating process, which is how tools reconstruct parent/child relationships.
KPROCESS and Scheduling State
The embedded KPROCESS holds everything the scheduler (the “kernel dispatcher”) needs, deliberately separated from the higher-level executive concerns. Where EPROCESS answers “who is this process and what does it own,” KPROCESS answers “how should its threads run.” Its important members include:
- A list head linking all the threads that belong to the process, so the scheduler can enumerate them.
- The process base priority, which sets the floor for the priorities of its threads.
- The default processor affinity mask, controlling which logical CPUs the threads may run on.
- The
DirectoryTableBaseagain at the kernel level, since address-space switching happens during scheduling. - Accumulated kernel and user CPU time counters for the process.
This layering is a recurring theme in Windows internals: a K-prefixed structure carries the raw scheduling and hardware state, and the E-prefixed structure wraps it with policy, identity, and resource ownership.
The PEB: The Process’s User-Mode View
Not all per-process state lives in the kernel. The Process Environment Block (PEB) is a structure in the process’s own user-mode address space that holds information the process and the loader need to consult constantly — and which does not require a kernel transition to read. EPROCESS.Peb stores its address from the kernel side; from user mode a thread reaches it through its Thread Environment Block, which on x64 is pointed to by gs:[0x30], with the PEB pointer at gs:[0x60].
Key PEB fields include:
Ldr— a pointer to the loader data, which maintains three linked lists of loaded modules:InLoadOrderModuleList,InMemoryOrderModuleList, andInInitializationOrderModuleList. Walking these lists is how a process (or shellcode) enumerates loaded DLLs without calling an API.ProcessParameters— aRTL_USER_PROCESS_PARAMETERSblock containing the command line, image path, current directory, and environment variables.ImageBaseAddress— where the main executable is mapped.BeingDebugged— a single byte set when a user-mode debugger is attached. It is the value behind theIsDebuggerPresentAPI.
Because these fields are readable without a syscall, they are a favorite of both packers and anti-analysis code. Reading BeingDebugged directly from the PEB, or checking the NtGlobalFlag field (which takes on tell-tale heap-debugging bits like FLG_HEAP_ENABLE_TAIL_CHECK when a process is spawned under a debugger), are classic anti-debug tricks precisely because they sidestep the APIs that a debugger might hook.
Walking the Process List
The ActiveProcessLinks field is worth dwelling on because it defines how “the list of running processes” physically exists. Each EPROCESS embeds a LIST_ENTRY whose Flink and Blink point into the ActiveProcessLinks field of the next and previous processes — not to the top of their structures. To move from a link back to the owning EPROCESS, the kernel subtracts the field’s offset (the CONTAINING_RECORD pattern). Starting from PsActiveProcessHead and following Flink until you return to the head visits every process exactly once.
This is exactly what Get-Process, Task Manager, and the kernel’s own PsGetNextProcess logic ultimately rely on. It is also, as we will see, the single data structure a rootkit tampers with to hide a process.
Inspecting EPROCESS in WinDbg
A kernel debugger turns all of this from abstraction into something you can touch. The following commands are the core workflow for examining processes:
# List every process: PID, EPROCESS address, image name
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS ffffe00abc123080
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ad000 ObjectTable: ffff800012345678 Image: System
PROCESS ffffe00abc456080
SessionId: 1 Cid: 0f2c Peb: 00a3e000 ParentCid: 0e40
DirBase: 3f2a1000 ObjectTable: ffff8000abcdef00 Image: notepad.exe
# Dump the EPROCESS type with field names and offsets
kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x438 UniqueProcessId : Ptr64 Void
+0x440 ActiveProcessLinks : _LIST_ENTRY
+0x4b8 Token : _EX_FAST_REF
+0x570 ObjectTable : Ptr64 _HANDLE_TABLE
+0x550 Peb : Ptr64 _PEB
...
# Overlay the type onto a specific instance
kd> dt nt!_EPROCESS ffffe00abc456080 ImageFileName Token Peb
# Switch the debugger's address-space context to that process
kd> .process /P ffffe00abc456080
# With that context set, dump the process's PEB
kd> !peb
!process 0 0 gives you the roster and each process’s EPROCESS pointer. dt nt!_EPROCESS prints the structure layout — note that the offsets shift between Windows builds, which is why exploit code resolves them dynamically rather than hard-coding them. Applying the type to an instance address lets you read live field values, and .process /P followed by !peb crosses into the user-mode view so you can read the command line and module lists.
Security Relevance
Several EPROCESS fields are directly weaponized, which is why they are worth knowing by name:
- Token stealing. The classic payload for a kernel write primitive is to locate the
Systemprocess (PID 4), read itsTokenpointer, and copy it over theTokenfield of an attacker-controlled process. Because the token is what the security reference monitor checks, the process instantly runs withNT AUTHORITY\SYSTEMrights without any credential ever being touched. - DKOM process hiding. Direct Kernel Object Manipulation rootkits unlink a process from the
ActiveProcessLinkslist — rewriting the neighbors’Flink/Blinkto skip over it. The threads still get scheduled (the scheduler uses different lists), but any tool that enumerates viaPsActiveProcessHeadno longer sees the process. - Protected Process Light (PPL). A process’s protection level — a signer and type recorded in the
EPROCESS— restricts which other processes may open a handle to it with sensitive access. This is the mechanism that guardslsass.exewhen PPL is enabled, and attackers seeking to dump LSASS memory must contend with it, sometimes by loading a signed driver to clear the protection field from the kernel.
In every case the underlying lesson is the same: the EPROCESS is the authoritative record of a process’s identity and privileges, so an attacker who can write to kernel memory can rewrite reality by editing it, and a defender who understands its layout knows exactly where to look for tampering.
Conclusion
The EPROCESS structure is the hub that connects a process’s address space (DirectoryTableBase, VadRoot), its resources (ObjectTable, Peb), its identity (UniqueProcessId, Token), and its scheduling state (the embedded KPROCESS). Learning to read it in WinDbg demystifies both normal process management and the tricks that offensive and defensive tooling play on it. But a process only holds resources — nothing in it runs until a thread does. In the next article we turn to the ETHREAD and KTHREAD structures and the scheduler that brings them to life.
You Might Also Like
This article is part of the Windows Internals series. These related deep-dives cover adjacent parts of the system:



Comments