$ cat writeup.md…
$ cat writeup.md…
alfactf
Task: a browser runner game hides the reward behind a 1000-point requirement in obfuscated client-side JavaScript. Solution: inspect the bundled code, recover the XOR-based string decoder and decrypt the embedded flag directly without playing.
Низкополигональный раннер с котом, рюкзаком и очень длинной дорогой.
English summary: the target serves a small browser runner game. The obvious objective is to survive long enough to reach the score threshold and reveal the flag.
URL: https://cat-k4sl0sey.alfactf.ru/
Initial enumeration showed a static single-page application:
GET / returned an HTML page that loaded /assets/index-yflPIC15.js and /assets/index-BREGg8EB.cssrobots.txt only contained User-agent: * and Disallow: /.git or .env returned the same app shell instead of useful filesThat strongly suggested the interesting logic lived in the client bundle.
After inspecting and deobfuscating the JavaScript bundle, the important constants and checks became clear:
ns = 0x3e8; function cs(arr, key) { return arr.map((v, i) => String.fromCharCode(v ^ (key + 3 * i))).join(''); } flag: () => [ cs([0x57,0x55,0x5a,0x5e], 0x36), cs([0x42,0x51,0x4a,0x31,0x2c,0x2b,0x14,0x2d,0x30,0x20,0x8,0x33,0x33,0x3f,0x2,0x39,0xb,0xd,0xc,0x19,0x5,0x19,0x18,0x15,0xfc], 0x39) ].join('')
And the score check used that generator directly:
if (state.score >= ns && !state.f0) { state.f0 = true; setBannerText(us.flag()); }
So the challenge was not about winning the game legitimately. The flag was already present in the bundle, only lightly hidden behind a custom XOR-based decoder and a score gate.
The bundle also contained cheat mechanics:
catcatcat → FASTERpowerup → GODMODE500These were interesting hints that the game was meant to be inspected rather than played honestly, but direct flag recovery was still faster.
We also checked a similar CTFBase writeup, 20260109_duckerz_task78_polet_normalny, which reinforced the same pattern: when a web game keeps flag logic on the frontend, inspect and decode the bundle instead of grinding points.
The helper function applies XOR to each array element with a position-dependent key:
decoded_char = value ^ (key + 3 * index)
Decoding the two arrays from us.flag() reconstructs the full flag.
#!/usr/bin/env python3 def cs(arr, key): return ''.join(chr(v ^ (key + 3 * i)) for i, v in enumerate(arr)) flag = ''.join([ cs([0x57, 0x55, 0x5a, 0x5e], 0x36), cs([ 0x42, 0x51, 0x4a, 0x31, 0x2c, 0x2b, 0x14, 0x2d, 0x30, 0x20, 0x08, 0x33, 0x33, 0x3f, 0x02, 0x39, 0x0b, 0x0d, 0x0c, 0x19, 0x05, 0x19, 0x18, 0x15, 0xfc ], 0x39), ]) print(flag)
Output:
alfa{music_cat_in_a_backpack}
This is enough to solve the challenge without modifying score values or using the in-game cheats.
cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ grep --similar