Table of Contents


Nullcon CTF 2025 Writeup

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)