Volatility 3 Fundamentals
Volatility 3 is the primary open-source memory analysis framework. This chapter covers installation, symbol setup, the core plugin workflow, and the most important plugins for incident response — from process listing to network connections to registry extraction from memory.
You have a 32 GB RAM image from a Windows 2022 server confirmed compromised with a Cobalt Strike beacon. Volatility 3 will let you answer the key questions: What processes are running and which have suspicious parent relationships? Are there any injected code sections in legitimate processes? What network connections are active? What credentials are cached in LSASS? What registry keys are loaded? This chapter walks through the analysis workflow from first command to finished findings.
Installation and Setup
# Volatility 3 installation
pip3 install volatility3
# Or from source for latest:
git clone https://github.com/volatilityfoundation/volatility3
cd volatility3 && pip3 install -e .
# Verify installation
vol -h | head -5
# Symbol tables are required for Windows analysis
# Download pre-built symbol packs from Volatility Foundation:
# https://downloads.volatilityfoundation.org/volatility3/symbols/
# Place symbols in volatility3/symbols/ or configure path
# Windows symbols automatically downloaded on first use (internet needed)
# For airgapped lab: manually download and place:
# windows.zip → volatility3/symbols/windows/
# mac.zip → volatility3/symbols/mac/
# Test against your image
vol -f /cases/CASE001/RAM.raw windows.info
Core Plugin Reference
| Plugin | What it does | Command |
|---|---|---|
| windows.pslist | List all processes from the EPROCESS list | vol -f image.raw windows.pslist |
| windows.pstree | Process list with parent-child hierarchy | vol -f image.raw windows.pstree |
| windows.psscan | Scan memory for EPROCESS structures (finds hidden processes) | vol -f image.raw windows.psscan |
| windows.cmdline | Command line arguments for each process | vol -f image.raw windows.cmdline |
| windows.dlllist | DLLs loaded by each process | vol -f image.raw windows.dlllist --pid 1234 |
| windows.malfind | Find memory regions with executable code not backed by a file on disk | vol -f image.raw windows.malfind |
| windows.netscan | Network connections (active + recently closed) | vol -f image.raw windows.netscan |
| windows.netstat | Active TCP connections (Windows 10+) | vol -f image.raw windows.netstat |
| windows.hashdump | Extract NTLM hashes from SAM (requires SYSTEM key) | vol -f image.raw windows.hashdump |
| windows.lsadump | Extract LSA secrets | vol -f image.raw windows.lsadump |
| windows.registry.hivelist | List loaded registry hives | vol -f image.raw windows.registry.hivelist |
| windows.registry.printkey | Print a registry key from memory | vol -f image.raw windows.registry.printkey --key "Software\Microsoft\Windows\CurrentVersion\Run" |
| windows.handles | Open handles for a process | vol -f image.raw windows.handles --pid 1234 |
| windows.filescan | Find FILE_OBJECT structures — files open or recently accessed | vol -f image.raw windows.filescan |
| windows.dumpfiles | Extract file content from memory cache | vol -f image.raw windows.dumpfiles --physaddr 0x... |
Process Analysis Workflow
RAM="/cases/CASE001/RAM.raw"
OUT="/cases/CASE001/memory"
mkdir -p $OUT
# Step 1: Process tree — visual parent-child relationships
vol -f $RAM windows.pstree --output csv > $OUT/pstree.csv
# Step 2: Process scan (catches DKOM-hidden processes)
# Compare pslist vs psscan — processes in psscan but not pslist = hidden
vol -f $RAM windows.psscan --output csv > $OUT/psscan.csv
vol -f $RAM windows.pslist --output csv > $OUT/pslist.csv
# Step 3: Command lines — what arguments did each process receive?
vol -f $RAM windows.cmdline --output csv > $OUT/cmdlines.csv
# Step 4: Network connections
vol -f $RAM windows.netscan --output csv > $OUT/netscan.csv
# Step 5: Run malfind to locate injected code
vol -f $RAM windows.malfind --output csv > $OUT/malfind.csv
echo "Core analysis complete. Check $OUT/"
"""
Analyze Volatility pstree output for suspicious parent-child relationships.
Flags processes with unexpected parents.
"""
import csv
# Known-normal parent-child relationships
EXPECTED_PARENTS = {
"lsass.exe": ["wininit.exe"],
"services.exe": ["wininit.exe"],
"svchost.exe": ["services.exe"],
"explorer.exe": ["userinit.exe"],
"cmd.exe": ["explorer.exe", "cmd.exe", "conhost.exe"],
"powershell.exe": ["explorer.exe", "cmd.exe", "svchost.exe"],
"werfault.exe": ["svchost.exe", "wininit.exe"],
"taskhost.exe": ["services.exe"],
"spoolsv.exe": ["services.exe"],
}
# Flag suspicious: process with unexpected parent
SUSPICIOUS_PARENTS = {
"lsass.exe": ["any process except wininit.exe"],
"services.exe": ["any process except wininit.exe"],
"svchost.exe": ["anything not services.exe"],
"winlogon.exe": ["anything not smss.exe"],
"powershell.exe": ["office apps", "browser processes"],
}
with open("/cases/CASE001/memory/pstree.csv") as f:
reader = csv.DictReader(f)
for row in reader:
proc = row.get("ImageFileName", "").lower()
parent = row.get("__children", "") # Volatility pstree output
pid = row.get("PID", "")
ppid = row.get("PPID", "")
# Flag: processes with 0 or 4 as parent (SYSTEM spawning unexpected children)
if ppid in ("0", "4") and proc not in (
"system", "idle", "smss.exe", "csrss.exe", "wininit.exe", "winlogon.exe"
):
print(f"[SUSPICIOUS] PID {pid} {proc} has parent PID {ppid} (SYSTEM)")
# Flag: svchost not spawned by services.exe
# (require correlation with pslist PPID lookup)
print(f"PID {pid}: {proc} (parent: {ppid})")
Understanding malfind Output
malfind identifies memory regions that are executable and writable, with MZ/PE headers or shellcode signatures, that aren't backed by a file on disk — the hallmark of code injection.
How to Interpret malfind Results
═══════════════════════════════════════════════════════════════════
malfind output columns:
Process name — which process has the suspicious region
PID — process ID
Start VPN — virtual address of the suspicious region
End VPN — end address
Tag — memory region type (VadS = private, anonymous)
Protection — PAGE_EXECUTE_READWRITE (RWX) is classic injection
CommitCharge — pages committed
PrivateMemory — True = not backed by file (key indicator)
File output — optionally dump the region to a file
Classic injection signatures:
PAGE_EXECUTE_READWRITE (RWX) + PrivateMemory=True + MZ header
→ Almost certainly injected PE (DLL or EXE)
PAGE_EXECUTE_READWRITE + PrivateMemory=True + x86 shellcode
→ Shellcode injection (no PE header — raw shellcode)
Not all malfind hits are malicious:
.NET JIT compiled code appears as RWX private
Some legitimate apps use JIT or dynamically generated code
→ Triage by process: svchost.exe with malfind = high suspicion
node.exe with malfind = possibly normal (V8 JIT)
Extract the suspicious region for deeper analysis:
vol -f RAM.raw windows.malfind --pid 1234 --dump
→ Saves .dmp files that can be analyzed with YARA or Ghidra
Q & A
Q: pslist and psscan disagree — a process appears in psscan but not pslist. What does that mean and what do you do?
A process in psscan but not pslist has been "unlinked" from the EPROCESS doubly-linked list — a rootkit or advanced implant technique called Direct Kernel Object Manipulation (DKOM). pslist walks the linked list; psscan scans raw memory for EPROCESS structures regardless of list membership. The hidden process almost certainly represents malware or an attacker tool that has patched kernel memory to hide itself. Actions: (1) Note the PID and all associated information from the psscan output. (2) Run windows.cmdline --pid [PID] to get the command line if possible. (3) Run windows.dlllist --pid [PID] to see loaded DLLs. (4) Run windows.handles --pid [PID] to see what files/sockets the process has open. (5) Run windows.dumpfiles or process dump to extract the binary for analysis. (6) Escalate severity immediately — DKOM-capable malware is sophisticated and suggests a more capable adversary than commodity malware.