In this section

Volatility 3 windows.pslist: the Commands, the Options and Reading the Output

Module 0

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.

01

windows.info, the Command You Run First

Establish what the image is before asking it anything

Never 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.

You captured at 14:31 by your watch and SystemTime says 14:31:06. What have you just learned?pick one, then the tell

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.

02

windows.pslist, and the Listing It Returns

The second command, and the one you will run most

windows.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.

the kernel's list of process objects, as pslist walks it System PID 4 smss.exe PID 336 lsass.exe PID 672 explorer.exe PID 1428 ... a process whose links were removed never visited, and leaves no gap in the output the walk steps straight past it

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.

03

Making pslist Output Usable: -r and grep

Renderers, and the pipe you will use constantly

The 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.

You sort that CSV by CreateTime. What does the machine's day look like?predict, then reveal
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
Three clusters, and the third has one member. That grouping took no expertise and no threat intelligence, only sorting one column, and it has already reduced eighteen lines to one worth asking about.

Plotted against a clock rather than listed, the same eighteen rows look like this.

07:41 08:52 14:07 14:31 boot 10 processes logon and apps 7 processes cmd.exe 1 process, alone five hours in which the machine created nothing at all

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.

04

windows.pstree, the Same Data Shaped as Descent

Where a missing parent becomes visible

windows.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.

explorer.exe has no parent in the tree. What does that most likely mean?pick one, then the tell

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.

05

Narrowing With --pid, and What Comes Next

Plugin options, and the three commands you run after pslist

Up 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.

vol -o ./out -f IMAGE windows.pslist --pid 3312 --dump how Volatility runs always BEFORE the plugin the plugin what the plugin does always AFTER the plugin

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.

06

What pslist Cannot Tell You

Four gaps, and which command closes each

A 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.

Sort each question by whether pslist answers ittap a question, then a bucket
pslist answers itanother plugin does

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.

07

The Six Commands as a Sequence

What you type, in order, on any image

Everything 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.

08

The Same Image as a Drive Letter

MemProcFS, and when browsing beats a plugin

Volatility 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.

09

Practice

Install Volatility and run the sequence
hands on
Do this Get the tool working and run all six commands
  1. Install both tools: Volatility 3 from the project's repository, and MemProcFS from its releases page. Confirm each runs before going further.
  2. Confirm Volatility's own help works: vol -h should list the global options this sub used, and vol -f x windows.info -h should show the plugin's own. Knowing which options belong where is most of using the tool.
  3. 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.
  4. Run windows.info first, every time. Note the architecture, the DTB and the system time, and compare that time against when you took the capture.
  5. 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.
  6. Run windows.pstree and 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.
  7. Pick one process and run windows.handles --pid and windows.dlllist --pid against 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.
What you should end up with: Volatility 3 installed and running, all six commands executed against a real image, a process list sorted by creation time with its clusters identified, and every unparented process in the tree explained. That is the working loop for the rest of the course.

Extend it

Once the six commands run, these are the variations worth trying.

  1. Run the same plugin with -r quick, -r pretty and -r json and see what each is good for. The json output is what you would parse if you were automating any of this.
  2. Run windows.pslist --dump --pid with -o pointing 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.
  3. Time windows.info on a large image. It is slower than you expect because Volatility is scanning to find the kernel, which is why the --save-config option exists.
  4. 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.