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
Memory Forensics Triage Exercise: Work Volatility Output With No Indicator to Search For
Introduction
Ten subs have taught the plugins one at a time and the order to run them in. This one hands you output you have not seen, from a machine you know nothing about, with no indicator to search for. That is the condition every real triage starts from, and it is the one thing a walkthrough cannot rehearse.
The exercise below is the module's six questions worked against one Windows server. Nothing in it requires a plugin this module has not covered, and nothing in it can be answered by recognizing a name: every process on the page is a legitimate Windows binary.
Scenario
NE-SRV-MCR-04 is a web server. The firewall flagged outbound traffic from it at 09:10 on 12 March and somebody captured memory two minutes later. You have the output of five commands and nothing else: no alert detail, no process name, no hash, no indicator. The machine runs a database and an IIS worker, both of which are supposed to be there, and roughly sixty other processes that are also supposed to be there. Find what is wrong with it.
Before You Start, the Order
What you already know, and what it saves youResist reading the output top to bottom. It is arranged in the order the commands were run, not in the order of usefulness, and working through it linearly means forming opinions about a process list before you know whether the image is readable.
Work the six questions in sequence instead, in the order the module taught them: can I read this, what was running, what ran and is gone, what is inside it, what does it have open, what was it talking to. The order is what makes an unfamiliar image tractable at all, and improvising through it is what makes triage feel slow and inconclusive.
The first question is whether the image is readable at all, and the windows.info output at the top answers it: a build, a kernel base and a system time. If that had been empty, MF0.6 applies and nothing below it would mean anything. It costs one glance and it rules out a whole category of wasted afternoon.
The second and third questions are the survey. Sort the process list by creation time and find what sits outside the clusters; then compare the scan against the list and read the exit times. Both are mechanical, neither requires judgment about attackers, and between them they should hand you a name.
Both survey questions are cheap, and that is the argument for running them before forming any opinion. Neither needs a hypothesis, neither can be biased by one, and on a machine where nothing is wrong they cost a few minutes and produce a defensible statement that nothing is anomalous by its metadata.
Only then do the narrowing questions apply. malfind, handles and netscan each need a target or benefit from having one, which is why they come after rather than before, and why arriving at them with a process in mind turns hundreds of rows into a short check.
- The gate. One glance, and everything below it is meaningless without it.
- The survey. One sort on one column. This is where most investigations get their name.
- The blind spot. The difference between the two listings, and ExitTime resolves most of it.
- First narrowing. Run it against the target the survey gave you.
- Second narrowing. Needs a target, which is why it is not first.
- The outside. Run whole, read against the names you now hold.
There is one more thing worth fixing in your head before you begin. An unremarkable survey is not a result. It establishes that no process is anomalous by its own metadata and says nothing about what is inside any of them, so if the first three questions come back quiet, the answer is to keep going rather than to write the machine off as clean.
The Exercise
Six questions against one serverThe exercise is below. It holds the output of five commands against one server, about thirty-five lines in total, and every question is answerable from what is on the page without running anything.
Read the output in full before answering anything, including the parts that look routine. The six questions below follow the module's order, and each one is answerable from what is on the page.
Give yourself the survey questions before looking at the later commands. The temptation with a scrollable artifact is to read the malfind output at the bottom first, because it is the one that looks like a finding, and doing that skips the reasoning that made it worth running.
Answer in your own words rather than matching phrases. The check looks for the concepts rather than the wording, and an answer that names the right structure for the wrong reason will read as correct and teach you nothing, which is the one failure mode a self-marked exercise has.
There is no time limit and no scoring beyond the concept check. The value is in the order you work the questions rather than in the answers, and somebody who reaches the right conclusion from the wrong structure has learned the less useful half of this module.
The reset control in the header clears an attempt, and using it is worth more than reading the model answer when a question goes badly. An exercise you have worked twice teaches more than one you have read the answer to once.
If a question defeats you, go back to the sub it came from rather than to the model answer. Each of the six maps onto one earlier sub, in order, and the mapping is exact: the survey questions to MF0.1 and MF0.2, the memory question to MF0.3, the handles question to MF0.4, and the connection to MF0.5.
What the Survey Was Supposed to Give You
Two processes, isolated by a sortWork through what the first three questions should have produced, and check it against what you wrote rather than reading it as a walkthrough.
The machine booted at 22:04 on 11 March and was captured at 09:12 the next morning. Almost every process on it was created within about forty seconds of boot, which is the ordinary shape MF0.1 described: a cluster at boot, a cluster at logon on a workstation, and whatever sits outside both.
PS> Import-Csv .\ps.csv | Sort-Object CreateTime |
>> Select-Object CreateTime, PID, ImageFileName -Last 4
CreateTime PID ImageFileName
2026-03-11 22:04:41 1520 sqlservr.exe
2026-03-11 22:05:02 2288 w3wp.exe
2026-03-12 09:08:17 6104 cmd.exe
2026-03-12 09:08:29 6212 whoami.exe
Sorted ascending and tailed, the machine's whole day fits in four rows. Two processes on 12 March and nothing else for eleven hours. The gap is the finding, and producing it took one sort on one column, with no knowledge of this machine, this estate or any attacker. On a server that has been up overnight the boot cluster is dense and everything after it is sparse, which makes the shape easy to read once you have seen it twice.
It is worth being precise about what that sort actually required. Not a list of suspicious process names, not threat intelligence, not knowledge of this estate: one column, ascending, and the ability to notice a gap. That is the cheapest useful operation in this entire module and it is the one most often skipped in favor of scanning the names.
It is also worth noting what the four rows do not contain: no process name that would appear on any indicator list, nothing misspelled, nothing running from a temporary directory. Everything visible here is a legitimate Windows binary in a legitimate location.
Notice what the sort does not tell you. A command prompt on a server is ordinary, and whoami is a command administrators run constantly. Neither is suspicious in isolation. If you stopped at this point you would have two entirely unremarkable processes, no reason to look further, and a defensible-sounding note saying nothing anomalous was found in the process list.
The Parent Is the Finding
Reading one column acrossReading down a single column found the two processes. Reading across one row finds the reason they matter.
The PPID of the command prompt is 2288, and 2288 is w3wp.exe, the IIS worker process. That single relationship is what turns two ordinary processes into an investigation.
A web worker process has no ordinary reason to start a shell at all. It serves requests, and serving a request does not involve spawning cmd.exe, so a shell parented by one is either an administrator doing something unusual on a production server, or a request that reached code it should never have reached. Either needs explaining, and neither is routine enough to leave in a note as accounted for.
Note what it took to see this. Not a rule, not a detection, not a list: reading the parent column of a row you were already looking at, and knowing what a web worker does for a living.
The distinction between the two is worth holding onto because it decides what goes in a report. A shell on a server is a fact about the machine; a shell parented by a web worker is a fact about how that shell came to exist, and only the second one is worth somebody's morning.
This is the same judgment MF0.1 asked for about explorer.exe and MF0.3 asked for about which programs generate code at run time. It is knowledge of what ordinary software does rather than knowledge of attackers, and it is the recurring skill of this whole module.
The third option in that aid is worth a word, because it looks odd and is not. Running net on a modern Windows system starts net1.exe to do the actual work, so a pair a second apart is the normal shape of one command rather than evidence of two. Somebody who did not know that would report a duplicate execution, which is the kind of error that comes from pattern-matching rather than from knowing the platform.
What the Scan Recovered
Two commands that had already finishedwindows.psscan returned net.exe and net1.exe, both carrying an exit time one second after their creation time, and windows.pslist returned neither of them. That difference is exactly the diff MF0.2 was built around, and it took two listings and one comparison to produce.
Question three is the one people expect least from, because a listing that mostly duplicates the one before it looks like wasted effort. On this image it is where two of the four things the operator did come from.
MF0.2's first check explains it completely. A populated exit time means the process ended, Windows unlinked it from the list the moment it did, and the structure survived in memory because nothing had needed that memory yet. Nothing about either row was hidden, and reporting them as though they had been would be wrong.
Both rows also sit inside the four-minute window the shell was alive, and their parent is 6104, so they attach directly to the process the survey had already found rather than floating free in the output. That is what makes them worth reading rather than noise.
The ordinary explanation is still a useful result, and this is the clearest example of it in the module. Those two rows recover commands that had already finished before anybody captured anything, both of them children of the same shell, which tells you what was done in the four minutes between the shell starting and the capture. pslist alone would have shown you a shell and left you guessing about what was typed into it.
the shell at 09:08:17, and its children ...
09:08:17 cmd.exe started, parent w3wp.exe
09:08:29 whoami which account am I
09:08:51 net and what can it reach
# twelve seconds apart. orientation, not activity.
Write that distinction into the answer rather than leaving it implicit. The structures establish that three named processes ran in a particular order at particular times; the reading of that order as orientation is yours, and labeling it as an inference is what stops a reader treating it as evidence.
The Column That Answered the Alert
Two established connections, one of them the jobEverything up to this point came from the host's own bookkeeping about processes and nothing else. This question is the one that connects the machine to the alert that started the whole thing.
The capture happened because a firewall flagged outbound traffic, and the firewall could name an address and could not name a program. windows.netscan closes exactly that gap.
Two connections are established, and telling the two apart takes no threat intelligence whatsoever. The database holds one to an internal address on its own service port, which is a database doing exactly what a database does and needs no further thought. The web worker holds one to an external address on 8443, and the web worker is the same process the shell was parented by. A web server making an outbound connection to the internet is not automatically wrong, which is why the corroboration matters more than the row on its own.
PS> Import-Csv .\net.csv | Where-Object State -eq 'ESTABLISHED' |
>> Format-Table Owner, PID, ForeignAddr, FPort
Owner PID ForeignAddr FPort
w3wp.exe 2288 185.220.101.7 8443
sqlservr.exe 1520 10.5.2.51 50113
That one row is the whole argument for having captured memory at all. The firewall knew an address and a volume. Only the host knew which of sixty-odd processes produced the traffic, and the answer is the same process the survey had already made interesting for an unrelated reason.
It is also worth noticing which question answered the alert, because it was not the first one run. The survey found the process for reasons that had nothing to do with the network, and the network row arrived afterwards and agreed with it. Had the alert been wrong, or had somebody captured this machine at random, the survey would have found the same process anyway.
Two independent structures now point at 2288: a parent relationship in the process list and an owner field in the network structures. Neither structure was designed to corroborate the other, and neither knows the other exists. That independence is what gives an accumulation its weight, and this is what it looks like in practice.
Writing It Up, and What Is Missing
Three sentences, and the gap you name yourselfOne structure remains, and it is the one that says what is actually inside the process everything else has been pointing at.
malfind supplies it: one page of executable private memory in 2288 with no file behind it, beginning fc 48 83 e4 f0. Those bytes align the stack and begin position-independent code, which is a recognizable shape rather than an identification. Naming a specific tool or family from five bytes would be going well beyond what the output supports, and it is exactly the kind of claim this course keeps warning about.
There is a decision buried in that paragraph worth making explicit, because it is the one an analyst gets wrong under pressure. The five bytes are the strongest-looking thing on the whole page, and they support the weakest claim: a shape. The parent relationship and the owner column, which look mundane, support claims that are precise and checkable. The temptation is always to lead a report with the exotic evidence, and the exotic evidence is usually the part that will not survive a question.
With that, five structures across five plugins all describe process 2288, and the write-up is a matter of assembling them honestly rather than deciding anything further.
The finding goes into three sentences, and the second is the one that matters.
- What the evidence records. At 09:12 on 12 March, w3wp.exe (PID 2288) held unbacked executable memory at 0x1d40000, owned an established connection to 185.220.101.7:8443, and was the parent of a shell created at 09:08:17 that ran whoami and net.
- What it does not establish. Nothing here shows that code executed from that region, what crossed the connection, how much data moved, or how the machine was reached in the first place.
- What would close the gap. Proxy and firewall logs for that address and port across 09:08 to 09:12 answer the volume question; IIS request logs for the minutes before 09:08 are where the initial access would appear.
Those three sentences keep the same shape regardless of what the image held. The first is checkable by anybody with the file, the second removes the conclusions a reader would otherwise supply, and the third turns both into work somebody can pick up.
Read the second sentence again and notice that it is longer than the first. That is the normal shape of an honest finding early in an investigation, and a report whose second sentence is shorter than its first is usually claiming more than it holds.
Notice what the write-up does not say. It does not say the server was compromised through a web application vulnerability, which is the obvious reading and is an inference about a request nobody has looked at yet.
Practice
Do it again on something you made hands on- On the lab from MF0.7, start a program from an unexpected parent: a shell from a service, or anything from a process that has no business launching things. Capture, and find the parent relationship yourself.
- Run a command that exits immediately, capture a minute later, and confirm it appears in psscan with an exit time and not in pslist. That is the module's second question, on evidence you produced.
- Open a connection from that same process to your second machine and find it in netscan with your process in the Owner column.
- Now hand the image to somebody without telling them what you did, and watch which of the six questions finds it first. It is usually the sort on creation time, and watching that happen is worth more than being told.
- Write the three-sentence finding for your own image, including the second sentence. Check it against what you actually did, which is the only time you will ever be able to.
Extend it
These turn one worked exercise into a repeatable way of starting.
- Time yourself through the six questions on this output. Knowing it takes minutes rather than hours is what makes you willing to run them on a machine you only half suspect.
- Work the same output in the wrong order, starting with handles on every process. Notice how much longer it takes to reach the same place, which is the argument for the order rather than an assertion of it.
- Take the finding above and write the strongest challenge to it you can. Anything that survives your own challenge will survive somebody else's.
- Write the six commands into one script that takes an image path and produces six CSV files, then run it against this image. The survey should cost one command and produce the files the rest of the work is done on.
Drill one is worth doing with a clock. Most people are surprised by how short the sequence is once it is familiar, and the reason that matters is not efficiency: an analyst who believes triage takes an afternoon will not run it on a machine they only half suspect, and half-suspicion is where most of these start.
Drill two is the one that teaches the order rather than telling you about it. Starting with the handle table on sixty processes produces several thousand rows and no way to rank them, and arriving at the same conclusion the long way is how the reason for the sequence stops being something you were told.
You have worked an unfamiliar image with no indicator to search for, isolated two processes by sorting one column, found the finding in a parent relationship rather than a process name, recovered two exited commands the process list could not show, used the one column no network source holds, and written a finding whose second sentence names what it does not establish. That is the module. The paid modules take each of these plugins to depth, starting with acquisition and the structures the kernel maintains.