Reading width
Wide uses the full column for everything, text, diagrams, code, and exercises. Narrow keeps the standard reading width.
Text size
Scales the body text. Headings and code blocks keep their size.
In this section
Volatility 3 windows.pslist: the Commands, the Options and Reading the Output
Introduction
Search for how to find injected code in a memory image and the first result will tell you to run vol -f image windows.malfind. That command was removed from Volatility in June 2026. It now lives at windows.malware.malfind, and every cheat sheet, conference slide and blog post still showing the short form fails on a current install with an unhelpful error about an unknown plugin.
That is the gap this course exists in. The tooling moves, the free material does not, and the difference between the two is an afternoon lost to an error message that does not say what is wrong.
This sub is the commands a practitioner actually types in the first ten minutes of looking at a Windows memory image, in the order they type them, with the output each one returns. It uses two tools rather than one: Volatility, which runs a plugin and prints rows, and MemProcFS, which mounts the same image as a drive letter so you can browse it. Both are free, both are current, and they answer the same questions in ways that suit different moments.
Nothing here needs you to have an image. There is one below, and a listing from it you will read three times before the sub is finished.
Scenario
NE-ENG-031 is an engineering workstation that started behaving oddly this afternoon. Memory was captured at 14:31 and the file is NE-ENG-031-1431.raw. You have the file and nothing else: no alert, no ticket, no idea what happened. The six commands below are what a practitioner actually types in the first ten minutes, in the order they type them.
Before any of the commands, read the listing they produce. Three questions, all answerable from the columns, and none of them needs a process name you recognize.
If the third question took you somewhere other than the row at 14:07, reread the creation times rather than the names. The rest of this sub is the commands that produced that listing and the ones you run once it has given you a row to chase.
windows.info, the Command You Run First
Establish what the image is before asking it anythingNever start with windows.pslist, however tempting it is to go straight to the process list. Start by confirming that Volatility can read the image at all, and that it believes it is looking at the operating system you think it is. Both of those can be false, and finding out later wastes an hour.
$ vol -f NE-ENG-031-1431.raw windows.info
Volatility 3 Framework 2.28.2
Variable Value
Kernel Base 0xf80342a00000
DTB 0x1ad000
Is64Bit True
IsPAE False
primary WindowsIntel32e
memory_layer FileLayer
KdVersionBlock 0xf80342c2a3a0
Major/Minor 15.26100
MachineType 34404
NtSystemRoot C:\Windows
NtProductType NtProductWinNt
SystemTime 2026-03-15 14:31:06
That is a lot of fields and most of them you will never use. Four matter on a first run. Is64Bit and primary say Volatility worked out the architecture and picked a layer, which means it found the kernel. DTB is the directory table base, the root of the page tables, and having it is what makes every later plugin possible. SystemTime is when the capture was taken according to the machine itself, which is not necessarily when it was taken according to reality.
Major/Minor is the build, which is the value that has to match a symbol table exactly, and Kernel Base is where Volatility found the kernel in memory. Between them they are the evidence that the tool has correctly identified what it is reading rather than guessing.
If this command fails or returns nothing, no other plugin will work, and the reason is almost always missing symbols rather than a corrupt image, which is a fixable problem with a specific fix. That is a whole sub later in this module.
One more reason to run it first, beyond the checking. windows.info is what forces Volatility to work out the memory layout, and that work is the slow part of any run. The framework can save the result with --save-config and reuse it with --config, which turns every subsequent plugin on the same image from slow to immediate. On a large image that is the difference between a comfortable afternoon and an irritating one.
Run this every time, on every image, before anything else. It takes a few seconds and it establishes both that the tooling works and what the machine believed the time was.
windows.pslist, and the Listing It Returns
The second command, and the one you will run mostwindows.pslist walks the list of process objects the kernel maintains to track what is running, and prints one line per process.
$ vol -f NE-ENG-031-1431.raw windows.pslist
PID PPID ImageFileName Offset(V) SessionId CreateTime
4 0 System 0xa88e3e07a040 N/A 2026-03-15 07:41:02 UTC
336 4 smss.exe 0xa88e3e8b1080 N/A 2026-03-15 07:41:04 UTC
468 336 csrss.exe 0xa88e3e9c2140 0 2026-03-15 07:41:11 UTC
548 336 wininit.exe 0xa88e3ea11080 0 2026-03-15 07:41:14 UTC
624 548 services.exe 0xa88e3ff08080 0 2026-03-15 07:41:17 UTC
672 548 lsass.exe 0xa88e40122080 0 2026-03-15 07:41:19 UTC
812 624 svchost.exe 0xa88e40233300 0 2026-03-15 07:41:22 UTC
904 624 svchost.exe 0xa88e40251080 0 2026-03-15 07:41:23 UTC
1108 624 svchost.exe 0xa88e4029a240 0 2026-03-15 07:41:26 UTC
1344 624 MsMpEng.exe 0xa88e40410080 0 2026-03-15 07:41:38 UTC
560 336 csrss.exe 0xa88e400f1080 1 2026-03-15 08:51:54 UTC
640 548 winlogon.exe 0xa88e4011c300 1 2026-03-15 08:51:58 UTC
1428 1204 explorer.exe 0xa88e41f3a080 1 2026-03-15 08:52:11 UTC
2016 1428 msedge.exe 0xa88e4211c080 1 2026-03-15 08:54:02 UTC
2104 1428 msedge.exe 0xa88e42140080 1 2026-03-15 08:54:03 UTC
2260 1428 Teams.exe 0xa88e42260140 1 2026-03-15 08:55:41 UTC
2884 1428 OUTLOOK.EXE 0xa88e42551080 1 2026-03-15 09:02:17 UTC
3312 1428 cmd.exe 0xa88e42881080 1 2026-03-15 14:07:33 UTC
One thing to be clear about before reading any of the columns: this plugin finds processes by walking a linked list that the kernel maintains for its own purposes. It is not searching memory, it is following pointers from one process object to the next, exactly as the operating system does when it needs to enumerate what is running.
Following pointers is fast and honest, and it can only reach what is still linked.
That mechanism is why the output is fast and why it is trustworthy: you are reading the same records the operating system relies on to function, not a reconstruction. It is also the source of the one blind spot this plugin has, because a list can only be walked while its links are intact. Hold that thought; the next sub is entirely about it.
The plugin prints Threads, Handles and Wow64 between the offset and the session; they are cut here so the lines fit the page. Nothing is reordered or renamed.
Eighteen lines here and a real workstation runs to sixty or more, but the shape holds: a block of system processes from boot, a block belonging to the user's session, and whatever sits outside both.
Now the columns. PID identifies the process to the operating system while it runs. PPID is the identifier of whatever created it, written once at creation and never updated. Offset(V) is the virtual address of the process object itself, so every other value on the line was read out of the structure sitting at that address. It is the column people skip and the one that lets a reader verify your finding, because a PID is ambiguous across two captures and an offset is not. SessionId is which logon session it belongs to: session 0 is non-interactive and is where services run, session 1 is the user's own, and N/A means the process belongs to no session at all, which is correct for System and would be nonsense as a zero. CreateTime is when the kernel created the object, written by the kernel into kernel memory, which is why it outranks almost every other timestamp you will meet.
Making pslist Output Usable: -r and grep
Renderers, and the pipe you will use constantlyThe terminal is the wrong place to read sixty lines of tabular data and there is no prize for persevering. Volatility gives you two ways out of that, and both are worth having in your fingers rather than looking up.
Sixty lines of default output is hard to read, and Volatility's global -r option changes the renderer. The default is quick, which prints rows as it finds them at the cost of ragged columns. pretty waits until the run finishes and aligns everything, which is better to read and slower to start. csv and json are for feeding the output into something else rather than reading it yourself.
$ vol -r pretty -f NE-ENG-031-1431.raw windows.pslist
$ vol -q -r csv -f NE-ENG-031-1431.raw windows.pslist > pslist.csv
$ vol -f NE-ENG-031-1431.raw windows.pslist | grep -i explorer
1428 1204 explorer.exe 0xa88e41f3a080 1 2026-03-15 08:52:11 UTC
The -q on the second line suppresses the progress output that would otherwise land in the middle of your CSV. -r and -q are both global options and go before the plugin name, not after it. That trips people up constantly: plugin options like --pid go after the plugin, Volatility's own options go before it.
The third line is the pipe you will type more than any other. Volatility writes plain text to standard output, so every filtering tool you already know works on it unchanged: grep for a name, grep -c to count, sort and awk if you prefer them to a spreadsheet. There is no special query syntax to learn.
The csv line is the one worth adopting as a habit. Getting the process list into a spreadsheet lets you sort by creation time in one click, which is the single most useful thing you can do with a process list and is genuinely awkward at a terminal. The json renderer is the one to reach for if you ever automate any of this, since it gives you the column names alongside the rows.
sorted ascending by CreateTime ...
07:41:02 - 07:41:38 ten processes boot
08:51:54 - 09:02:17 seven processes logon and the user's apps
14:07:33 one process five hours later, alone
Plotted against a clock rather than listed, the same eighteen rows look like this.
The line is ordinary. The empty space either side of it is the finding.
Three clusters and the third has one member. Nothing about that grouping required knowing anything about this machine, and it is why the CSV line is worth typing even when the terminal output looks readable.
windows.pstree, the Same Data Shaped as Descent
Where a missing parent becomes visiblewindows.pstree enumerates processes by exactly the same mechanism pslist uses, walking the same kernel list, and then indents each process under its parent. Same data, same blind spots, different shape. It will not show you anything pslist missed, and the shape makes two things obvious that a flat list hides: what the ordinary service chain looks like, and which processes have no parent at all.
$ vol -f NE-ENG-031-1431.raw windows.pstree
PID PPID ImageFileName CreateTime
4 0 System 2026-03-15 07:41:02 UTC
* 336 4 smss.exe 2026-03-15 07:41:04 UTC
548 336 wininit.exe 2026-03-15 07:41:14 UTC
* 624 548 services.exe 2026-03-15 07:41:17 UTC
** 812 624 svchost.exe 2026-03-15 07:41:22 UTC
** 1344 624 MsMpEng.exe 2026-03-15 07:41:38 UTC
* 672 548 lsass.exe 2026-03-15 07:41:19 UTC
1428 1204 explorer.exe 2026-03-15 08:52:11 UTC
* 2016 1428 msedge.exe 2026-03-15 08:54:02 UTC
* 2260 1428 Teams.exe 2026-03-15 08:55:41 UTC
* 3312 1428 cmd.exe 2026-03-15 14:07:33 UTC
Asterisks mark depth, one per level. services.exe sits under wininit.exe with several svchost.exe processes beneath it, which is the ordinary service chain on every Windows machine and is what you want to be able to recognize at a glance.
It is worth running pstree on every image even though it shows you nothing pslist did not already contain. Descent is hard to see in a flat list and obvious in an indented one, and the whole value of this plugin is that it does that one transformation for you.
Now look at explorer.exe. It is at the left margin, not indented under anything, even though its PPID says 1204. The tree puts it at the root because there is no process 1204 anywhere in the image to nest it under. In the flat listing that fact was a number you would have had to check by hand; here the layout does the checking for you.
Notice what answering that required: knowledge of how Windows starts a desktop, not knowledge about attackers or indicators. That is the shape of most memory forensics. Anomalies are departures from ordinary system behavior, so ordinary system behavior is the thing worth studying, and an analyst who knows what a clean Windows process tree looks like needs no list of indicators to notice one that is wrong.
That also explains why pstree is worth running on machines you believe are clean. Building the picture of normal is the work; spotting the departure is comparatively easy once you have it.
Narrowing With --pid, and What Comes Next
Plugin options, and the three commands you run after pslistUp to this point every command has been run against the whole image, which is correct while you are surveying and wasteful once you are not. Once the list has pointed at a process, most plugins take --pid to restrict their output to it, which turns thousands of lines into a few dozen. The option goes after the plugin name.
$ vol -f NE-ENG-031-1431.raw windows.dlllist --pid 1428
$ vol -f NE-ENG-031-1431.raw windows.handles --pid 1428
$ vol -f NE-ENG-031-1431.raw windows.netscan
$ vol -o ./out -f NE-ENG-031-1431.raw windows.pslist --pid 3312 --dump
Each of those is a command you will run in anger before this module is finished, so it is worth knowing now what each of them returns and why you would reach for it. dlllist shows which modules are loaded inside a process, handles shows the objects it has open and the access it was granted to each, netscan shows connections and which process owns each, and --dump with an output directory writes the process executable out to disk so it can be examined with tools that know nothing about memory images.
windows.netscan is the odd one out in that list because it takes no --pid. It scans for network structures across the whole image and prints the owning process for each connection it finds, so you run it whole and read the process column. That is worth knowing because it is the only source on the machine that ties a connection to the program responsible; a firewall sees the addresses and never learns which program opened them.
Get this wrong and you get an argument error, not a wrong answer. It is the only syntax rule the tool has.
Note -o ./out on the last one: it is a global option so it goes before the plugin, while --pid and --dump belong to the plugin and go after. Getting that wrong produces an argument error rather than a wrong answer, which is the kind of mistake you only make twice. The rule is worth saying once more because it is the entire syntax of the tool: anything about how Volatility runs goes before the plugin, anything about what the plugin does goes after.
What pslist Cannot Tell You
Four gaps, and which command closes eachA process list is an inventory of what exists. That is genuinely useful and it is a narrower thing than people assume, because several of the questions an investigation actually cares about sit outside it no matter how carefully the columns are read.
Work the sort below before reading on, because getting one wrong is more informative than getting them all right.
Two of the five are answered by columns you have already read and three are not answered at all, and the pattern behind that split is worth naming. pslist reports the process OBJECT: its identity, its descent, its timing, where it sits in memory. Everything about what the process is doing, holding or talking to lives in different structures entirely, which is why those questions need different plugins rather than more careful reading of this one.
The third question is the one to sit with. The listing you have read on this page would look exactly the same if explorer.exe had been running attacker code the entire time, because pslist reports that a process exists and never what is inside it.
That is not a criticism of the plugin. It is doing exactly what it claims, and the error is entirely on the analyst who treats a clean process list as evidence that a machine is clean. A process list showing nothing unusual is a statement about process objects and about nothing else.
There is a fourth gap that announces itself less than the others. A process that exited before the capture has been unlinked from the list this plugin walks, so it leaves no blank line and no marker: the output looks exactly as complete as it would if nothing were missing. There is no gap in the numbering, no placeholder, no warning. That is what the next sub is about, and it is the reason this one kept mentioning that pslist walks a list.
The Six Commands as a Sequence
What you type, in order, on any imageEverything above collapses into a short sequence you can run on any Windows image without thinking about it, which is the point: the thinking should go on the output rather than on what to type.
$ vol -f IMAGE windows.info # does it read? what time?
$ vol -q -r csv -f IMAGE windows.pslist > ps.csv # list, into a spreadsheet
$ vol -f IMAGE windows.pstree # descent, and orphans
$ vol -f IMAGE windows.netscan # connections, with owners
$ vol -f IMAGE windows.handles --pid TARGET # what it has open
$ vol -f IMAGE windows.dlllist --pid TARGET # what is loaded inside
Three things about that sequence are worth noticing. The order is not arbitrary: windows.info proves the tooling works before you invest time, the process list gives you targets, and the last two commands need a target to point at. The global options sit before the plugin name in every line. And four of the six take no options at all, which is the general case with Volatility: most plugins do one thing and need only the image.
The first four are run against every image and cost a few minutes between them. The last two are aimed at a process the first four told you to look at, which is why TARGET is a placeholder rather than a number.
Ten minutes is a realistic estimate for the first four on a workstation image, most of it spent waiting rather than typing. On this image TARGET is arguably 1428, because explorer.exe is the parent of the one process that arrived five hours after everything else. That is a question rather than a finding, and the difference matters for what you do next. A question sends you to another command; a finding goes in a report. An analyst who writes "cmd.exe launched at 14:07, indicating compromise" has skipped every command in this module and produced a sentence nobody can check and nobody can act on.
The rest of the module is the sequence that turns that question into something defensible, one plugin at a time. Each one narrows what is possible, and the report is written from what is left rather than from what looked suspicious at the start.
The Same Image as a Drive Letter
MemProcFS, and when browsing beats a pluginVolatility answers a question you already know how to ask. MemProcFS answers the ones you do not, by mounting the image as a filesystem and letting you look around.
PS> MemProcFS.exe -device NE-ENG-031-1431.raw -mount M
PS> dir M:\name
Directory: M:\name
explorer.exe-1428
cmd.exe-3312
lsass.exe-672
Every process is a directory, named and numbered, and inside each one are files holding what the plugins return: the command line, the loaded modules, the handles, the memory regions. Reading a process becomes dir and type rather than remembering which plugin carries which field.
That difference matters more than it sounds when you do not yet know what you are looking for. A plugin returns what you asked for; a filesystem lets you notice something you were not looking for, which is most of what early triage actually is.
There is a second advantage that only shows up under pressure. Everything in that mount is readable by tools that know nothing about memory forensics: findstr, a text editor, a script somebody else wrote, your own grep habits. An image stops being a format requiring a specialist tool and becomes a directory tree, which is the difference between handing a colleague a file and handing them something they can actually open.
The trade is that a filesystem has no history. You cannot diff two mounts the way MF0.2 diffs two listings, and you cannot pipe a directory into a counting command as easily as a CSV. Exploration and analysis are different activities and these two tools are good at different ones.
It takes -pagefile0 pagefile.sys where you have the page file, which recovers memory Windows had written out to disk, and -device pmem against a live machine with the WinPMEM driver rather than against a file at all.
Neither tool replaces the other. Volatility scripts and produces text you can filter, count and diff, which is every workflow in this module. MemProcFS is faster to explore and far easier to hand to somebody who has never used either. Run whichever fits the question, and know that both read the same structures underneath.
Practice
Install Volatility and run the sequence hands on- Install both tools: Volatility 3 from the project's repository, and MemProcFS from its releases page. Confirm each runs before going further.
- Confirm Volatility's own help works:
vol -hshould list the global options this sub used, andvol -f x windows.info -hshould show the plugin's own. Knowing which options belong where is most of using the tool. - Get an image to work on. Capture your own from a virtual machine you control, or use one of the public sample images the Volatility project and several universities publish for teaching. Either works for this exercise.
- Run
windows.infofirst, every time. Note the architecture, the DTB and the system time, and compare that time against when you took the capture. - Run the pslist-to-CSV command, open it in a spreadsheet and sort by CreateTime. Identify the boot cluster, the logon cluster, and anything that sits apart from both.
- Run
windows.pstreeand find every process sitting at the left margin with no parent above it. Explain each one. On a clean machine you should be able to account for all of them. - Pick one process and run
windows.handles --pidandwindows.dlllist --pidagainst it. You will not understand most of the output yet, and the point is to see what the commands return before the module explains them.
Extend it
Once the six commands run, these are the variations worth trying.
- Run the same plugin with
-r quick,-r prettyand-r jsonand see what each is good for. The json output is what you would parse if you were automating any of this. - Run
windows.pslist --dump --pidwith-opointing at an empty directory, and look at what it writes. Knowing the tool can extract an executable changes what you do when you find a suspicious one. - Time
windows.infoon a large image. It is slower than you expect because Volatility is scanning to find the kernel, which is why the--save-configoption exists. - Deliberately run a plugin option before the plugin name and read the error. Making the mistake once on purpose is cheaper than making it at three in the morning.
Drill two is the one with the most in it. An executable pulled out of memory is not the same bytes as the file on disk, because the loader has already done its work on it, and seeing that for yourself explains a great deal about why memory analysis and file analysis answer different questions.
Drill four is worth the thirty seconds. Volatility's argument order is the commonest source of friction for people new to it, and the error message is clear once you have seen it.
You can run the six commands that start any Windows memory investigation, mount the same image with MemProcFS and browse it, know which options go before the plugin and which after, read every column windows.pslist prints, and name which plugin closes each gap the process list leaves. MF0.2 runs a seventh command that returns a different answer from pslist against the same image.