Heap of Secrets - Vishwa CTF 2026
tl;dr
The challenge hint said the flag was already in the browser and suggested taking a snapshot. The frontend JavaScript allocates session objects in memory and stores a decoded token in one of them. That token is the flag.
Challenge points: 500
Recon
Loading the homepage source showed a large inline script. The relevant part:
initSession()callsfetch("/api/init")- The response contains
session_seedandtrace_vector - The code decodes the token with XOR:
const decoded = data.trace_vector.map(b => b ^ key);
const token = decoded.map(c => String.fromCharCode(c)).join("");
- The decoded value is stored in
session.license_token
This matches the hint: the secret is not rendered directly, but it is allocated in browser-side objects and would appear in a heap snapshot.
Relevant JavaScript
const res = await fetch("/api/init");
const data = await res.json();
const key = data.session_seed;
const decoded = data.trace_vector.map(b => b ^ key);
const token = decoded.map(c => String.fromCharCode(c)).join("");
session.license_token = token;
API Response
Request: GET /api/init
Response:
{
"ts": 1775226941,
"node": "edge-7",
"session_seed": 56,
"trace_vector": [110,81,75,80,79,89,123,108,126,67,80,11,12,72,103,13,86,12,72,13,80,8,76,103,9,75,103,124,11,93,72,103,117,11,85,8,74,65,69],
"build": "prod-eu-west-3",
"version": "2.4.1"
}
Solve
XOR each element of trace_vector with session_seed = 56, then convert the resulting bytes to characters.
trace = [110,81,75,80,79,89,123,108,126,67,80,11,12,72,103,13,86,12,72,13,80,8,76,103,9,75,103,124,11,93,72,103,117,11,85,8,74,65,69]
key = 56
print(''.join(chr(b ^ key) for b in trace))
Output:
VishwaCTF{h34p_5n4p5h0t_1s_D3ep_M3m0ry}
Verification
Submitting the decoded value to /api/submit returned:
{"ok": true, "msg": "🎉 Correct! Flag accepted."}
Flag
VishwaCTF{h34p_5n4p5h0t_1s_D3ep_M3m0ry}