algorave
algorave
Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: base64_decoding, memoization_fix, recursive_function_analysis, visual_flag_extraction
Summary
Walkthrough of the CUSTOM Reversing algorave. Task: extract a flag from a Strudel.cc live coding music challenge with browser-crashing recursive JavaScript. Solution: add memoization and depth limiting to the recursive signal function, run the fi
Description
The challenge provided a link to an HTML file that redirected to strudel.cc (a live coding music platform). The HTML contained base64-encoded JavaScript code that generated music using the Strudel library.
Challenge URL: https://2025.ctf.link/assets/files/algorave-c649c74aa5785a46.html
Analysis
Initial Investigation
The HTML file contained a meta refresh redirect to strudel.cc with base64-encoded JavaScript in the URL fragment:
html read-only • copy allowed<meta http-equiv="refresh" content="0; url=https://strudel.cc/#base64_encoded_code_here">
Code Structure
After decoding the base64, the JavaScript code revealed:
- A large
notesarray containing musical note patterns likenote("[c4,f#4]"),note("[c4,c#4,d4,d#4,e4,f4,f#4]"), etc. - A recursive function
f(k)inside asignal()call that computed indices into the notes array - A
pianorollvisualization component
The Problem
The original recursive code caused browser hangs due to infinite/deep recursion:
javascript read-only • copy allowedrand = signal(t => { const f = k => { if (silence._steps.gte(k)) return k; const a = cycleToSeconds(f(id(k-silence._steps))+scramble(notes[Math.floor(t)]).length, ...); return _mod(a + ... + f(constant(k - parseFractional(notes[4]._steps.n), getFrequency)), notes.length); }; return f(Math.floor(t)); });
The function f(k) called itself recursively without proper base cases, leading to stack overflow or infinite loops when executed in the browser.
Solution
Step 1: Fix the Recursive Function
Added memoization and depth limiting to prevent browser hangs:
javascript read-only • copy allowedconst memo = {}; const maxDepth = 500; rand = signal(t => { const floorT = Math.floor(t); const f = (k, depth) => { if (depth > maxDepth) return 0; if (k <= 0) return 0; const memoKey = k + '_' + depth; if (memo[memoKey] !== undefined) return memo[memoKey]; if (silence._steps.gte(k)) { memo[memoKey] = k; return k; } const a = cycleToSeconds( f(id(k-silence._steps), depth+1) + scramble(notes[Math.floor(t)]).length, ... ); const result = _mod( a + ... + f(constant(k - parseFractional(notes[4]._steps.n), getFrequency), depth+1), notes.length ); memo[memoKey] = result; return result; }; return f(floorT, 0); });
Step 2: Run the Fixed Code
With the memoization fix applied:
- Opened strudel.cc in browser
- Pasted the modified code
- Started playback
Step 3: Extract the Flag
When the code ran correctly, the pianoroll visualization displayed letters forming the flag. The letters appeared in the bottom-right corner of the visualization as the music played.
By examining the pianoroll visualization over time, the flag characters were revealed sequentially through the musical note patterns.
References
- Strudel.cc - Live coding music platform
- Algorave - Algorithmic rave music movement
Key Takeaways / Lessons
- Mastering base64_decoding, memoization_fix, recursive_function_analysis, visual_flag_extraction was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2025-12-29 • CUSTOM • algorave • Difficulty: Medium