< [ ๐Ÿ“‘ Return to Research Knowledge Base ]

Windows Memory Forensics: Volatility 3 Analysis, Kernel Pool Invariants & Process Hollowing Detection

โฑ๏ธ 6 min read

1. Volatile Memory in Incident Response & Threat Hunting

Modern advanced threat groups and sophisticated ransomware cartels actively employ fileless execution, living-off-the-land techniques, and in-memory payload staging. These techniques leave zero persistent artifacts on physical disk storage. When responding to advanced intrusions, volatile memory (RAM) is the ultimate source of truth, containing decrypted command strings, active network sockets, injected shellcode, unhooked API pointers, and transient cryptographic keys.

Memory analysis requires understanding Windows kernel architectures, executive objects, and the forensic tools necessary to reconstruct operational state from raw physical address dumps.

2. Windows Kernel Internals: Executive Process Blocks & VAD Trees

Every running user-mode process in Windows is represented in kernel space by an EPROCESS (Executive Process) structure. Understanding two core kernel primitives is mandatory for memory forensics:

The EPROCESS structure contains an ActiveProcessLinks field of type LIST_ENTRY. This circular doubly-linked list connects all running processes. Standard Windows administrative utilities (Task Manager, tasklist.exe) enumerate processes by traversing this list.

Adversaries utilize Direct Kernel Object Manipulation (DKOM) to unhook a malicious process’s ActiveProcessLinks (pointing the previous node’s forward pointer to the next node). This completely hides the process from Task Manager and API-based EDR hooks. However, the thread structures (ETHREAD) and memory manager allocations remain resident in memory, allowing pool-tag scanners to discover the hidden process.

B. The Virtual Address Descriptor (VAD) Tree

The Windows Virtual Memory Manager maintains a self-balancing binary search tree (AVL tree) called the VAD tree for every process. The VAD tree tracks all allocated virtual memory regions, memory protections (PAGE_READWRITE, PAGE_EXECUTE_READWRITE), and whether a region is memory-mapped to a file on disk or committed in private memory.

+-------------------------------------------------------------+
|                 Windows Process Memory Layout               |
|                                                             |
|   Virtual Memory Space                                      |
|   +-----------------------+ <--- High Memory (0x7FFFFFFF)   |
|   | Loaded DLLs (ntdll)   | [Mapped File: PAGE_EXECUTE_READ]|
|   +-----------------------+                                 |
|   |                       |                                 |
|   | Injected Shellcode    | [Private: PAGE_EXECUTE_READWRITE| <-- [ANOMALY!]
|   | (Malfind Hit)         |  Unmapped, RWX Permissions]     |
|   +-----------------------+                                 |
|   | PE Header / Main Code | [Mapped File: svchost.exe]      |
|   +-----------------------+ <--- Low Memory (0x00010000)    |
+-------------------------------------------------------------+

3. Deep Dive: Process Hollowing & In-Memory Injection

Process Hollowing (T1055.012) is a stealth evasion technique where an attacker starts a legitimate, signed Windows process in a suspended state, unmaps its legitimate code, and replaces it with malicious executable payload:

  1. Process Creation: Spawns a legitimate binary (e.g., svchost.exe or calc.exe) with the CREATE_SUSPENDED flag via CreateProcessW().
  2. Unmapping Legitimate Sections: Invokes NtUnmapViewOfSection() to release the virtual address space containing the legitimate application’s code.
  3. Memory Allocation & Injection: Allocates executable memory in the remote suspended process via VirtualAllocEx() with PAGE_EXECUTE_READWRITE permissions, and copies the malicious PE payload using WriteProcessMemory().
  4. Context Hijacking & Execution: Queries the thread state with GetThreadContext(), modifies the instruction pointer (RIP on x64, EIP on x86) to point to the newly injected entry point, and calls ResumeThread().

4. Investigating In-Memory Intrusions with Volatility 3

Volatility 3 is the industry-standard memory forensics framework. When analyzing a raw memory capture (acquired via LiME, DumpIt, or FTK Imager), investigators execute specialized plugins to detect hidden processes and in-memory injection:

A. Detecting DKOM Process Hiding

# 1. Standard process list (traverses ActiveProcessLinks)
python vol.py -f memory.raw windows.pslist

# 2. Kernel pool tag scanner (scans unallocated pool memory for EPROCESS blocks)
python vol.py -f memory.raw windows.psscan

Comparing the outputs of windows.pslist and windows.psscan instantly reveals hidden processes: any PID appearing in psscan but absent from pslist has been hidden using DKOM.

B. Identifying Injected Shellcode with Malfind

The windows.malfind plugin inspects VAD trees across all processes to detect memory segments that are allocated as private, carry executable permissions (PAGE_EXECUTE_READWRITE), and contain binary headers or shellcode:

python vol.py -f memory.raw windows.malfind --pid 1420

A typical malicious finding displays an unmapped memory segment starting with 4D 5A (the MZ DOS header) or shellcode NOP sleds (90 90 90) inside what appears to be a legitimate host process.

5. Python Memory Triage Automation

The following Python script automates the triage of Volatility 3 JSON output to detect suspicious process parentage and unauthorized executable memory allocations.

# Automated Volatility 3 Triage Parser
import json

def audit_memory_triage(pslist_json_path: str, malfind_json_path: str):
    print("[*] Commencing Automated Memory Triage...")
    
    with open(pslist_json_path, "r") as f:
        pslist = json.load(f)
        
    with open(malfind_json_path, "r") as f:
        malfind = json.load(f)

    # 1. Build Process Map
    processes = {p["PID"]: p for p in pslist}
    
    # 2. Audit PPID Invariants (e.g. svchost must only be spawned by services.exe)
    for pid, proc in processes.items():
        image_name = proc.get("ImageFileName", "").lower()
        ppid = proc.get("PPID", 0)
        parent = processes.get(ppid, {})
        parent_name = parent.get("ImageFileName", "UNKNOWN").lower()

        if image_name == "svchost.exe" and parent_name != "services.exe":
            print(f"[!] ANOMALY: Rogue svchost.exe (PID: {pid}) spawned by invalid parent: {parent_name} (PPID: {ppid})")

        if image_name == "lsass.exe" and parent_name != "wininit.exe":
            print(f"[!] CRITICAL: Rogue lsass.exe (PID: {pid}) spawned by: {parent_name} (PPID: {ppid})")

    # 3. Correlate Malfind Injections
    injected_pids = set()
    for hit in malfind:
        pid = hit.get("PID")
        injected_pids.add(pid)
        print(f"[!] INJECTION DETECTED: PID {pid} ({hit.get('Process')}) at base {hex(hit.get('StartVPN', 0))}")

    print(f"[*] Memory Triage Complete: {len(injected_pids)} processes flagged with injected memory.")

if __name__ == "__main__":
    print("[*] Volatility Memory Automation Engine initialized.")

6. Incident Response & Remediation Playbook

  • Preserve Memory Before Disconnecting or Re-booting: Never reboot an infected endpoint before memory acquisition. Power cycling completely destroys volatile memory evidence and unencrypted ransomware keys.
  • Enforce Driver Blocklists: Prevent kernel DKOM attacks by enforcing Microsoft Vulnerable and Malicious Driver Blocklist (HVCI – Hypervisor-Protected Code Integrity).
  • Implement EDR with Kernel Telemetry: Deploy EDR solutions that monitor ETW-Ti (Event Tracing for Windows – Threat Intelligence) to intercept VirtualAllocEx and WriteProcessMemory calls across process boundaries in real time.

Threat Hunting & Strategic Attribution Playbook

Converting forensic findings into actionable threat intelligence requires rigorous adherence to intelligence analysis standards:

  1. Diamond Model Activity Threading: Correlate the observed adversary capability (malware, exploits) and infrastructure (C2 domains, IP blocks, JARM hashes) against known threat actor profiles in MITRE ATT&CK. Establish analytical confidence using the Admiralty Code.
  2. Passive DNS & Infrastructure Pivoting: Query historical passive DNS (pDNS) repositories to map the attacker’s broader hosting topology. Identify SSL/TLS certificate reuse, common WHOIS registrant attributes, and co-located command servers.
  3. Defensive Engineering via MITRE D3FEND: Map identified adversary TTPs to corresponding D3FEND defensive countermeasures. Deploy canary credentials, harden process execution policies, and implement network micro-segmentation to permanently deny the threat actor operational access.
  4. Automated STIX/TAXII Threat Sharing: Package validated threat intelligence into standardized STIX 2.1 JSON bundles. Automate TAXII feed publication to SIEM, SOAR, and perimeter firewall blocklists for automated threat neutralization.
Shreyansh Sharma
// VERIFIED AUTHOR & SECURITY RESEARCHER

Shreyansh Sharma

Founder of VERASTRA, Co-Founder of PraanSewa, and President of the AI/ML Club (GEC Ajmer). Independent Cybersecurity Researcher, Red Hat Certified (RHCSA #250-102-605), Machine Learning Specialist (Coursera #M5JEZE29JALE), and veteran of 50+ hackathons specializing in adversarial AI defense, zero-day research, and cloud infrastructure hardening.