Step 242. Memory Forensics: Volatility 3 — How to Catch Evidence That Vanishes When the Power Goes Off
Level 3 — Forensics Track | Difficulty ★★★★☆ | Estimated time: 5 hours
Prerequisites: Step 239 (file signatures) complete. You know what a process is and how to search for strings (
strings).
⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.
- What you need: WSL Ubuntu + Python 3 (measured: 3.12).
/procaccess (root). - Caution: this environment has neither Volatility nor a practice memory image. All Volatility output is a "Screen example," while the principles of memory forensics — process lists, network connections, strings in memory — are measured live from Linux’s
/proc.
The moment you power off a computer at a crime scene, the contents of RAM evaporate. Yet the most important evidence — the malicious process running right now, the command just typed, a decrypted key, a password sitting in cleartext — lives only in RAM, not on disk. Memory forensics is the technique of freezing this volatile evidence into a dump and dissecting it. Today you learn the map of the standard tool Volatility 3, and verify the principles behind its plugins directly on a live Linux system.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Enumerate the kinds of evidence that exist only in memory (processes, connections, command lines, cleartext data)
- Reconstruct a process list and network connections by reading
/proc - Extract strings directly from another process’s memory
- Explain Volatility’s analysis order (
windows.info→pslist/psscan→pstree→ collection) - Know how the
pslist/psscandifference is used for rootkit detection
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | WSL Ubuntu bash + Python 3 (measured: Python 3.12) |
| Today’s tools | /proc/<pid>/ (comm, cmdline, maps, mem), /proc/net/tcp — all measured live |
| Concepts needed | Volatility, process memory maps, dumping, the Volatility plugin system (Screen example) |
| Today’s deliverable | A /proc observation script + "a secret string pulled from another process’s memory" |
2-1. Why Memory — The Things That Aren’t on Disk
Disk is the world of "what was stored"; memory is the world of "what’s happening right now." The representative list of evidence that exists only in RAM:
- Running processes and their command lines — including fileless malware that never exists as a file
- Live network connections — who the machine is talking to at this very moment
- Decrypted data — keys and strings that are encrypted on disk but cleartext while running
- Command history and clipboard — what the user just did
This is why the beginner’s mistake of "just power it off first" is fatal in incident response (IR). Cut the power and this entire list evaporates.
2-2. Dump and Dissection — A Two-Stage Division of Labor
Memory forensics splits into two stages. ① Dump: freeze the RAM at the moment of the incident into a single file (tools like WinPmem on Windows or LiME on Linux read at the kernel level). ② Dissection: restore meaning from that raw blob of bytes. A dump is just gigabytes of bytes, so you need a tool that knows "where the process list lives and what each process’s structures look like" — that tool is Volatility.
2-3. /proc — The Memory Window of a Live System
Linux already has the perfect practice window open. /proc is a virtual filesystem the kernel maintains, showing the state of the currently running system as if it were files:
| Path | Contents | Volatility counterpart |
|---|---|---|
/proc/<pid>/comm, cmdline |
Process name & command line | pslist, cmdline |
/proc/net/tcp |
Current TCP connection table | netstat |
/proc/<pid>/maps |
The process’s memory map | memmap |
/proc/<pid>/mem |
The actual contents of that memory | memmap --dump |
Today we read the same information from a live system instead of a dump file — because what Volatility does on a dead dump, /proc shows you directly on a living system.
2-4. Volatility 3 — The Plugin Map (Screen Example)
It’s not installed in this environment, but the real-world standard workflow looks like this (Screen example — not actually executed):
# Screen example — stage 0: identify the image's OS (the first step of every analysis)
$ vol -f memory.raw windows.info
Variable Value
Kernel Base 0xf8054b600000
NTBuildLab 19041.329.amd64fre.vb_release.190602-1739
# Screen example — stage 1: two kinds of process lists
$ vol -f memory.raw windows.pslist # follows the kernel's "current list"
PID PPID ImageFileName
4 0 System
512 4 smss.exe
3844 512 powershell.exe
$ vol -f memory.raw windows.psscan # sweeps all of memory for structure traces
PID PPID ImageFileName
...
5920 3844 evil.exe ← a process that wasn't in pslist!
The difference between the two lists is the key clue. pslist reads the operating system’s official registry; psscan scans all of memory for traces of process structures. A process that erased its own name from the official registry (a rootkit) appears only in psscan.
# Screen example — stage 2: collecting relationships and traces
$ vol -f memory.raw windows.pstree # parent-child: powershell.exe under winword.exe → the face of macro infection
$ vol -f memory.raw windows.cmdline # what command launched each process
$ vol -f memory.raw windows.netstat # network connections at dump time
$ vol -f memory.raw -o out/ windows.memmap --dump --pid 5920 # dump the suspicious process's memory to a file
$ strings out/pid.5920.dmp | grep -i "DH{" # find strings in it
Those last two lines — dumping a suspicious process’s memory and running strings over it — are exactly the job we’ll do ourselves today with /proc.
3. Follow Along
3-1. Staging the Incident — A Process Holding a Secret
Create a process that carries a secret only in memory (all local output in this chapter measured 2026-09-09 on WSL).
Input (victim.py)
import time, sys, os
SECRET = "DH{m3m0ry_n3v3r_f0rg3ts}" # this value really exists in the running process's RAM
print(f"[victim] PID={os.getpid()} holding a secret in memory, waiting...")
sys.stdout.flush()
time.sleep(120)
python3 victim.py &
[victim] PID=616 holding a secret in memory, waiting...
How to read it: this process writes no files and uses no network. The secret lives only in this process’s RAM. No matter how hard you search the disk, this running process’s variable value won’t turn up — it’s evidence you can only see by looking at memory.
3-2. Reconstructing the Process List — The Principle of pslist
Each numbered directory in /proc is one process. Read the names and command lines:
import os
for pid in sorted((p for p in os.listdir("/proc") if p.isdigit()), key=int):
try:
name = open(f"/proc/{pid}/comm").read().strip()
cmd = open(f"/proc/{pid}/cmdline", "rb").read().replace(b"\x00", b" ").decode().strip()
print(f"PID {pid:>6} {name:16s} {cmd[:60]}")
except (FileNotFoundError, PermissionError):
pass
PID 1 systemd /sbin/init
PID 2 init-systemd(Co /init
PID 49 systemd-journal /usr/lib/systemd/systemd-journald
PID 98 systemd-udevd /usr/lib/systemd/systemd-udevd
PID 616 python3 python3 victim.py
...
(Measured 2026-09-09 — 52 processes total, excerpt shown.)
How to read it: PID 616 catches the eye — you can even see the command line python3 victim.py. This is exactly the information Volatility’s pslist (list) and cmdline (command line) plugins restore from a dump. We just read the same thing from a live system.
3-3. Reconstructing Network Connections — The Principle of netstat
/proc/net/tcp is the current TCP connection table. The addresses are little-endian hex, so they need decoding:
import socket, struct
STATES = {"0A": "LISTEN", "01": "ESTABLISHED"}
def dec(hexaddr):
h, p = hexaddr.split(":")
ip = socket.inet_ntoa(struct.pack("<I", int(h, 16)))
return f"{ip}:{int(p, 16)}"
with open("/proc/net/tcp") as f:
next(f)
for line in f:
cols = line.split()
st = STATES.get(cols[3], cols[3])
if st:
print(f"{dec(cols[1]):22s} -> {dec(cols[2]):22s} {st}")
127.0.0.54:53 -> 0.0.0.0:0 LISTEN
127.0.0.1:46047 -> 0.0.0.0:0 LISTEN
10.255.255.254:53 -> 0.0.0.0:0 LISTEN
127.0.0.53:53 -> 0.0.0.0:0 LISTEN
(Measured 2026-09-09. The LISTEN entries on port 53 are WSL’s internal DNS windows.)
How to read it: it’s quiet right now, but if there were an ESTABLISHED to an unknown external IP here — that’s the evidence "who is connected at this very moment," the kind that vanishes when the power goes off. What windows.netstat restores from a dump is this table’s incident-moment version.
3-4. Pulling the Secret from Process Memory — The Principle of memmap+strings
Today’s peak. Read /proc/<pid>/maps (the memory map), then search the actual contents of the writable regions (/proc/<pid>/mem) for a string starting with DH{:
import os, re, sys
target = sys.argv[1] # the victim's PID
found = []
with open(f"/proc/{target}/maps") as f:
maps = [l.split() for l in f if "rw" in l.split()[1]] # writable regions only
mem = os.open(f"/proc/{target}/mem", os.O_RDONLY)
scanned = 0
for m in maps:
start, end = (int(x, 16) for x in m[0].split("-"))
size = end - start
if size > 8 * 1024 * 1024:
continue
try:
os.lseek(mem, start, os.SEEK_SET)
data = os.read(mem, size)
scanned += size
for mt in re.finditer(rb"DH\{[ -~]{1,60}\}", data):
s = mt.group().decode()
if s not in found:
found.append(s)
except OSError:
continue
os.close(mem)
print(f"PID {target}: scanned {scanned/1024:.0f}KB of writable memory")
for s in found:
print("found:", s)
python3 memscan.py 616
PID 616: scanned 5140KB of writable memory
found: DH{m3m0ry_n3v3r_f0rg3ts}
(Measured 2026-09-09.)
How to read it: a string that was never stored anywhere on disk was pulled out of another process’s RAM. These 30 lines are the very principle of what Volatility’s memmap --dump + strings combination does — follow the memory map (maps), read the contents (mem), and find string patterns. In real cases, decrypted keys, C2 addresses, and cleartext credentials come out here. "Even an encrypted file is cleartext in memory the moment it runs" — the power of memory forensics in one line.
3-5. The Full Workflow, Organized — If You Had a Dump
Translate today’s practice into Volatility’s language and it looks like this (Screen example):
# Screen example — the standard order for analyzing a memory dump
1. windows.info → identify which OS this dump is (the basis for symbol selection)
2. windows.pslist → the official process list
3. windows.psscan → the scanned list — a difference from pslist means suspected hiding ← 3-2's principle
4. windows.pstree → strange parent-child pairs (a document spawning a shell?)
5. windows.cmdline → command lines of suspicious processes ← 3-2's principle
6. windows.netstat → connections at dump time ← 3-3's principle
7. memmap --dump + strings → strings from a suspicious process's memory ← 3-4's principle
What we did in /proc today in 3-2, 3-3, and 3-4 follows the same principles as steps 2, 5, 6, and 7 of this order. The tool works on "a dead dump"; we worked on "a live system" — that’s the only difference.
4. Missions & Exercises
Mission — An Observation Report on My Own System
- Extend 3-2’s code into a list that also reads each process’s PPID (parent number) (the
PPid:line of/proc/<pid>/status) - Use that list to print a parent-child tree (indented form) — the principle of pstree
- With 3-4’s scanner, scan one other process besides victim (e.g., a running shell) and observe what strings come out
- Write, in five sentences or fewer, "why incident response dumps memory first instead of powering off," citing today’s three experiments as evidence
Exercises
Problem 1. What is the difference between pslist and psscan, and why is a difference between their results suspicious?
Problem 2. An encrypted malware file couldn’t be found on disk, but memory analysis found it. Why?
Problem 3. In 3-4, only the "writable (rw)" regions of maps were read. Guess the reason for this choice.
Problem 4. Reading /proc/<pid>/mem can fail with a permission error. What privilege is needed, and what’s the corresponding barrier at a real incident scene?
5. Model Answers & Completion Criteria
Mission Model Answer
①–② Core code for the PPID tree:
procs = {}
for pid in (p for p in os.listdir("/proc") if p.isdigit()):
try:
status = open(f"/proc/{pid}/status").read()
name = re.search(r"Name:\t(.+)", status).group(1)
ppid = int(re.search(r"PPid:\t(\d+)", status).group(1))
procs[int(pid)] = (name, ppid)
except Exception:
pass
def tree(pid, depth=0):
if pid in procs:
print(" " * depth + f"{procs[pid][0]} ({pid})")
for p, (n, pp) in procs.items():
if pp == pid:
tree(p, depth + 1)
tree(1)
③ Observation example: scanning a shell process shows environment variables, fragments of recent commands, and remnants of history — a direct confirmation that "a process’s memory holds the residue of what that process has done."
④ Skeleton of the answer: the process list (3-2), the connection table (3-3), and the cleartext secret (3-4) all exist only in RAM and disappear when the power goes off. The string we pulled from PID 616 today is the proof. That’s why the field dumps memory before disk.
How to verify: ① Does the tree draw the actual hierarchy under systemd? ② Was the scan observation recorded? ③ Does the answer to ④ cite all three experiments?
Exercise Answers
Answer 1. pslist walks the linked list the kernel maintains (the official registry); psscan scans all of memory for traces of process structures. A rootkit hides by detaching its own node from the official registry, but erasing every structure trace somewhere in memory is hard. That’s why a process that appears only in psscan is suspected of being hidden — it could also be the residue of a terminated process, so further confirmation is needed.
Answer 2. To run, the CPU must read the code, and at that moment the code and data unwind into cleartext in memory. Disk encryption protects the "stored state" only; it cannot protect the "running state." As we saw in 3-4 today, memory is the place where every secret of execution lies exposed in cleartext.
Answer 3. Writable regions hold the heap and stack — the data a process produced while running (variables, input values, decryption results). Read-only regions are mostly program code and constants, identical to the original file on disk. Since the goal is finding "what came into being while running," rw regions are the first search area.
Answer 4. On Linux you need to be the same user or have root privileges (including ptrace constraints). The corresponding barrier at a real scene is kernel-level protection — it’s why memory-dump tools (WinPmem, LiME) demand administrator/kernel-driver privileges, and modern OS protections (kernel memory access restrictions, encrypted RAM) are making this collection steadily harder.
Completion Criteria Checklist
- [ ] I can name the four kinds of evidence that exist only in memory
- [ ] I read the process list and command lines from
/procmyself - [ ] I decoded
/proc/net/tcpand reconstructed the connection table - [ ] I extracted a secret string from another process’s memory
- [ ] I memorized Volatility’s analysis order (info → pslist/psscan → pstree → collection)
- [ ] I can explain the relationship between the pslist/psscan difference and rootkit detection
- [ ] Mission: completed the PPID tree and observation report
6. Common Pitfalls & Fixes
Wall 1. Permission denied keeps you from reading /proc/<pid>/mem
Symptom: os.open raises PermissionError.
Cause: another user’s process memory is protected. The ptrace security policy (ptrace_scope) can also block reads.
Fix: target a process you launched, or run as root. This very constraint is the operating system’s answer that "memory is a protected area" — and the reason real dump tools need kernel privileges.
Wall 2. Volatility fails on every plugin
Symptom (Screen example): vol -f dump.raw windows.pslist throws a symbol error.
Cause: without symbols (the structure map) matching the dump’s OS version, Volatility cannot interpret what the bytes mean.
Fix: always start with windows.info — its output is the basis for symbol selection. Reverse the order and every plugin lies to you.
Wall 3. psscan returns too many results
Symptom (Screen example): psscan shows dozens of unknown processes.
Cause: scanning also catches the debris of terminated processes. They’re not all alive.
Fix: narrow the "hiding" candidates by taking the difference from pslist, and look at the exit-time field if present. Distinguishing debris from concealment is the analyst’s job.
Wall 4. Reading mem at an address from maps raises OSError
Symptom (verifiable during the 2026-09-09 exercise): lseek/read fails on some regions.
Cause: pages that are mapped but not currently in physical memory (swapped out), or kernel-only regions.
Fix: catch the exception and skip — that’s the job of try/except OSError in the practice code. In real dumps too, swapped pages simply aren’t there. "There are things even a memory dump doesn’t have" is a fact worth remembering.
Wall 5. You ran strings but nothing meaningful came out
Symptom (Screen example): tens of thousands of string lines pour out of the dumped process memory and you can’t find the point.
Cause: memory mixes in strings from code and libraries. Not everything is a clue.
Fix: narrow with patterns — the flag format (DH{), IP shapes, http, .exe, file paths. Deciding "the shape of what you’re looking for" first, like the regex in 3-4, is half of string analysis.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Volatility | RAM’s contents evaporate with the power — so dump first |
| Memory-only evidence | Running processes, live connections, cleartext data, command history |
/proc |
The memory window of a live system — a practice ground for Volatility’s principles |
| Dump and dissection | A two-stage division of labor: collection (kernel privileges) and interpretation (symbols) |
| pslist vs psscan | Official registry vs full scan — the difference is the signal of concealment |
| Symbols | The dictionary that translates a dump’s bytes into structures — info comes first |
Today’s Commands & Tools
| Command/tool | What it does |
|---|---|
/proc/<pid>/comm, cmdline |
Process name & command line (the principle of pslist & cmdline) |
/proc/net/tcp |
Current TCP connection table (the principle of netstat) |
/proc/<pid>/maps + mem |
Process memory map and contents (the principle of memmap) |
vol -f dump.raw windows.info (Screen example) |
Confirm the dump’s OS & symbols — always the first step |
vol ... windows.pslist / psscan / pstree (Screen example) |
Process list, hiding detection, relationships |
vol ... windows.memmap --dump --pid N (Screen example) |
Collect a suspicious process’s memory |
An Instinct More Important Than Commands
Memory forensics’ worldview is one sentence — everything that runs exists somewhere in cleartext. Encryption, packing, and fileless techniques are disguises on disk, and memory is the backstage where those disguises come off. Today’s 30 lines that pulled a secret string from another process’s RAM are the proof of that worldview.
At the same time, you’ve seen the opposite direction too: evidence this rich disappears with a single power button. "Don’t power it off — dump it," the first action at an incident scene, is a simple rule, and now you know in your bones why it’s the golden rule.
Once every box is checked, Step 242 is complete. Click the checkbox in the sidebar to save your progress.