Processes and the EPROCESS Structure

Windows Internals
Time it takes to read this article 6 minutes.

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 — a LIST_ENTRY that threads every process on the system into one doubly linked list. The list head is the kernel global PsActiveProcessHead.
  • DirectoryTableBase — the physical address of the process’s top-level page directory, the value loaded into CR3 when 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 DirectoryTableBase again 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, and InInitializationOrderModuleList. Walking these lists is how a process (or shellcode) enumerates loaded DLLs without calling an API.
  • ProcessParameters — a RTL_USER_PROCESS_PARAMETERS block 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 the IsDebuggerPresent API.

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 System process (PID 4), read its Token pointer, and copy it over the Token field of an attacker-controlled process. Because the token is what the security reference monitor checks, the process instantly runs with NT AUTHORITY\SYSTEM rights without any credential ever being touched.
  • DKOM process hiding. Direct Kernel Object Manipulation rootkits unlink a process from the ActiveProcessLinks list — rewriting the neighbors’ Flink/Blink to skip over it. The threads still get scheduled (the scheduler uses different lists), but any tool that enumerates via PsActiveProcessHead no 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 guards lsass.exe when 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

Copied title and URL