Lesson 1 of 5 · 25 min read

EPROCESS and Windows Process Structures — What Memory Contains

Memory forensics at the advanced level isn’t about memorizing Volatility plugin names — it’s about understanding what kernel structures look like in memory and why manipulations of those structures produce detectable artifacts.


The Windows Memory Hierarchy

Physical Memory

  ├── Kernel Space (not directly accessible from user mode)
  │   ├── EPROCESS structures (one per process)
  │   │   ├── ActiveProcessLinks (doubly-linked list)
  │   │   ├── UniqueProcessId / InheritedFromUniqueProcessId
  │   │   ├── CreateTime / ExitTime
  │   │   ├── Token (security context)
  │   │   ├── Peb (pointer to user-mode PEB)
  │   │   └── ThreadListHead (ETHREAD structures)
  │   │
  │   ├── ETHREAD structures (one per thread)
  │   └── Object Manager structures (handles, named objects)

  └── User Space (per-process virtual address space)
      ├── PEB (Process Environment Block)
      │   ├── ImageBaseAddress
      │   ├── ProcessParameters (CommandLine, ImagePathName)
      │   └── Ldr → PEB_LDR_DATA
      │       ├── InLoadOrderModuleList
      │       ├── InMemoryOrderModuleList
      │       └── InInitializationOrderModuleList

      └── VAD Tree (Virtual Address Descriptor) — per-region metadata

EPROCESS — Walking the Kernel Structure

# Volatility 3 — examine process structures

# pslist: walks the ActiveProcessLinks doubly-linked list
# Misses DKOM-hidden processes
vol.py -f memory.raw windows.pslist

# psscan: scans physical memory for EPROCESS pool tags
# Finds DKOM-hidden processes (and terminated/exited processes)
vol.py -f memory.raw windows.psscan

# Critical comparison: processes in psscan NOT in pslist = DKOM-hidden
# Script to find discrepancies:
vol.py -f memory.raw windows.psscan > psscan.txt
vol.py -f memory.raw windows.pslist > pslist.txt
# Compare PID columns: any PID in psscan not in pslist = rootkit artifact

# cmdline: reads PEB.ProcessParameters.CommandLine
# Shows full command line (pslist only shows truncated 15-char name)
vol.py -f memory.raw windows.cmdline

# Example: svchost.exe showing -k netsvcs in pslist
# vs. an injected process showing svchost.exe with no -k argument (process hollowing)

DKOM — How Rootkits Hide Processes

Before DKOM:
PsActiveProcessHead → EPROCESS(System) ⟷ EPROCESS(lsass) ⟷ EPROCESS(malware) ⟷ EPROCESS(svchost) →

After DKOM (malware hides itself):
PsActiveProcessHead → EPROCESS(System) ⟷ EPROCESS(lsass) ⟷ EPROCESS(svchost) →

                                                    (malware's Blink/Flink modified to skip it)

EPROCESS(malware) still exists in memory — threads still run, handles still valid.
pslist walks Flink and never reaches malware's EPROCESS.
psscan scans ALL memory for '\x50\x72\x6f\x63' (pool tag 'Proc') — finds malware's EPROCESS.
# Volatility: check process privileges (token manipulation detection)
vol.py -f memory.raw windows.privileges --pid [PID]

# Look for:
# SeDebugPrivilege PRESENT and ENABLED on a non-admin process
# SeTcbPrivilege (act as part of OS) — extreme red flag on non-SYSTEM process
# SeImpersonatePrivilege on unexpected processes

# Check token details
vol.py -f memory.raw windows.tokens

VAD Tree — Virtual Address Descriptors

# VAD tree: kernel structure tracking all virtual memory regions per process
# Useful for finding injected memory that is not backed by a file on disk

vol.py -f memory.raw windows.vadinfo --pid [PID]
# Columns: Base, End, Tag, Protection, CommitCharge, PrivateMemory, Mapped file

# Key indicators:
# VadS (Short VAD) with PAGE_EXECUTE_READWRITE protection
#   → Private (heap-like) memory that is executable AND writable = injection staging area
# 
# FILE_MAP with MZ header content
#   → DLL loaded from disk (normal)
#
# Private memory with MZ header + no file mapping
#   → Reflectively loaded DLL (never touched disk)

# Filter for executable private memory (injection IOC):
vol.py -f memory.raw windows.vadinfo --pid [PID] | \
  grep -E "PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_READ" | grep "Private"

Pool Tag Scanning — How psscan Works

Windows kernel allocates memory for kernel objects from tagged pool regions.
EPROCESS objects are allocated with pool tag '\x50\x72\x6f\x63' = "Proc".
ETHREAD objects use tag '\x54\x68\x72\x64' = "Thrd".

psscan algorithm:
1. Scan all pages of physical memory for the byte sequence that starts an EPROCESS pool allocation.
2. At each hit, attempt to validate the EPROCESS structure (size check, sanity checks on key fields).
3. Return all valid EPROCESS structures found — regardless of whether they are in the linked list.

This approach:
✓ Finds DKOM-hidden processes (not in linked list but still in memory)
✓ Finds recently terminated processes (EPROCESS freed but pool memory not yet reused)
✗ May return false positives from unrelated data that happens to match the pattern
✗ PPID for terminated processes may be incorrect (reused PID space)