$ cat writeup.md…
$ cat writeup.md…
tjctf
Task: a broken binary that won't run — 'one little thing' was changed. Solution: discover all 0x00 bytes were replaced with 0x20 (space) and ELF magic corrupted; restore nulls and fix header, then extract flag from putchar calls via static analysis.
I changed just one little thing and my racing moose won't run anymore!
A single binary file chall (16808 bytes) is provided. The file command identifies it as "data" — not a valid executable. The goal is to figure out what was changed, fix the binary, and recover the flag.
The binary is not recognized as an ELF:
$ file chall
chall: data
Hex inspection of the first 16 bytes reveals the corruption:
$ xxd chall | head -1
00000000: 7f45 4c4b 0201 0120 2020 2020 2020 2020 .ELK...
The ELF magic should be 7f 45 4c 46 (.ELF) but byte 3 is 0x4b (.ELK) instead of 0x46. More importantly, the padding bytes that should be 0x00 are all 0x20 (space).
Counting byte occurrences confirms the pattern:
0x00 bytes in the entire file — impossible for a valid ELF which uses nulls extensively for padding, string terminators, and zero-valued header fields0x20 (space) bytes — far too many for a 16KB binaryThe "one little thing" that was changed: all 0x00 null bytes were replaced with 0x20 (space). The ELF magic F (0x46) → K (0x4b) change is a +5 shift that's also consistent with the corruption pattern (though the primary transformation is the null-to-space replacement).
Replace all 0x20 bytes back to 0x00 and fix the ELF magic byte:
#!/usr/bin/env python3 data = open('chall', 'rb').read() fixed = bytearray(data) # Restore all 0x20 (space) -> 0x00 (null) for i in range(len(fixed)): if fixed[i] == 0x20: fixed[i] = 0x00 # Fix ELF magic: byte 3 should be 0x46 ('F'), not 0x4b ('K') fixed[3] = 0x46 open('chall_fixed', 'wb').write(bytes(fixed))
After fixing:
$ file chall_fixed
chall_fixed: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped
Note: This also zeroes out legitimate 0x20 bytes in string data (spaces become nulls), but the executable code and ELF structure are correctly restored since code bytes rarely contain 0x20.
The binary is not stripped and contains meaningful symbols: main, flag, flag1, flag2, flag3, flag4, plus imports for putchar and printf. The source file was chall.c.
Using radare2 for disassembly, the program logic is straightforward:
main calls flag(), flag1(), flag2(), flag3(), flag4() in sequenceputchar() calls with immediate character values as argumentsBy reading the immediate values passed to putchar across all five functions:
| Function | Characters |
|---|---|
flag | t, j, c, t |
flag1 | f, {, 5, m |
flag2 | a, 1, 1, _ |
flag3 | m, 0, 0, s |
flag4 | 3, } |
Concatenated: tjctf{REDACTED}
This decodes as "small moose" in leetspeak (5→s, 1→l, 0→o, 3→e), fitting the "racing moose" theme perfectly.
$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar