notes / writeup

Beautiful Memory Forensics Writeup

We were given a Windows memory dump named chall.dmp and several Volatility 3 outputs:

May 28, 2026 writeup

Challenge Summary

We were given a Windows memory dump named chall.dmp and several Volatility 3 outputs:

  • vol_info.txt
  • vol_pslist.txt
  • vol_cmdline.txt
  • vol_netscan.txt
  • vol_filescan.txt
  • vol_deskscan.txt
  • vol_windows.txt
  • vol_cmdscan.txt
  • vol_consoles.txt
  • vol_vadregex.txt
  • vol_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.

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_?}