Overview
| Field | Value |
|---|---|
| Category | Easy |
| Author | Fatmike |
| Binary | PE32+ x64 Windows GUI (C/C++) |
| Flag | CMO{y0u_g0t_r1ckr0ll3d} |
Recon
The binary is a custom-skinned Windows audio player. Running it shows a record player UI with a PLAY button and two switches. The README explains:
- One switch controls the direction the record spins
- The other keeps the pitch steady
- Both switches are broken
Key imports:
- WINMM:
waveOutOpen,waveOutWrite,waveOutPrepareHeader- audio playback - KERNEL32:
FindResourceW,LoadResource,LockResource- embedded WAV resource - USER32:
DialogBoxParamW,PostMessageW- custom dialog UI
The binary embeds a WAV resource (type SOUND, ID 141): 8-bit mono PCM, 44100 Hz, 720,623 bytes.
Program Flow
MainDialog Constructor (0x1400032B0)
Creates an audio player object (0x1B8 bytes) via sub_1400016F0, sets up a lambda callback, and creates the UI buttons.
Play Button Handler (0x140003860, case 1001)
- Loads WAV resource via
sub_140001F50 - Sets direction:
sub_140003A00(player, 1)->direction = 2 * (1 ^ 1) - 1 = -1(backward) - Sets pitch toggle:
sub_140003A20(player, 1)->pitch_toggle = 1(wobble enabled) - Starts playback:
sub_1400020F0(player)
Both switches are “broken”: the record plays backward with a wobbling pitch.
The Fix
To “fix” the switches:
- Pass
0instead of1tosub_140003A00->direction = 2 * (0 ^ 1) - 1 = 1(forward) - Pass
0instead of1tosub_140003A20->pitch_toggle = 0(steady pitch)
This gives normal forward playback without pitch wobble.
Start Playback (0x1400020F0)
- If
direction == -1: start position =pcm_size - 1(end of audio) - If
direction == 1: start position =0(beginning) - Allocates two 0x2000-byte waveOut buffers (double-buffering)
- Fills them via
sub_140002310and starts a playback thread
Buffer Fill / Verification (0x140002310)
For each output sample:
- Generate sample: If
pitch_toggle == 0, readspcm[position]and advances bydirection. Ifpitch_toggle == 1, calls the pitch wobble functionsub_140001E20. - Ring buffer: Stores the sample in a block-based ring buffer (16 bytes per block).
- Sliding window: Maintains a counter that saturates at
key_length(23). Once full, a 23-byte sliding window advances by 1 each sample. - XOR + hash check: XORs the 23-byte window with a hardcoded key, computes FNV-1a hash, and compares against a target.
- Callback: On match, invokes the lambda which stores the XOR result string and posts
WM_USER+1to display it in a popup dialog.
Pitch Wobble (0x140001E20)
When enabled, applies linear interpolation between adjacent samples with a wobbling playback rate:
pitch_ratestarts at 1.0 and oscillates between 0.3 and 1.2pitch_deltastarts at -1.0 and flips sign at the boundaries- Rate of change:
pitch_delta * 0.00005per sample
This creates a warped, unsteady playback.
Key Constants
XOR Key (23 bytes, from disassembly at 0x1400017B6)
30 2B 3D FC F6 B6 06 3B 0E B1 ED C0 E1 48 07 0C 0B BB F4 F9 48 01 19
Constructed from DWORDs on the stack:
Src[0] = 0xFC3D2B30 -> 30 2B 3D FC
Src[1] = 0x3B06B6F6 -> F6 B6 06 3B
Src[2] = 0xC0EDB10E -> 0E B1 ED C0
Src[3] = 0x0C0748E1 -> E1 48 07 0C
Src[4] = 0xF9F4BB0B -> 0B BB F4 F9
+ WORD 0x0148 -> 48 01
+ BYTE 0x19 -> 19
Important: IDA’s decompiler showed incorrect values for these constants (e.g., 0xFC3DB4A0 instead of 0xFC3D2B30). The disassembly had to be checked directly.
Target FNV-1a Hash
0x18940A3D (at offset 0x150 in player object)
Also misrepresented by the decompiler as 0x18946A3D.
FNV-1a (0x140001030)
Standard FNV-1a with seed 0x811C9DC5 and prime 0x01000193:
hash = 0x811C9DC5
for each byte b:
hash = (hash ^ b) * 0x01000193
Solve
The flag is hidden at PCM offset 132,300 in the embedded WAV. With forward playback and no pitch wobble (the “fixed” state), the 23-byte window at that offset XORs with the key to produce the flag:
|
|
Lessons Learned
IDA’s Hex-Rays decompiler can produce incorrect constant values, particularly for stack-initialized arrays. Always verify critical constants against the disassembly when results don’t match expectations.
Flag
CMO{y0u_g0t_r1ckr0ll3d}