notes / writeup
Beautiful Memory Forensics Writeup
We were given a Windows memory dump named chall.dmp and several Volatility 3 outputs:
Challenge Summary
We were given a Windows memory dump named chall.dmp and several Volatility 3 outputs:
vol_info.txtvol_pslist.txtvol_cmdline.txtvol_netscan.txtvol_filescan.txtvol_deskscan.txtvol_windows.txtvol_cmdscan.txtvol_consoles.txtvol_vadregex.txtvol_vad_flag.txt
The goal was to recover the CTF flag from memory.
Final flag:
BKISC{W3ll_M3mory_is_Str0nk_right_?}
0. Generating Volatility 3 Outputs
Environment Setup
First, I set up a Python virtual environment and installed Volatility 3:
# Configure Python environment in the challenge directory
cd "/Users/thinh/Documents/bkics/Beatiful Memory"
python3 -m venv .venv
# Activate and install Volatility 3
source .venv/bin/activate
pip install volatility3
The Volatility 3 CLI became available at:
.venv/bin/vol
Generating Analysis Files
I extracted various memory artifacts from the dump using different Volatility 3 plugins:
1. System Information
.venv/bin/vol -f "chall.dmp" windows.info > vol_info.txt
This provided kernel base address, OS version, system time at acquisition, and symbol information.
2. Process List
.venv/bin/vol -f "chall.dmp" windows.pslist > vol_pslist.txt
This identified all running processes, their PIDs, creation times, exit times, and parent-child relationships. Critical for identifying which applications were active near acquisition time.
3. Command Line Arguments
.venv/bin/vol -f "chall.dmp" windows.cmdline > vol_cmdline.txt
This extracted command-line arguments for each process, revealing application paths, startup parameters, and user context (C:\Users\supadupadev\).
4. File Scan
.venv/bin/vol -f "chall.dmp" windows.filescan > vol_filescan.txt
This scanned memory for FILE_OBJECT structures and listed all files that were open or cached in memory at the time of the dump.
5. Console History and Commands
.venv/bin/vol -f "chall.dmp" windows.consoles > vol_consoles.txt
.venv/bin/vol -f "chall.dmp" windows.cmdscan > vol_cmdscan.txt
These extracted console window history and command buffer data, useful for recovering user-executed commands.
6. Network Connections
.venv/bin/vol -f "chall.dmp" windows.netscan > vol_netscan.txt
This listed established TCP/UDP connections, providing evidence of what network resources the system was communicating with.
7. Desktop and Window Analysis
.venv/bin/vol -f "chall.dmp" windows.deskscan.DeskScan > vol_deskscan.txt
.venv/bin/vol -f "chall.dmp" windows.windows.Windows > vol_windows.txt
These attempted to recover desktop and window title information (though output was minimal in this dump).
8. Memory String Scanning with Regex
.venv/bin/vol -f "chall.dmp" windows.vadregexscan --pid 10980 2112 5028 6676 2016 10924 4208 7632 5904 5008 \
--pattern '(?i)flag\{[^}]{0,100}\}|ctf\{[^}]{0,100}\}|bkics\{[^}]{0,100}\}' \
--maxsize 200 > vol_vadregex.txt
.venv/bin/vol -f "chall.dmp" windows.vadregexscan --pid 10980 2112 5028 6676 2016 10924 4208 7632 5904 5008 \
--pattern '(?i)flag[^\r\n]{0,80}' \
--maxsize 120 > vol_vad_flag.txt
These scanned the virtual address space of Edge renderer processes for strings matching CTF flag patterns.
Processing the Outputs
After generating these files, I used standard Unix tools to search and analyze them:
# Search for keywords in all Volatility outputs
grep -i "flag\|ctf\|password\|token\|download" vol_*.txt
# Extract specific sections
sed -n '1,260p' vol_pslist.txt
sed -n '1,220p' vol_info.txt
1. Basic Memory Image Information
First, I checked the OS profile and acquisition time from vol_info.txt.
Command:
sed -n '1,220p' vol_info.txt
Important evidence:
Volatility 3 Framework 2.28.0
Kernel Base 0xf80528000000
Is64Bit True
Major/Minor 15.19041
SystemTime 2026-05-06 08:14:34+00:00
NtSystemRoot C:\WINDOWS
NtProductType NtProductWinNt
This shows the memory image is a 64-bit Windows 10 dump. The system time at acquisition was 2026-05-06 08:14:34 UTC.
2. Process Triage
I reviewed the process list to identify recently active user applications.
Command:
sed -n '1,260p' vol_pslist.txt
Important evidence:
4660 explorer.exe ... 2026-05-06 07:42:58.000000 UTC
336 msedge.exe ... 2026-05-06 08:13:07.000000 UTC
5220 msedge.exe ... 2026-05-06 08:13:07.000000 UTC
9304 msedge.exe ... 2026-05-06 08:13:07.000000 UTC
...
8324 DumpIt.exe ... 2026-05-06 08:14:31.000000 UTC
7024 conhost.exe ... 2026-05-06 08:14:31.000000 UTC
The dump was collected with DumpIt.exe at 08:14:31 UTC. Microsoft Edge was active immediately before acquisition, so browser memory and browser cache became the main investigation path.
3. Command Line Review
Next, I checked process command lines.
Command:
sed -n '1,260p' vol_cmdline.txt
Important evidence:
336 msedge.exe "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --no-startup-window
5220 msedge.exe "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --type=crashpad-handler "--user-data-dir=C:\Users\supadupadev\AppData\Local\Microsoft\Edge\User Data"
9304 msedge.exe "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --type=utility --utility-sub-type=network.mojom.NetworkService
10980 msedge.exe "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --type=renderer ...
2112 msedge.exe "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --type=renderer ...
5028 msedge.exe "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --type=renderer ...
This confirms the active user profile:
C:\Users\supadupadev\AppData\Local\Microsoft\Edge\User Data
The presence of multiple Edge renderer processes suggested that interesting data could be in browser form state, tab state, cache, or renderer memory.
4. Network Review
I also checked active network connections to understand user activity near acquisition time.
Command:
sed -n '1,260p' vol_netscan.txt
Important evidence:
192.168.254.135:55337 -> 202.89.233.101:443 ESTABLISHED 9304 msedge.exe 2026-05-06 08:13:10 UTC
192.168.254.135:55389 -> 74.125.68.113:443 ESTABLISHED 9304 msedge.exe 2026-05-06 08:13:53 UTC
192.168.254.135:55451 -> 35.190.80.1:443 ESTABLISHED 9304 msedge.exe 2026-05-06 08:14:12 UTC
192.168.254.135:55454 -> 34.102.146.192:443 ESTABLISHED 9304 msedge.exe 2026-05-06 08:14:56 UTC
Most interesting live network activity belonged to Edge around the acquisition window.
5. Initial Keyword Search in Volatility Outputs
I searched the generated Volatility text files for obvious flag strings and common CTF keywords.
Command:
rg -n -i "flag|ctf|password|secret|token|key|download|powershell|cmd|chrome|firefox|brave|edge|winrar|zip|7z|rar|txt|png|jpg|doc|pdf|temp|desktop|downloads" \
vol_filescan.txt vol_cmdline.txt vol_pslist.txt vol_deskscan.txt vol_windows.txt vol_cmdscan.txt vol_consoles.txt vol_vadregex.txt vol_vad_flag.txt
The file vol_vadregex.txt was empty:
Volatility 3 Framework 2.28.0
PID Process Offset Text Hex
The file vol_vad_flag.txt contained many strings from Edge internals, but they were not CTF flags. For example:
10980 msedge.exe ... flags --device-scale-factor=1.25 --num-raster-threads=1 ...
10980 msedge.exe ... FeatureFlagRefactorExperiment ...
10980 msedge.exe ... X-Edge-Shopping-Flag ...
These were browser feature-flag strings, not the challenge flag.
6. Direct Raw Memory Search for ASCII Flags
Since the provided Volatility outputs did not reveal the final flag, I searched the raw dump directly.
Command:
rg -a -b -o "BKISC\{[^}\r\n]{1,120}\}" chall.dmp
Output:
664480200:BKISC{Dunno_whut_to_say_T^T_Whut_r_u_doing_here?}
2475288864:BKISC{Woah_woah_u_know_sst1?}
This found two ASCII BKISC{...} strings. At this stage, both were candidates, but neither was trusted yet because CTF memory challenges often include decoys.
7. Context Around the ASCII Candidates
I extracted printable context around the two offsets.
Command:
.venv/bin/python -c 'import string
data=open("chall.dmp","rb")
for off in (664480200,2475288864):
data.seek(max(0,off-1200))
b=data.read(2400)
s="".join(chr(c) if (32<=c<127 or c in (9,10,13)) else "." for c in b)
print("\n===== offset",off,"=====")
idx=s.find("BKISC")
print(s[max(0,idx-700):idx+900])
'
Evidence for the first candidate:
l.i.n.k...t.x.t...t.x.t
L.I.N.K.T.X.~.1...T.X.T
BKISC{Dunno_whut_to_say_T^T_Whut_r_u_doing_here?}
Evidence for the second candidate:
X.9.V.B...t.x.t
BKISC{Woah_woah_u_know_sst1?}
Both strings appeared in cached file records. The second one was especially interesting because the cache filename was X9VB.txt.
8. Browser Cache Metadata for X9VB.txt
I searched for X9VB.txt, flag.txt, and related browser cache metadata.
Command:
.venv/bin/python -c 'import re
b=open("chall.dmp","rb").read()
for pat in [b"X9VB.txt", b"flag.txt", b"file:///c%3A/Users/supadupadev/Desktop/my-web/flag.txt"]:
print("\nPATTERN", pat.decode("latin1"))
c=0
for m in re.finditer(re.escape(pat), b):
off=m.start()
s=b[max(0,off-300):off+500]
txt="".join(chr(x) if 32<=x<127 else "." for x in s)
print("--",off,"--")
print(txt)
c+=1
if c>=3:
break
'
Important evidence:
{"version":1,"resource":"file:///c%3A/Users/supadupadev/Desktop/my-web/flag.txt","entries":[{"id":"X9VB.txt","timestamp":1774595352184}]}
This showed that X9VB.txt was a cache entry for:
file:///c:/Users/supadupadev/Desktop/my-web/flag.txt
The cached file contained:
BKISC{Woah_woah_u_know_sst1?}
However, this still looked like a hint or intermediate decoy because the text asks whether we know sst1, instead of looking like a final memory-forensics flag.
9. UTF-16LE Search
Because Windows browser form state often stores strings as UTF-16LE, I searched for BKISC{ encoded in UTF-16LE.
Command:
.venv/bin/python -c 'import re
b=open("chall.dmp","rb").read()
print("ASCII matches:")
for m in re.finditer(rb"BKISC\{[^}\r\n]{1,200}\}", b):
print(m.start(), m.group().decode("latin1"))
needle="BKISC{".encode("utf-16le")
print("UTF16LE matches:")
pos=0
found=0
while True:
i=b.find(needle,pos)
if i<0:
break
raw=b[i:i+500]
s=raw.decode("utf-16le", errors="ignore")
end=s.find("}")
print(i, s[:end+1] if end!=-1 else s[:120])
found+=1
pos=i+2
print("utf16_count",found)
'
Important output:
ASCII matches:
664480200 BKISC{Dunno_whut_to_say_T^T_Whut_r_u_doing_here?}
2475288864 BKISC{Woah_woah_u_know_sst1?}
UTF16LE matches:
596843296 BKISC{flag_is_here}
636237600 BKISC{flag_is_here}
...
1168042914 BKISC{W3ll_M3mory_is_Str0nk_right_?}
...
The repeated BKISC{flag_is_here} values looked like placeholders. The important unique candidate was:
BKISC{W3ll_M3mory_is_Str0nk_right_?}
10. Context Around the Final UTF-16LE Candidate
I extracted context around offset 1168042914.
Command:
.venv/bin/python -c 'b=open("chall.dmp","rb").read()
off=1168042914
s=b[max(0,off-1600):off+1600]
u=s.decode("utf-16le", errors="ignore")
clean="".join(ch if (32<=ord(ch)<127 or ch in "\n\r\t") else "." for ch in u)
idx=clean.find("BKISC")
print(clean[max(0,idx-700):idx+900])
'
Important evidence:
PostForm[password] text
kj8DFBPfgw6767!
PostForm[text] textarea
BKISC{W3ll_M3mory_is_Str0nk_right_?}
PostForm[is_burn] checkbox
off
PostForm[name] text
flag
PostForm[expiration] select-one
Additional nearby context showed Pastebin-related form state:
https://pastebin.com/
[_csrf-frontend PostForm[text]]
This is the strongest evidence in the investigation. The flag was stored in browser serialized form state as the value of PostForm[text], while the form name was flag.
11. Decoy Elimination
Several BKISC{...} strings were present:
BKISC{Dunno_whut_to_say_T^T_Whut_r_u_doing_here?}
BKISC{Woah_woah_u_know_sst1?}
BKISC{flag_is_here}
BKISC{W3ll_M3mory_is_Str0nk_right_?}
The first two were found in cached file records:
link.txt.txt -> BKISC{Dunno_whut_to_say_T^T_Whut_r_u_doing_here?}
X9VB.txt -> BKISC{Woah_woah_u_know_sst1?}
The X9VB.txt cache entry mapped to:
file:///c:/Users/supadupadev/Desktop/my-web/flag.txt
That made it useful evidence, but not the final answer. The final flag had better context because it was directly stored as the value of a browser form field:
PostForm[text] = BKISC{W3ll_M3mory_is_Str0nk_right_?}
PostForm[name] = flag
Therefore, the final flag is:
BKISC{W3ll_M3mory_is_Str0nk_right_?}
Conclusion
The challenge was solved by moving from normal Volatility triage to raw memory string analysis. The important clue was that the final flag was not visible as a normal ASCII string in the existing Volatility outputs. It was stored in UTF-16LE inside Microsoft Edge/Blink serialized form state.
Final answer:
BKISC{W3ll_M3mory_is_Str0nk_right_?}