cit@ctf 2025 writeups
catacombs [rev]
just strings it

Maybe they forgot sth, let’s solve it in the “intended” way
what if it’s a remote challenge
This feels like a maze challenge where we have to reach all the nodes. Once we’re done, call submit to get the flag

Digging into IDA: main -> runScript -> applyVisibleStep. I found EDGE_TABLE, which (as confirmed through testing) basically maps out all the edges of the “catacombs”.

escaperoom [rev]
This is another game/riddle challenge

I tried out all the commands. It seems that once we achieve the correct room state, we can 8. enter door override token to get the flag.
The tasks are listed in the facility log:

I also found the state variables for these things:
gstate = 0light offdword_5D90E4 = 1ventilation route eastdword_5D90E8 = 3cam 3dword_5D90EC = 2door patch 2 times- inside maintainance shell:
dword_5D90F1 = 1mirror first thendword_5D90F2 = 1hush dword_5D90F0 = 1emegency battery on
Alright, thing is set up

But i still need the door token; where could it be?
Looking at the references of those state variables, I found roomSignature() -> buildOverrideToken()
I couldn’t find where it was called, so i replicated it:
def rol32(v, n):
v &= 0xFFFFFFFF
return ((v << n) | (v >> (32 - n))) & 0xFFFFFFFF
def room_signature(g_state, e4, e8, ec, f0, f1, f2):
v0 = 324508639 if g_state else 610839776
inner = (521288629 * (e4 + 1) + rol32(v0 ^ 0xA17C3E29, 7)) & 0xFFFFFFFF
v5 = (668265261 * (ec + 5) + ((73244475 * (e8 + 3)) ^ inner)) & 0xFFFFFFFF
v1 = 0xFFFFFFFF & -1515890086 if f1 else 1515890085
v6 = (v1 ^ v5) & 0xFFFFFFFF
v2 = 826366246 if f0 else 655894552
v7 = (v2 + v6) & 0xFFFFFFFF
v3 = 0xFFFFFFFF & -559038737 if f2 else 0xFFFFFFFF & -1160724258
return (v3 ^ v7) & 0xFFFFFFFF
def build_override_token(g_state, e4, e8, ec, f0, f1, f2):
alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
spice = [19, 55, 49374, 48879, 90, 2766, 16962, 36877, 4660, 1911]
sig = room_signature(g_state, e4, e8, ec, f0, f1, f2)
v6 = (sig ^ 0x6F70656E) & 0xFFFFFFFF
token = []
for i in range(10):
v6 = (1664525 * v6 + spice[i] + 1013904223) & 0xFFFFFFFF
token.append(alphabet[v6 >> 27])
if i == 2 or i == 5:
token.append('-')
return ''.join(token)
print(build_override_token(
g_state=0, e4=1, e8=3, ec=2, f0=1, f1=1, f2=1
))
Running it prints: RHY-QVT-KAXJ

I wonder if there is a neat way to reverse the validate() in enterOverrideToken() for a better approach.
trivia
I was fed up at this point of writing. Some other challenges follow a similar game-like pattern:
- find & setup the correct state
- derive the token
- submit token & get flag
Except for the
reallysecurepasswordmanager, where we have to find a way to bypass the user check.
Let’s take a look at the interesting thing i found in these challenges:
mangled look
For example, this is from challenge say my name:

Upon looking it up, this was done with anonymous namespace. Let me explain:
A namespace is just a named container that groups related code together to avoid name collisions.
namespace math {
int add(int a, int b) { return a + b; }
}
namespace mycode {
int add(int a, int b) { return a + b; }
}
math::add(1, 2); // unambiguous
mycode::add(1, 2); // unambiguous
An **anonymous namespace** is the same thing but with no name:
namespace {
int add(int a, int b) { return a + b; }
}
The C++ standard gives symbols in anonymous namespaces internal linkage, IDA just labels them with the prefix 'anonynmous namespace'::

looks fun.
gdb start & ni denial
can’t run with start

in certain parts. the ni doesn’t work

i haven’t figured out why yet, be updating soon.