12.2 Memory Analysis with Volatility 3: pslist, pstree, malfind, netscan & Vadinfo
Key Takeaways
- Volatility 3 redesigns memory forensics architecture by replacing legacy static OS profiles with platform-agnostic symbol tables (Intermediate Symbol Format - ISF) dynamically retrieved from symbol servers.
- Process enumeration through windows.pslist follows the kernel EPROCESS circular doubly-linked list (ActiveProcessLinks), whereas windows.pstree maps parent-child process hierarchies (PPID to PID).
- Adversaries employ Direct Kernel Object Manipulation (DKOM) to unlink malicious processes from ActiveProcessLinks; windows.psscan bypasses this evasion by carving physical memory for unlinked _EPROCESS pool tags.
- Code injection, process hollowing, and reflective DLL loading are detected using windows.malfind (identifying executable, non-file-backed VAD memory pages with PAGE_EXECUTE_READWRITE permissions) and windows.ldrmodules (cross-referencing PEB module lists).
- Network telemetry is reconstructed using windows.netscan, which carves memory for TCP endpoint and UDP listener pool structures, exposing active and terminated C2 network sockets.
12.2 Memory Analysis with Volatility 3: pslist, pstree, malfind, netscan & Vadinfo
Quick Answer: The Volatility Framework is the industry-standard open-source platform for volatile memory forensics. Volatility 3 eliminates legacy Volatility 2 operating system "profiles" by adopting Intermediate Symbol Format (ISF) JSON symbol tables derived from Microsoft Program Database (PDB) debugging symbols. In Volatility 3, running processes are enumerated using
windows.pslist(which traverses theActiveProcessLinksdoubly-linked list in theEPROCESSblock) andwindows.pstree(which displays parent-child process hierarchies). To defeat Direct Kernel Object Manipulation (DKOM) rootkits that unlink processes fromActiveProcessLinks, examiners runwindows.psscanto carve unallocated memory for_EPROCESSpool tags. Injected code and reflective DLLs are uncovered usingwindows.malfind(flagging non-file-backed memory pages withPAGE_EXECUTE_READWRITEpermissions hosting executable code) andwindows.ldrmodules(cross-referencing PEB loader lists), while network connections are carved viawindows.netscan.
Volatility Architecture: Volatility 2 vs. Volatility 3
Analyzing a physical memory dump requires understanding the precise internal structures of the target operating system's kernel. Operating system kernels frequently shift data structure offsets between minor service packs, build numbers, and monthly cumulative updates.
+-------------------------------------------------------------------------+
| VOLATILITY 2 vs. VOLATILITY 3 ARCHITECTURE |
+-------------------------------------------------------------------------+
| Architectural Dimension | Volatility 2 (Legacy) | Volatility 3 (Current) |
|-------------------------|------------------------|------------------------------|
| **Symbol Resolution** | Hardcoded static | Dynamic Intermediate Symbol |
| | profiles (e.g., | Format (ISF) JSON tables; |
| | Win7SP1x64) | automated PDB symbol download|
|-------------------------|------------------------|------------------------------|
| **Operating System** | Tightly bound to OS | Platform-agnostic core |
| **Abstraction** | versions; requires manual| engine; auto-detects Windows, |
| | profile generation | Linux, and macOS kernels |
|-------------------------|------------------------|------------------------------|
| **Codebase Base** | Python 2.7 (Deprecated)| Python 3 (Object-oriented |
| | | modular plugin architecture) |
|-------------------------|------------------------|------------------------------|
| **Execution Syntax** | vol.py -f mem.raw | vol.py -f mem.raw |
| | --profile=Win10x64... | windows.pslist |
+-------------------------------------------------------------------------+
The Intermediate Symbol Format (ISF)
In Volatility 2, if an examiner analyzed a memory dump from a newly patched Windows 10 build that lacked a predefined profile, analysis failed until the examiner manually generated a custom profile using debugging tools.
Volatility 3 resolves this structural limitation through Intermediate Symbol Format (ISF):
- When ingesting a raw memory image, Volatility 3 scans the dump for the Windows kernel image (
ntkrnlmp.exeorntoskrnl.exe). - It extracts the GUID and age of the corresponding Microsoft Program Database (PDB) debugging symbol file embedded within the kernel header.
- Volatility 3 automatically connects to Microsoft's public symbol server, downloads the exact PDB file corresponding to that specific kernel build, and converts it into an ISF JSON symbol table.
- This symbol table provides byte-exact offsets for critical structures (
_EPROCESS,_KPROCESS,_VAD,_PEB,_ETHREAD), ensuring complete analytical accuracy across all operating system updates.
Process Enumeration Internals & DKOM Detection
In the Windows NT kernel, every executing process is represented by an Executive Process Block (EPROCESS), an opaque kernel-mode data structure residing in system pool memory. Understanding how Windows links and tracks these blocks is essential for identifying stealth rootkits.
Kernel ActiveProcessLinks Doubly-Linked Circular List:
+------------------+ +------------------+ +------------------+
| EPROCESS: PID 4 | | EPROCESS: PID 480| | EPROCESS: PID 720|
| (System) | | (smss.exe) | | (csrss.exe) |
| | | | | |
| [Flink] -------->|-----> | [Flink] -------->|-----> | [Flink] -------->|--+ (Loops to)
| [Blink] <--------|<----- | [Blink] <--------|<----- | [Blink] <--------|<-+ (Head)
+------------------+ +------------------+ +------------------+
| (Attacker unlinks pointers via DKOM)
▼
+------------------+
| HIDDEN PROCESS |
| (rootkit.exe) |
| PID 9999 |
| [Flink] points |
| to itself |
+------------------+
1. windows.pslist: Walking the ActiveProcessLinks List
The windows.pslist plugin walks the ActiveProcessLinks member of the _EPROCESS structure. ActiveProcessLinks is a circular doubly-linked list (LIST_ENTRY) containing a Forward Pointer (Flink) pointing to the next process's _EPROCESS structure and a Backward Pointer (Blink) pointing to the previous process.
- Limitations: Because
windows.pslistrelies entirely on pointer traversal, it only sees processes currently linked into the kernel scheduler's active list. If an attacker unlinks an entry,pslistwill never encounter it.
2. windows.pstree: Reconstructing Parent-Child Hierarchies
The windows.pstree plugin enumerates processes and arranges them into an indented tree hierarchy based on each process's Process ID (PID) and Inherited From Unique Process ID (PPID):
- Spotted Anomalies: Standard Windows processes exhibit immutable parent-child relationships. For example,
services.exemust always be the parent ofsvchost.exe, andsmss.exemust spawnwininit.exeandcsrss.exe. Ifpstreereveals an instance ofsvchost.exewhose PPID traces tocmd.exeorpowershell.exe, process masquerading or exploitation has occurred. - PPID Spoofing: Modern malware frequently leverages the
UpdateProcThreadAttributeAPI with thePROC_THREAD_ATTRIBUTE_PARENT_PROCESSflag to spoof its parent process ID in user mode. While user-mode tools are deceived by this attribute, memory analysis cross-referencing process creation timestamps and handle tables reveals the true lineage.
3. windows.psscan: Uncovering Rootkits via Pool Tag Carving
Advanced rootkits utilize Direct Kernel Object Manipulation (DKOM) to unlink a malicious process from ActiveProcessLinks:
- The rootkit driver modifies the
Flinkpointer of the preceding process to point directly to the succeeding process, and adjusts theBlinkpointer accordingly. - The unlinked malicious process continues to execute because the Windows thread scheduler schedules individual Threads (
_KTHREAD), not processes. - The Countermeasure (
windows.psscan): Rather than walking linked lists,windows.psscanperforms pool carving. It scans physical memory pages for the unique 4-byte pool tag signature associated with process allocations (Procin older Windows builds or0x636f7250in modern systems). When it discovers an_EPROCESSblock, it parses the structure regardless of whether it is linked intoActiveProcessLinks.
| Command | Primary Data Source | Detects DKOM Unlinked Processes? | Detects Terminated / Exited Processes? |
|---|---|---|---|
windows.pslist | ActiveProcessLinks doubly-linked list | No (Bypassed by unlinking) | No (Unlinked upon termination) |
windows.pstree | PID and PPID relationships in active list | No (Bypassed by unlinking) | No (Unlinked upon termination) |
windows.psscan | Physical memory pool tag carving (Proc) | Yes (Identifies orphaned blocks) | Yes (Remnants persist in deallocated RAM) |
# Enumerate active processes via doubly-linked list
python3 vol.py -f /evidence/mem.raw windows.pslist
# Display parent-child process hierarchy
python3 vol.py -f /evidence/mem.raw windows.pstree
# Carve memory for unlinked and terminated processes
python3 vol.py -f /evidence/mem.raw windows.psscan
Injected Code & Hidden Module Detection: Malfind & Ldrmodules
Adversaries rarely leave malicious executables running as independent, easily recognizable processes. Instead, they inject malicious shellcode or DLLs into legitimate system processes (e.g., explorer.exe, svchost.exe, lsass.exe) through techniques such as DLL Injection, Process Hollowing (RunPE), and Reflective DLL Injection.
Legitimate Process Virtual Address Space:
+-------------------------------------------------------------+
| 0x00400000 - Legitimate PE Executable Image (PAGE_READONLY) |
+-------------------------------------------------------------+
| 0x7FFF0000 - ntdll.dll / kernel32.dll (PAGE_EXECUTE_READ) |
+-------------------------------------------------------------+
| 0x02AB0000 - INJECTED CODE REGION |
| • Memory Protection: PAGE_EXECUTE_READWRITE |
| • Memory Type: MEM_PRIVATE (Not mapped to disk) |
| • First Bytes: 4D 5A (MZ Header) or Shellcode |
| ===> FLAGGED BY windows.malfind |
+-------------------------------------------------------------+
1. windows.malfind: Identifying Injected Memory Ranges
The windows.malfind plugin is the premier tool for detecting process injection in memory. It scans the Virtual Address Descriptor (VAD) tree of every process, evaluating memory ranges against three specific criteria:
- Memory Protection Mask: The memory page must be marked with executable and writable permissions—predominantly
PAGE_EXECUTE_READWRITE(0x40/ RWX). - Memory Allocation Type: The memory allocation must be
MEM_PRIVATEorMEM_COMMITrather thanMEM_IMAGE(memory mapped directly from a legitimate executable or DLL file on disk). Legitimate code is virtually always mapped from a file on disk. - Content Signature: The memory range contains executable code, characterized by an unmapped MZ (
4D 5A) header, a Portable Executable stub, or common shellcode assembly instructions (e.g.,NOPsleds0x90, stack setup0x55 0x8B 0xEC, or call instructions0xE8).
When malfind flags a suspicious region, it outputs the process name, PID, memory start address, hex dump, and disassembled assembly instructions.
# Scan memory for injected code and dump suspicious regions to disk
python3 vol.py -f /evidence/mem.raw windows.malfind --dump
2. windows.ldrmodules: Detecting Unlinked / Reflective DLLs
In standard Windows execution, whenever a process loads a DLL, the OS loader updates three circular doubly-linked lists inside the Process Environment Block (PEB_LDR_DATA):
InLoadOrderModuleList: Tracks DLLs in the sequence they were loaded into memory.InMemoryOrderModuleList: Tracks DLLs ordered by their virtual memory base addresses.InInitializationOrderModuleList: Tracks DLLs in the order their initialization routines (DllMain) executed.
Reflective DLL Injection bypasses the standard Windows API loader entirely. The malware allocates private memory, maps its own DLL sections, and manually resolves import addresses without calling LoadLibrary. Consequently, the injected DLL exists in the process's Virtual Address Descriptors (VAD) but is completely absent from all three PEB loader lists.
The windows.ldrmodules plugin cross-references the VAD tree with the three PEB loader lists:
# Identify unlinked modules by cross-referencing VAD and PEB lists
python3 vol.py -f /evidence/mem.raw windows.ldrmodules
- If
InLoad,InInit, andInMemareFalse, but the module appears in theMappedPathor VAD with executable permissions, the DLL was injected reflectively or deliberately unlinked to evade detection.
Network Sockets, Process Arguments & Handles
1. windows.netscan: Reconstructing Network Activity
Network sockets reside in volatile memory and provide direct forensic evidence of Command and Control (C2) communication, lateral movement, and data exfiltration.
- The
windows.netscanplugin carves physical memory for network structures (_TCP_ENDPOINT,_TCP_LISTENER,_UDP_ENDPOINT). - It extracts the protocol (TCP/UDP), local IP address and port, foreign/remote IP address and listening port, socket operational state (
ESTABLISHED,LISTENING,TIME_WAIT), owning Process ID (PID), process owner name, and the timestamp when the connection was created. - Forensic Advantage: Because
netscancarves pool tags, it can recover closed or terminated network connections whose memory structures have not yet been overwritten by kernel reallocations.
# Scan memory for active and closed network connections
python3 vol.py -f /evidence/mem.raw windows.netscan
2. windows.cmdline: Extracting Command-Line Execution Arguments
Malware often executes system utilities (Living-off-the-Land Binaries - LotLBs) with complex arguments. The windows.cmdline plugin traverses the _RTL_USER_PROCESS_PARAMETERS structure located within each process's _PEB to extract the full command-line string used during process invocation.
- Reveals encoded PowerShell commands (
powershell.exe -nop -w hidden -enc JAB...), discovery commands (whoami /all,net group "Domain Admins" /domain), and credential dumping parameters (rundll32.exe comsvcs.dll, MiniDump).
# Extract process execution command-line parameters
python3 vol.py -f /evidence/mem.raw windows.cmdline
3. windows.handles: Inspecting Open Kernel Handles
Processes interact with system resources through Handles managed by the kernel. The windows.handles plugin enumerates open handles for a given process:
- Mutants (Mutexes): Malware frequently creates unique named mutexes (e.g.,
Global\\Injector_Mutex_v1) to prevent multiple instances from executing simultaneously. Discovering a known malicious mutex identifies malware families instantly. - Opened Files & Named Pipes: Identifies open handles to sensitive files, encrypted ransom staging archives, or named pipes utilized by C2 frameworks (e.g., named pipes like
msagent_12used by Cobalt Strike). - Registry Keys: Identifies registry persistence keys currently held open by malicious threads.
# Enumerate Mutex (Mutant) handles across all processes
python3 vol.py -f /evidence/mem.raw windows.handles --object-type Mutant
4. windows.vadinfo: Virtual Address Descriptor Analysis
The Windows kernel uses a balanced self-balancing binary search tree (the VAD Tree) to track virtual memory address ranges allocated to each process.
- The
windows.vadinfoplugin traverses the VAD tree of a target process. - It details the start and end virtual addresses, allocation protection flags (
PAGE_NOACCESS,PAGE_READONLY,PAGE_EXECUTE_READWRITE), commit status, private memory flags, and the mapped file path (if the allocation maps an image from disk). - Forensic Utility: Essential for deep reverse engineering of complex rootkits and confirming memory hollowings by inspecting memory protection permissions across the process's allocated address space.
Practical Forensic Case Scenario: Investigating In-Memory Beaconing
The Incident
A defense contractor's security operations center (SOC) detects sporadic, beacon-like HTTPS traffic originating from an internal engineering workstation to a foreign IP address. Disk-based antivirus scans return zero detections.
Volatility 3 Analysis Workflow
- Process Lineage Examination (
windows.pstree): The examiner runswindows.pstreeand notes a suspicious instance ofspoolsv.exe(Print Spooler) with PID 4128. However, instead of being a child ofservices.exe, PID 4128 was spawned bypowershell.exe(PID 3104). - Network Connection Mapping (
windows.netscan): Runningwindows.netscanreveals that PID 4128 (spoolsv.exe) maintains an active, established TCP connection to198.51.100[.]88over port8443. Legitimate print spoolers never establish external internet connections. - Injected Code Detection (
windows.malfind): The examiner targets PID 4128 withwindows.malfind. The plugin immediately flags a memory range at virtual address0x0000018f2a100000withPAGE_EXECUTE_READWRITEprotection. The hex dump reveals an unmappedMZheader (4D 5A 90 00) followed by string references toReflectiveLoader. - Handle Inspection (
windows.handles): Inspecting mutant handles for PID 4128 reveals an active mutex namedGlobal\\MSSE-8492-Enc. Threat intelligence databases correlate this exact mutex string to a customized Cobalt Strike Beacon payload. - Remediation: Armed with the remote C2 IP address and injected payload offsets, the incident response team blocks the C2 communication at the perimeter firewall and purges the persistence mechanisms established via PowerShell.
A forensic analyst analyzes a memory capture from a compromised server. The analyst executes windows.pslist, which returns 42 running processes. Next, the analyst executes windows.psscan, which identifies 45 process structures, including a suspicious executable named kworker_stealth.exe that was completely absent from the pslist output. What technical mechanism explains the discrepancy between the two plugins?
An investigator executes windows.malfind against a memory image acquired from an endpoint suspected of hosting a Cobalt Strike payload. The output displays a memory allocation inside svchost.exe at virtual address 0x0000021b4a000000 with PAGE_EXECUTE_READWRITE permissions, containing an MZ header and an instruction sequence starting with 4D 5A 90 00. Why is this specific finding indicative of malicious code injection?
During a malware investigation, an analyst discovers that a suspected malicious DLL is executing inside explorer.exe. However, when the analyst reviews the Process Environment Block loader structures using windows.ldrmodules, the DLL is completely missing from InLoadOrderModuleList, InMemoryOrderModuleList, and InInitializationOrderModuleList, despite appearing in the process VAD tree with executable rights. What attack technique does this pattern represent?