A prior examination of process memory structures established the foundational framework for advanced memory-forensic analysis. This post now presents the detection of a suspicious program that conceals itself within the process address space—by unlinking dynamic-link libraries (DLLs) from the Process Environment Block (PEB).
In Windows memory forensics, each _EPROCESS kernel object maintains a pointer (via its Peb member) to the associated user-mode Process Environment Block (_PEB). Where present (typically in user-mode processes), the _PEB furnishes critical process artifacts, including:
- the full image path of the process executable (ImagePathName),
- the complete command-line string used at process creation (CommandLine),
- the current working directory,
- pointers to the process heaps,
- the standard handles (stdin/stdout/stderr), and
- three doubly linked lists (InLoadOrderModuleList, InMemoryOrderModuleList, and InInitializationOrderModuleList) that enumerate the full paths of DLLs mapped into the process address space.
These lists reside within the _PEB_LDR_DATA structure referenced by the PEB. Analysts should note that the PEB and its subordinate structures are version-dependent, reside in user-mode memory (and are therefore susceptible to direct manipulation or unlinking by malware), and are absent or incomplete in certain system/kernel-only processes. The primary structure itself is designated _PEB.
In Windows memory forensics, the following _PEB members constitute primary investigative artifacts and must be interpreted with awareness of their susceptibility to manipulation:
- BeingDebugged: Indicates whether a debugger is currently attached to the process. Historically, certain malware families have invoked DebugActiveProcess against themselves to monopolize the single allowed debugger attachment, thereby implementing an anti-debugging measure. Consequently, a non-zero value in the absence of any legitimate active debugger constitutes a notable forensic indicator of potential anti-analysis activity.
- ImageBaseAddress: Records the base virtual address at which the process’s primary executable image (.exe) is mapped within its address space. Memory-forensic tools (e.g., Volatility’s procdump plugin) consult this field prior to carving the executable from a memory sample in order to locate the correct starting offset.
- Ldr: Holds a pointer to the associated _PEB_LDR_DATA structure, which enumerates the DLLs loaded into the process and supplies the three doubly linked module lists used for module-order traversal.
- ProcessParameters: References an _RTL_USER_PROCESS_PARAMETERS structure that stores critical creation-time metadata, including the full image path, command-line string, and current working directory.
- ProcessHeap: Identifies the primary process heap, which is automatically allocated during process initialization.
- NumberOfHeaps: Records the total number of heaps currently associated with the process. Although a process begins with a single heap, additional heaps may be created via HeapCreate.
- ProcessHeaps: An array of pointers to all process heaps; the first entry invariably coincides with the address stored in ProcessHeap, reflecting its status as the primary heap.
Analysts should note that these fields reside in user-mode memory, are subject to direct alteration or unlinking by malware, and exhibit layout variations across Windows versions.
The _PEB_LDR_DATA structure—referenced by the Ldr member of the process’s _PEB—contains pointers to three doubly linked lists that enumerate the DLLs mapped into the process address space. Collectively, these lists furnish the primary view of loaded modules recoverable from user-mode memory. The three doubly linked lists maintained by _PEB_LDR_DATA (listed below) each consist of elements of type _LDR_DATA_TABLE_ENTRY. The term “module” denotes any executable image mapped into the process address space, encompassing both the primary process executable and all loaded DLLs.
- InLoadOrderModuleList – Orders modules according to the chronological sequence in which they were loaded. Because the process executable is invariably the first image mapped into the address space, its _LDR_DATA_TABLE_ENTRY occupies the head of this list.
- InMemoryOrderModuleList – Orders modules according to their relative positions within the process’s virtual address space. Owing to Address Space Layout Randomization (ASLR) and other allocation dynamics, a module loaded later may reside at a lower base address than one loaded earlier.
- InInitializationOrderModuleList – Orders modules according to the sequence in which their DllMain entry points were invoked. This ordering diverges from load order because a module’s DllMain is not necessarily executed immediately upon loading—and in some cases is never executed—for example, when a DLL is loaded solely as a data file or image resource (controlled via the dwFlags parameter of LoadLibraryEx).
These lists constitute primary artifacts for reconstructing a process’s module set. Inconsistencies among them (most commonly the absence of a module from one or more lists) remain a classic indicator of DLL unlinking techniques employed by malware. Analysts must also account for the fact that the structures reside in user-mode memory and are therefore subject to direct manipulation, with layouts that vary across Windows versions. The interrelationships among the _PEB, _PEB_LDR_DATA, and the three module lists are illustrated in the diagram above.
Memory-forensic frameworks (for example, Volatility’s ldrmodules plugin) systematically traverse all three lists to reconstruct the set of loaded DLLs. Discrepancies among the lists—most commonly the absence of a module from one or more of them—represent a classic indicator of DLL unlinking, a technique frequently employed by malware to conceal injected or malicious libraries.
The _LDR_DATA_TABLE_ENTRY structure represents the fundamental per-module metadata record maintained by the loader. Each instance describes a single executable image (the process executable or a loaded DLL) mapped into a process’s address space and is linked into one or more of the three doubly linked lists anchored by _PEB_LDR_DATA.
The following members of _LDR_DATA_TABLE_ENTRY constitute the core investigative artifacts used to identify, locate, and extract modules from process address space:
InLoadOrderLinks/InMemoryOrderLinks/InInitializationOrderLinks: Forward and backward pointers that embed the entry in the three module lists. Unlinking malware typically severs one or more of these links so that standard enumeration (e.g., Volatility’s dlllist) no longer reports the module, while the image itself remains mapped.
- DllBase: Records the virtual base address at which the module is mapped within the process. Memory-forensic tools (e.g., Volatility’s dlldump and related carving plugins) consult this value to determine the starting offset from which the PE image is extracted.
- EntryPoint: Holds the address of the first instruction executed by the module. In the majority of cases, this value is taken directly from the PE header’s AddressOfEntryPoint field; deviations may indicate entry-point redirection or other post-load modifications.
- SizeOfImage: Specifies the size, in bytes, of the module’s mapped image. Combined with DllBase, it defines the address range occupied by the module and supports validation against corresponding VAD nodes.
- FullDllName: Contains the complete path to the module’s on-disk file (for example, C:\Windows\System32\kernel32.dll). Because this Unicode string resides in user-mode memory, it is susceptible to overwriting or nullification by malware seeking to obscure its origin.
- BaseDllName: Stores only the base filename portion of the module (for example, kernel32.dll). Like FullDllName, this field can be manipulated and should be cross-checked against independent sources such as VAD filenames or file-system artifacts.
- LoadCount (also appearing as ObsoleteLoadCount or ReferenceCount on later Windows versions): Maintains a reference count reflecting the number of times LoadLibrary (or an equivalent loader call) has been issued for the module. Analysts examine this value to distinguish modules loaded via the Import Address Table (IAT) from those loaded through explicit LoadLibrary calls and to assess whether a module remains safely unloadable.
These fields reside in pageable user-mode memory and are therefore subject both to paging and to direct alteration. Layout and naming of certain members (particularly the reference-count fields) vary across Windows versions; forensic frameworks rely on version-specific profiles to interpret them correctly. Cross-validation against the VAD tree remains essential when assessing the integrity of any _LDR_DATA_TABLE_ENTRY.
In a live kernel-debugging session, the structures described above can be examined directly to observe their in-memory layout and interrelationships. The _PEB (and the memory it points to) resides in user-mode address space. By default, kd operates in the System/kernel context and cannot resolve or read arbitrary user-mode pages belonging to another process. To fix this, switch the debugger into the target process’s context (use the _EPROCESS address of the process you are examining):
.process /p /r ffffe009259d60c0
The /p flag translates user-mode addresses using the process’s page tables; /r reloads user-mode symbols.
Begin by enumerating the active process list to identify a suitable target process for deeper analysis. Once the _EPROCESS object for the selected process has been located, inspect that structure to recover the pointer to its associated Process Environment Block. The _EPROCESS object contains a dedicated Peb member that references the user-mode _PEB; extracting this pointer provides the entry point for subsequent examination of the PEB, its loader data (_PEB_LDR_DATA), and the individual module entries (_LDR_DATA_TABLE_ENTRY).
![]() |
| Get the Pointer to the _PEB |
With the _PEB located at 0x000000ce8ec05000, the next step is to inspect this structure to recover the pointer to its associated _PEB_LDR_DATA`. The _PEB maintains this reference in its Ldr member. The _PEB_LDR_DATA structure, in turn, anchors the three doubly linked lists (InLoadOrderModuleList, InMemoryOrderModuleList, and InInitializationOrderModuleList) that enumerate the modules loaded into the process address space.
![]() |
| Get the Pointer to the ldr (_PEB_LDR_DATA) |
Next, examine the _PEB_LDR_DATA structure.
It contains the heads of the three doubly linked lists previously described. Each list enumerates the modules mapped into the process address space by linking together _LDR_DATA_TABLE_ENTRY records. To begin walking one of these lists, follow the Flink pointer of InLoadOrderModuleList to the first _LDR_DATA_TABLE_ENTRY and identify the corresponding module.
![]() |
| Enumerate the first entry of the InLoadOrder module list |
The first entry in the list resolves to DumpIt.exe. Because the examination is focused on the InLoadOrderModuleList, modules appear in the chronological order in which they were mapped into the process address space. The primary process executable is invariably the first image loaded and therefore occupies the head of this list in the overwhelming majority of cases. The complete set of loaded modules can be enumerated by successively following the Flink (and, if desired, Blink) pointers of the doubly linked list.
In this post, we have examined the core Windows process memory structures that govern how modules are tracked inside a running process: the _EPROCESS object, its associated _PEB, the _PEB_LDR_DATA that anchors the three module lists, and the individual _LDR_DATA_TABLE_ENTRY records that describe each loaded image. These structures form the primary view of loaded executables and DLLs available to both the operating system and to memory-forensic tools. Because they reside in user-mode address space, they are also directly accessible to malware. Techniques such as DLL unlinking exploit exactly this accessibility by removing entries from one or more of the three doubly linked lists, thereby concealing malicious modules from simple enumeration.
By systematically walking the InLoadOrderModuleList, InMemoryOrderModuleList, and InInitializationOrderModuleList, and by cross-referencing the results against other sources (most notably the VAD tree), analysts can identify inconsistencies that strongly indicate concealment. The same structures also supply the critical metadata—base addresses, image sizes, and path names—required to carve modules from memory for further static or dynamic analysis.
Mastery of these data structures is therefore foundational for effective Windows memory forensics. Whether performed live with a kernel debugger or offline with frameworks such as Volatility, the ability to navigate from _EPROCESS through the PEB and its loader lists remains one of the most reliable methods for uncovering hidden code in process memory.










Post a Comment