A malware analysis challenge focused on examining a user-mode process dump using WinDbg. The objective is to navigate process structures, inspect loaded modules, analyze memory regions within the dump, identify injected shellcode, and extract indicators that reveal the threat actor’s activity.
A user ran a dropper from their Downloads folder. It stayed small, with only a handful of threads, and opened a named pipe for inter-process communication. It then injected shellcode into a notepad process by allocating a private read-write-execute region and starting a thread in it. That shellcode is a Cobalt Strike beacon, identifiable by its embedded beacon DLL, post-exploitation pipe names and HTTP check-in URLs, and it beacons to a hard-coded C2 address. Everything came out of two minidumps, using a small structure parser for the stream data and strings for the memory contents.
evidence/update.DMP → user-mode minidump of the malicious executable. Open with WinDbg, or parse the streams directlyevidence/notepad.DMP → user-mode minidump of the injected notepad processTooling note: No WinDbg or minidump library was available on the analysis box, so the streams (SystemInfo, ModuleList, ThreadList, MiscInfo, ThreadInfoList, MemoryInfoList) were read with a short struct-based Python parser. The same data is one
lm,~or!addressaway in WinDbg.
The SystemInfo stream of the dump holds the OS build the process ran on. Read the major, minor and build numbers there; the ntdll.dll file version in the module list tells the same story and gives the revision field the format mask asks for.
python3 -I tools/md.py evidence/update.DMP
The module list of update.DMP starts with the main image. It sits in a user profile’s Downloads folder, not a system path, and its name is built to look like a routine updater.
python3 -I tools/md.py evidence/update.DMP | grep -i mod
The ThreadList stream holds one record per thread alive at dump time. Count them for update.DMP.
python3 -I tools/md.py evidence/update.DMP | grep -c '^thr'
Pipe names are plain strings in process memory. Searching the dump for the \\.\pipe\ prefix surfaces one custom name that does not belong to Windows. It is shaped like a Microsoft Security Essentials pipe to blend in.
strings -a -el evidence/update.DMP | grep -i 'pipe'
The second dump is a notepad process, which has no business holding an executable private region. Its PID is in the MiscInfo stream, already in decimal.
python3 -I tools/md.py evidence/notepad.DMP
The ThreadInfoList stream carries a creation time for every thread. Sort the notepad threads by it and take the newest, thread 720, created at 01:09:12.931. Drop the milliseconds to match the format mask.
python3 -I tools/md.py evidence/notepad.DMP | grep '^ti'
The MemoryInfoList stream lists region protections. Filter notepad.DMP for private (non-image, non-mapped) regions that are executable. The small RWX page at 0xb120870000 is the shellcode; WinDbg’s !address shows it as b120870000`, with the backtick the mask expects.
python3 -I tools/md.py evidence/notepad.DMP | grep '^reg'
Note: This pick rests on the region being the only small private RWX allocation, which fits an injected stage rather than anything notepad allocates itself. It was reasoned from the region list rather than read from a labelled artifact.
Beacon configuration and its HTTP request paths are readable in the shellcode’s memory. Search for URLs and the address that the check-in paths point at.
strings -a evidence/notepad.DMP | grep -iE 'https?://|submit.php'
Three markers agree: a beacon.x64.dll reflective payload name, postex_ pipe names used for post-exploitation jobs, and submit.php?id= as the beacon’s result-posting URL. All three are Cobalt Strike defaults.
strings -a evidence/notepad.DMP | grep -iE 'beacon|postex_|submit.php'
| Phase | Action |
|---|---|
| Execution | update.exe runs from the user’s Downloads folder |
| Command and Control | Process opens the named pipe MSSE-1641-server |
| Defense Evasion | Shellcode is placed in a private RWX region in notepad.exe (PID 2336) and a thread is started in it |
| Command and Control | Cobalt Strike beacon checks in over HTTP to the C2 address |
| Type | Value |
|---|---|
| File | C:\Users\s1rx\Downloads\update.exe |
| Named pipe | \\.\pipe\MSSE-1641-server |
| IP (C2) | 101[.]10[.]25[.]4 |
| Memory region | 0xb120870000 in notepad.exe (PID 2336) |
| URL path | submit.php?id= |
| Technique | ID | Description |
|---|---|---|
| User Execution: Malicious File | T1204.002 | update.exe run from Downloads |
| Process Injection | T1055 | Shellcode in a private RWX region inside notepad.exe |
| Inter-Process Communication | T1559 | Custom named pipe used by the dropper and Cobalt Strike post-ex pipes |
| Application Layer Protocol: Web Protocols | T1071.001 | HTTP beaconing to the C2 |
A notepad process with an executable private region is injected. Notepad has no reason to hold a small private RWX allocation. Scanning memory for private executable regions in processes that should not JIT, or alerting on VirtualAllocEx plus CreateRemoteThread into a different process, catches this stage.
Pipe names that imitate security products are still pipe names. A MSSE-<digits>-server pipe is a known Cobalt Strike default. Sysmon pipe events (17 and 18) matched against a list of default C2 pipe patterns, including postex_, flag the beacon without any payload analysis.
Strings in a process dump are enough to name the framework. The beacon DLL name, post-exploitation pipes and default URL paths survived in memory unencrypted. Capturing a dump of a suspicious process early gives responders the C2 address and family within minutes.
A fresh executable named update.exe in Downloads should not run. Application control that limits execution from user-writable paths would have blocked the first step.
A downloaded fake updater was enough to land a Cobalt Strike beacon inside notepad. The process dumps held everything needed to name the family and the C2 without running anything, because the beacon’s defaults were never hidden in memory.