How do memory-forensic frameworks establish analytic context within an unstructured dump? The principal nexus is the Kernel Debugger Data Block (KDBG, formally _KDDEBUGGER_DATA64). This structure furnishes the critical pointers that enable tools to locate the system’s active-process list and other kernel globals. On contemporary platforms—particularly 64-bit Windows Vista and later—the KDBG is frequently encoded; successful recovery therefore requires an additional decryption stage that leverages system entropy sources. Location of the KDBG proceeds via two principal avenues. The first follows a pointer chain originating in the Kernel Processor Control Region (KPCR). On Windows XP and cognate releases, the boot-processor KPCR resided at a fixed virtual address (classically 0xFFDFF000 on x86), permitting direct dereference. Beginning with Vista, however, the KPCR became subject to dynamic allocation and address-space layout randomization, rendering fixed-offset techniques obsolete and obliging tools to employ signature-based or relational searches. Alternatively, the KDBG itself may be recovered by scanning for its characteristic owner tag and structural invariants. The pronounced version-, architecture-, and service-pack specificity of many analysis suites stems directly from the continual evolution of these layouts; tool authors must continually adapt to remain current. Because recovery of the KPCR or KDBG is computationally intensive, analysts must exercise patience during the initial processing phase.
Once the KDBG (or an equivalent symbol-derived locus) is resolved, it yields the PsActiveProcessHead pointer, the head of the doubly-linked list of executive process (_EPROCESS) blocks that enumerates processes the kernel regards as active. Analogous to the centrality of the file within a file-system image, virtually all subsequent analysis orbits the process object. Each _EPROCESS references a Process Environment Block (PEB) that encapsulates a rich constellation of process metadata—full image path, command-line arguments, and the loader data table enumerating loaded modules (DLLs). Concurrently, the _EPROCESS anchors a Virtual Address Descriptor (VAD) tree that catalogues every virtual-address range allocated to the process. The VAD tree is of particular forensic utility: by cross-correlating its contents against the module lists and other process structures, an examiner can detect discrepancies indicative of code injection or other memory-resident tradecraft. Kernel modules—most commonly device drivers—extend system functionality and are likewise located via lists reachable from the KDBG or related structures; because malware frequently leverages such modules to achieve persistence or privilege, their enumeration is essential.
Having reconstructed the principal object graphs, advanced frameworks proceed to identify outliers. Windows memory structures, though intricate, are readily susceptible to Direct Kernel Object Manipulation (DKOM). Unlinking a process or network socket from its native list, for example, renders the object invisible to conventional list-walking utilities while it continues to execute. Consequently, mature suites eschew exclusive reliance on linked-list traversal and instead perform exhaustive pool-tag and structure-signature scans to recover concealed objects. An analogous methodology applies to the detection of hooks—whether inline, import-address-table, or system-service-descriptor-table modifications. Legitimate software (device drivers, security products) also installs hooks; thus, forensic tools surface candidates for human adjudication rather than rendering definitive verdicts.
The terminal phase of the analytic workflow is the examiner’s domain. The frameworks surface the reconstructed process lists, module inventories, VAD trees, network artifacts, and candidate anomalies. It remains the analyst’s responsibility to discriminate benign from malicious activity. Although the volume of data may appear formidable, contemporary tooling substantially mitigates the cognitive burden, thereby facilitating rigorous, evidence-based interpretation.
The figure above presents a simplified schematic of the principal kernel memory structures under discussion. Central to any coherent reconstruction of a Windows memory image is the executive process block (_EPROCESS). The Kernel Debugger Data Block (KDBG) furnishes, via the PsActiveProcessHead pointer, the head of the doubly-linked list of active _EPROCESS structures. Each _EPROCESS in turn maintains critical cross-references that collectively define the process’s runtime footprint: a pointer to the Process Environment Block (PEB), which enumerates the image and its loaded modules; a reference to the process’s handle table (ObjectTable), which catalogues the kernel objects currently in use; an access-token pointer that encodes the process’s security context and privileges; a Virtual Address Descriptor (VAD) tree that maps every virtual-address range allocated to the process; and linkage into the thread list that identifies the threads scheduled to execute code within that address space.
While the diagram abstracts away version-specific layout variations, address-space layout randomization, and the occasional encoding of the KDBG itself on post-Vista platforms, the fundamental centrality of the _EPROCESS as the nexus connecting these objects remains invariant and constitutes the primary analytic fulcrum for memory-forensic examination.
The _EPROCESS Structure
The _EPROCESS structure constitutes a cornerstone of Windows memory forensics. Allocated from the non-paged kernel pool, it encapsulates the essential state of every process and exposes well over one hundred members, many of which are pointers to ancillary structures that collectively furnish a comprehensive view of process identity, resource consumption, and execution context.
Among its most forensically significant fields are the DirectoryTableBase (DTB) contained within the embedded _KPROCESS, the pointer to the Process Environment Block (PEB), and affinity data that constrain processor scheduling. The DTB supplies the page-directory base required for virtual-to-physical address translation—an indispensable capability during offline memory analysis. In addition, _EPROCESS yields the process identifier (PID), the parent-process identifier (via InheritedFromUniqueProcessId), and the creation and exit timestamps, thereby enabling investigators to reconstruct precise activity timelines. Although the PEB itself resides in the process’s user-mode address space, it is referenced by a pointer within _EPROCESS and ranks among the richest sources of process-contextual evidence. Its Ldr member points to a _PEB_LDR_DATA structure that maintains the ordered lists of loaded modules, while its ProcessParameters member references an _RTL_USER_PROCESS_PARAMETERS structure containing the full command-line arguments, current directory, and environment block. Discrepancies between these module lists and the VAD tree, or anomalous command-line content, frequently indicate code injection, process hollowing, or related tradecraft. Complementing this information, the ObjectTable field of _EPROCESS points to a _HANDLE_TABLE that enumerates the kernel objects (files, registry keys, sockets, etc.) currently held by the process.
The DTB value is located within the _KPROCESS (itself embedded at the base of _EPROCESS); while an offset of 0x28 has been observed on many recent builds, this location remains version- and architecture-dependent and must be confirmed against the appropriate symbols or profile. The PEB further supplies an ImageBaseAddress that designates the base of the primary executable image. By examining the Portable Executable (PE) section table at that base address, an analyst can identify the virtual addresses of the remaining sections and, where the corresponding pages remain resident, reconstruct a substantial portion of the original binary.
Taken together, systematic examination of the _EPROCESS and its linked structures not only illuminates the behaviour of active processes but also surfaces potential indicators of compromise and residual attack artifacts within the memory image.
Windows maintains a unique executive process block (_EPROCESS) for every active process; these structures are allocated from the non-paged pool of kernel memory, ensuring their continuous residency and accessibility irrespective of the paging state of the process’s user-mode address space. While the _EPROCESS itself resides in the non-paged pool and is managed through the Object Manager, certain associated structures or extended fields may involve paged memory, and modern Windows versions impose additional protections and layout variations that forensic tools must accommodate. The type definition and constituent member layout of the _EPROCESS structure are enumerated as follows.
![]() |
| Figure 2: _EPROCESS Structure |
The following members of the _EPROCESS structure constitute the principal forensic anchors for process reconstruction:
- Pcb: The embedded kernel process control block (_KPROCESS), located at the base of _EPROCESS. It houses critical operational data, most notably the DirectoryTableBase (the page-directory base required for virtual-to-physical address translation) and cumulative execution-time accounting for both kernel-mode and user-mode intervals.
- CreateTime: A 64-bit UTC timestamp recording the moment of process instantiation.
- ExitTime: A 64-bit UTC timestamp recording process termination. A null value indicates that the process remains active; a non-zero value supplies temporal context for exit analysis.
- UniqueProcessId: The process identifier (PID)—a unique integer assigned by the kernel that serves as the primary handle for process reference across the system.
- ActiveProcessLinks: The doubly-linked list entry that concatenates all processes currently regarded as active by the kernel. The majority of user-mode and kernel APIs that enumerate processes traverse this list; consequently, it is a frequent target of Direct Kernel Object Manipulation (DKOM) techniques intended to conceal processes from conventional enumeration.
- SessionProcessLinks: A secondary doubly-linked list that associates processes belonging to the same terminal or logon session, facilitating session-scoped analysis.
- InheritedFromUniqueProcessId: The PID of the parent process at the time of creation. This value is immutable after process instantiation, even if the parent subsequently terminates, thereby preserving lineage information for investigative reconstruction.
- Session: A pointer to the _MM_SESSION_SPACE structure, which encapsulates session-specific state including logon-session metadata and graphical-user-interface object information.
- ImageFileName: An 16-byte ASCII array containing the initial characters of the process executable’s filename. Longer names appear truncated; recovery of the complete Unicode path requires examination of the corresponding VAD node or the ImagePathName / CommandLine members of the associated Process Environment Block (PEB).
- ThreadListHead: The head of a doubly-linked list of _ETHREAD structures representing every thread that has executed within the process context.
- ActiveThreads: An integer tally of currently active threads. A zero count constitutes a strong indicator that the process has terminated, even if residual structures remain resident.
- Peb: A kernel-mode pointer that references the Process Environment Block residing in the process’s user-mode address space. The PEB supplies pointers to the loader data tables (DLL lists), current working directory, command-line arguments, environment variables, process heaps, and standard handles—data that is frequently paged and therefore may be incomplete in memory dumps.
- VadRoot: The root of the Virtual Address Descriptor (VAD) tree. Each node details a contiguous virtual-address range allocated to the process, including original protection attributes (read, write, execute) and whether the region is backed by a mapped file—information essential for detecting code injection, hollowing, or anomalous memory mappings.
Collectively, these fields furnish the examiner with a multi-dimensional view of process identity, lineage, temporal context, memory layout, and security posture, while remaining subject to version-specific layout variations, potential DKOM interference, and the partial availability of user-mode structures.
The layout and internal organization of the _EPROCESS structure are subject to frequent modifications across Windows versions, service packs, and architecture variants due to kernel evolution. Consequently, memory-forensic frameworks must be continuously updated—through revised profiles, symbol information, or structural definitions—to ensure reliable identification and correct interpretation of _EPROCESS instances across the full range of supported builds. Such adaptability is indispensable for preserving the integrity of the investigative process and the evidentiary reliability of artifacts recovered from memory images.
![]() |
| Figure 3a: Screenshot of EPROCESS from a Windows 10 memory dump (OS build 17134). |
![]() |
| Figure 3b: Screenshot of EPROCESS from a Windows 10 memory dump (OS build 19041) |
The variability of _EPROCESS structural signatures across operating-system versions constitutes a persistent challenge in memory forensics. Introduction of a new Windows build necessitates fresh determination of the corresponding signature through systematic analysis of representative memory images. Even incremental updates applied to an existing installation may alter internal layout, thereby invalidating previously established signatures. In certain instances, the candidate signature proves non-unique, compelling the examiner to corroborate identification through secondary structural invariants or additional fields within the _EPROCESS. A further complication arises from heterogeneity in dump formats and their associated header offsets: differing acquisition methods or the presence of supplementary metadata can shift absolute offsets and produce divergent parsing results, underscoring the necessity of robust, profile-driven or symbol-assisted identification techniques.
Embedded within each _EPROCESS is a _LIST_ENTRY structure called ActiveProcessLinks. This structure comprises two pointer fields: Flink (forward link), which references the ActiveProcessLinks of the subsequent _EPROCESS, and Blink (backward link), which references the corresponding field of the preceding _EPROCESS. The resulting topology constitutes a circular doubly-linked list of active process objects, whose head is conventionally identified by the kernel global PsActiveProcessHead.
Because the links address the _LIST_ENTRY offset rather than the structure's base, recovery of the enclosing object requires offset subtraction via the CONTAINING_RECORD macro. This list underpins nearly all conventional process enumeration—NtQuerySystemInformation (SystemProcessInformation), tasklist, and Task Manager all traverse it—making it the principal target of Direct Kernel Object Manipulation (DKOM): unlinking an _EPROCESS entry evades list-walking enumerators while leaving the object schedulable, since the scheduler operates on KTHREAD/KPROCESS dispatcher linkage and PspCidTable, not ActiveProcessLinks. This asymmetry is precisely why robust detection pivots to cross-view techniques—pool tag scanning for Proc allocations, handle-table walks, or thread-based enumeration via ThreadListEntry—that don't depend on the compromised list at all".
![]() |
| Figure 4: _EPROCESS doubly linked list |
Traversal of the ActiveProcessLinks doubly-linked list remains a foundational technique in memory forensics for the enumeration of processes the kernel currently regards as live. This list-walking approach constitutes the primary mechanism employed by Volatility’s pslist plugin (and by functionally equivalent plugins in other frameworks) to reconstruct the active-process inventory from a memory image. Because the technique relies exclusively on the integrity of the linked-list pointers, it is susceptible to Direct Kernel Object Manipulation (DKOM) attacks that unlink a process while leaving the underlying _EPROCESS intact and schedulable; complementary pool-scanning methods (e.g., Volatility’s psscan) are therefore routinely employed to detect such concealment. To inspect the underlying _LIST_ENTRY structure that implements this linkage, the following command may be issued within the kernel debugger:
![]() |
| Figure 5: _LIST_ENTRY Structure |
Initiation of active-process list traversal requires first locating the list head. This is achieved by resolving the global kernel symbol nt!PsActiveProcessHead, a static kernel variable—not itself an _EPROCESS object—that functions as the fixed anchor of the circular _LIST_ENTRY chain linking every _EPROCESS via its ActiveProcessLinks member. Because it is never subject to unlinking, the anchor itself remains reliable even when individual list nodes have been manipulated.
Examination of the debugger output above establishes that, in the present memory image, PsActiveProcessHead resides at the virtual address 0xfffff80001979e20. (It must be emphasized that this address is instance-specific and will vary across operating-system versions, architectures, and boots).
With the list head thus resolved, the Flink and Blink members of the embedded _LIST_ENTRY may be inspected to expose the circular chain of active _EPROCESS structures. Because these pointers reference the ActiveProcessLinks offset rather than the base of each enclosing _EPROCESS, recovery of the full process objects requires the conventional containing-record offset arithmetic before subsequent fields can be meaningfully examined. Consequently, the base address of the enclosing _EPROCESS is recovered by subtracting the relative offset of the ActiveProcessLinks member (illustrated here as 0x0e8 as given in Figure 2) from the pointer value obtained via Flink or Blink. It must be stressed that this offset is version-, architecture-, and service-pack-dependent; the value 0x0e8 is representative of certain 64-bit Windows builds only and must be confirmed against the appropriate symbols or profile for the target image before reliable structure recovery can proceed.
From the foregoing examination, it is immediately apparent that the initial process in the active list is System. The ActiveProcessLinks.Flink of this _EPROCESS further discloses that the subsequent process object resides at the virtual address 0xfffffa800234a3d8` (an address specific to the present memory image). By iteratively applying the same procedure—feeding the newly obtained Flink value into the containing-record offset calculation and inspecting the resulting ImageFileName—the examiner may continue walking the circular chain until the list head is once again reached, thereby enumerating every process the kernel currently regards as active.
At this juncture, the list-traversal facilities of the kernel debugger are employed to walk the entire active-process chain. The starting address supplied to the traversal command is obtained by subtracting the relative offset of the ActiveProcessLinks member from the relevant Flink (or Blink) pointer, thereby recovering the base of the first _EPROCESS structure.
dt nt!_EPROCESS <System_EPROCESS_address> -l ActiveProcessLinks.Flink -y ImageFileName -y UniqueProcessId
Once this correctly adjusted address is provided, the debugger can systematically follow the circular doubly-linked list, applying the containing-record arithmetic at each step and exposing successive process objects until the list head is once again reached.
With the advent of Windows 10, Microsoft adopted a markedly accelerated release cadence—typically two major feature updates per year. This tempo exerts continual pressure on memory-forensic tooling, because successive builds virtually always introduce modifications to in-memory data structures. The most reliable means of tracking these revisions is the Microsoft build number, which can be obtained on a live system via the “About your PC” dialog (or equivalent programmatic interfaces). Recording the precise build number at the time of acquisition is therefore an essential item of case documentation.
Volatility 3 largely mitigates the resulting profile-management burden through dynamic symbol-table resolution. A symbol table, ordinarily derived from Microsoft debugging information (PDB files), supplies precise structural layouts and offsets for critical kernel objects. Volatility 3 retrieves or caches these tables on demand, thereby eliminating the rigid, version-specific profiles required by Volatility 2. The principal trade-off is that Volatility 3 normally requires network access to the Microsoft symbol server; offline environments must pre-stage the requisite symbol packs, which the Volatility project publishes in substantial archives.
In the Volatility 2 workflow, kdbgscan is customarily the first plugin executed against an unfamiliar image. It enumerates candidate _KDDEBUGGER_DATA64 (KDBG) structures by signature, subjects each candidate to a battery of sanity checks, and proposes matching profiles. Beginning with 64-bit Windows 8, the KDBG itself is encrypted; the plugin must therefore also locate the KdCopyDataBlock routine in order to recover the decryption key. Because of the exhaustive scanning and validation steps, kdbgscan ranks among the slower plugins. When time is constrained, an analyst may instead test a short-list of plausible profiles—guided by acquisition date, registry artifacts, or prior knowledge—until coherent output is obtained from core plugins such as pslist, psscan, filescan, and hivelist. Successful recovery of a sensible process list, file objects, and registry hives constitutes strong empirical confirmation of a correct profile. The same validation logic applies, mutatis mutandis, to the symbol tables employed by Volatility 3; the equivalent plugins (windows.pslist.PsList, etc.) and the diagnostic plugin windows.info.Info serve analogous purposes. Output from kdbgscan can initially appear voluminous. Useful evaluative criteria include:
- presence of a non-empty PsActiveProcessHead list,
- presence of a populated PsLoadedModuleList,
- a KernelBase annotated “Matches MZ: True”,
- a coherent build string,
- congruence between the suggested profile and the reported build string,
- recovery of one or more valid KPCR addresses.
Multiple legitimate KDBG structures may coexist within a single image, and a given structure may map to more than one profile. By presenting every candidate, kdbgscan enables the examiner to select the most consistent match and, if necessary, to fall back to alternative candidates when subsequent plugins produce anomalous results.
Additional structures and debugger extensions of comparable forensic significance—including the Process Environment Block (_PEB), the loader data table (_PEB_LDR_DATA), individual module entries (_LDR_DATA_TABLE_ENTRY), and the Virtual Address Descriptor (VAD) tree—likewise warrant detailed examination. These constructs will be treated in subsequent articles.












Post a Comment