
Nullcon CTF 2025 Writeup
A quick walkthrough of the reversing challenges from Nullcon CTF 2025.
Nullcon CTF 2025 Writeup
A collection of my solutions for the reversing challenges in Nullcon CTF 2025.
Scrambled
This challenge involves unshuffling a scrambled string. We brute-force the seed to find the correct order of chunks and the XOR key to decode the message.
Solution Script
import random
scrambled_hex = "1e1f7e731f69781e1b646e19196e75191e781975196e757e671219666d6d1f756f6465510b0b0b57"
scrambled = [int(scrambled_hex[i:i+2], 16) for i in range(0, len(scrambled_hex), 2)]
chunk_size = 4
chunks = [scrambled[i:i+chunk_size] for i in range(0, len(scrambled), chunk_size)]
def unshuffle(chunks, seed):
order = list(range(len(chunks)))
random.seed(seed)
random.shuffle(order)
paired = list(zip(order, chunks))
paired.sort(key=lambda x: x[0])
return [b for _, chunk in paired for b in chunk]
for seed in range(15):
candidate = unshuffle(chunks, seed)
for key in range(256):
decoded = "".join(chr(b ^ key) for b in candidate)
if "ENO{" in decoded:
print("Seed:", seed, "Key:", key, "->", decoded)
# Seed: 10 Key: 42 -> ENO{5CR4M83L3D_3GG5_4R3_1ND33D_T45TY!!!}
Flag: ENO{5CR4M83L3D_3GG5_4R3_1ND33D_T45TY!!!}
Flag Checker
This challenge reverses an encryption scheme involving a right rotation by 3 bits, subtraction of the byte’s index, and an XOR with a constant 0x5A.
Solution Script
encrypted = [
0xF8, 0xA8, 0xB8, 0x21, 0x60, 0x73, 0x90, 0x83, 0x80, 0xC3,
0x9B, 0x80, 0xAB, 0x09, 0x59, 0xD3, 0x21, 0xD3, 0xDB, 0xD8,
0xFB, 0x49, 0x99, 0xE0, 0x79, 0x3C, 0x4C, 0x49, 0x2C, 0x29,
0xCC, 0xD4, 0xDC, 0x42
]
flag = []
for i in range(34):
byte = encrypted[i]
rotated = ((byte >> 3) | ((byte << 5) & 0xFF)) & 0xFF
after_sub = (rotated - i) % 256
original = after_sub ^ 0x5A
flag.append(chr(original))
print(''.join(flag))
Flag: ENO{R3V3R53_3NG1N33R1NG_M45T3R!!!}
Backtrack
This challenge involves decompressing a binary file (data.bin) using a custom algorithm found at sub_401690 in the original binary.
Solution Script
def decompress(data):
if len(data) < 4:
raise ValueError("Invalid data")
if data[0] == 1:
return data[4:]
out = bytearray()
i = 4
n = len(data)
while i < n:
if i + 1 >= n:
break
flag = data[i] | (data[i + 1] << 8)
i += 2
bits = 16
while bits > 0 and i < n:
if flag & 1:
if i + 1 >= n:
raise ValueError("Incomplete back-reference")
first = data[i]
length = (first & 0x0F) + 1
offset = ((first >> 4) << 8) | data[i + 1]
i += 2
if offset > len(out):
raise ValueError("Invalid offset")
src_index = len(out) - offset
for _ in range(length):
out.append(out[src_index])
src_index += 1
else:
out.append(data[i])
i += 1
flag >>= 1
bits -= 1
return bytes(out)
with open('data.bin', "rb") as f:
data = f.read()
decompressed = decompress(data)
with open('output', "wb") as f:
f.write(decompressed)