Post

CSAW CTF 2023: writeups

CSAW CTF 2023: writeups

These are my notes for CSAW CTF 2023 (held in September 2023). All the challenges here are reverse engineering: rebug1, rebug2, Impossibrawler and rox. The first three have flags. For rox I recovered the input the binary wants, but its output is a decoy string, so I list it as unsolved.

Rev - rebug1

Files: rebug1.zip

The challenge gives an ELF binary, test.out, that asks for a string.

Reading the decompiled code, the program checks that a loop counter i equals 12 after the loop ends.

rebug1 check on i = 12

So any input string v8 that is 12 characters long makes the loop end with i = 12:

1
2
3
4
⚡halston ❯❯ ./test.out
Enter the String: AAAAAAAAAAAA
that's correct!
csawctf{c20ad4d76fe97759aa27a0c99bff6710}

The other way is to patch the program while debugging it so the check always passes.

rebug1 debugging, patch step rebug1 debugging, result

Flag: csawctf{c20ad4d76fe97759aa27a0c99bff6710}

Rev - rebug2

Files: rebug2.zip

The challenge gives a binary called bin.

Looking at the control flow, the program builds the flag in a function named xoring. So I debug the program and read the data at that point.

rebug2 control flow with the xoring function

Debugging it caused no problems.

rebug2 flag data in the debugger

Flag: csawctf{01011100010001110000}

Rev - Impossibrawler

Files: impossibrawler.zip

The challenge is a Godot game. I got Impossibrawler.exe and Impossibrawler.pck. The icon of the exe looks like a Godot export, so I guessed the pck was built with Godot and decompiled it with the GDRE tools.

Impossibrawler exe and pck files

The decompile gives many files, including audio, graphics and game data.

Decompiled project files

Going through the scripts, the flag is given after you pass level 1, reach level 2 and win it. Playing is hard because shots do not kill the target, which looks like it was done on purpose. Instead I rewrote the flag code in GDScript so it matches the program. The game reseeds the random number generator with a fixed seed, so the first randf() value is deterministic, and the flag is its hex-encoded string.

1
2
3
4
5
6
7
8
9
10
extends Node

var rng = RandomNumberGenerator.new()

func _ready():
    rng.seed = 0
    var fbytes1 = str(rng.randf())
    var flg = fbytes1.to_ascii().hex_encode()
    print("csawctf{" + flg + "}")
    pass

Flag: csawctf{302e323032323732}

Rev - rox

Files: rox.zip

Status: unsolved (no csawctf{...} flag). The source writeup for this challenge was empty, so I analyzed the food binary statically with file, strings, objdump and a Python script.

food is a FreeBSD x86-64 ELF, not stripped. It takes one command line argument and passes it to verify. What I found:

  • verify holds a 74-byte array on the stack and a global vector<int> of 3497 values (copied from .rodata).
  • Stage 1 XORs the first len(arg) bytes of the array with the argument.
  • Stage 2 sets v[i] = G[(G[(10*i+12) % n] + arg[i % len]) % n] ^ v[i].
  • Stage 3 runs a 300-round loop per byte from index 5 on, mixing in j << 5 and whether v[i-5] == 0x6e.
  • The result is compared with a 74-byte string in .rodata. A mismatch prints a random decoy such as flag{trying random strings isn't going to help}.

Since stage 3 and stage 2 are invertible per byte, I inverted them from the target string and tried every key length. Only length 29 is consistent, and the readable key is aN0ther_HeRRing_or_iS_tHis_iT. I checked it with a forward simulation in Python, and it reproduces the target string exactly:

1
2
3
4
5
6
7
8
key = b"aN0ther_HeRRing_or_iS_tHis_iT"
v = v0[:]
for i in range(len(key)): v[i] ^= key[i]
for i in range(N): v[i] = (G[(G[(10*i+12) % n] + key[i % len(key)]) % n] ^ v[i]) & 0xff
for i in range(5, N):
    for j in range(300):
        v[i] = (v[i] ^ ((j << 5) & 0xff) ^ (1 if v[i-5] == 0x6e else 0)) & 0xff
# bytes(v) == b"flag{ph3w...u finaLly g0t it! jump into cell wHen U g3t t0 the next cha11}"

The string the binary prints for the correct key is flag{ph3w...u finaLly g0t it! jump into cell wHen U g3t t0 the next cha11}. It is not in the csawctf{...} format, and it reads like a message to move on to the next challenge, so I do not claim it as the flag. I could not run the FreeBSD binary here to confirm the exact output, and I did not find any other csawctf{...} value in it.

This post is licensed under CC BY 4.0 by the author.