Protostar - Stack Buffer-Overflow
Protostar est une machine virtuelle fournis par la plateforme exploit-exercices, qui a pour but d’introduire au développement d’exploits, et plus particulièrement aux failles de type buffer-overflow. Les quatres premiers niveaux sont graduels de façon à servir de base pour le vrais buffer-overflow, qui est fait au challenge numéro 0x5. Le challenge 0x6 permet quand à lui d’expérimenter avec le ROP (Return Oriented Programing) et le ret2libc.
Stack 0x0
Ce tout premier niveau nécessite d’effectuer un débordement mémoire capable d’écraser la valeur d’une variable dans la stack. Il suffit ici d’exécuter le programme en fournissant une entrée utilisateur de plusieurs dizaines de lettres “A”, jusqu’à écraser le contenu de la variable “buffer”. L’espace attribué à la variable est de 64 bytes. Il suffit donc d’envoyer 65 lettres “A” pour commencer à modifier le contenu de la variable. Prenons tout de même le temps d’observer au travers de gdb ce qu’il s’est passé. Je place un breakpoint sur la dernière instruction du programme: Avant de lancer le programme avec comme paramètre 65 fois la lettre “A”: En observant le contenu de la stack, nous voyons bien que notre chaine de “A”, ou “\x41” en hexadécimal, à remplacé une part de l’espace mémoire non alloué à la variable ‘buffer’.
Stack 0x1
Ce niveau nécessite de changer le contenu d’une variable au travers d’un débordement mémoire. Nous cherchons donc ici à modifier la valeure de la variable ‘modified’ de façon à ce que celle-ci soit égale à ‘0x61626364’. Rien de plus simple, il suffit de raisonner logiquement ! Le buffer alloué à la variable ‘buffer’ à une taille de 64 bytes. Nous devrions donc écraser la stack à partir de 65 bytes passés en paramètre du programme. Il restera ensuite à déterminer la position du contenu de la variable ‘modified’ dans la stack, puis d’en remplacer le contenu par ‘0x61626364’ en little endian. Puisque le programme nous retourne la valeure de la variable ‘modified’, il n’y a même pas besoin de passer par un débugger. Essayons de passer l’alphabet au bout des 64 “A” pour trouver la position de la variable cible dans la stack: L’espace mémoire attribuée à la variable ‘modified’ commence donc au 65ème bytes. Nous avons écrasé le contenu de la variable avec “44434241”, soit “ABCD” en hexadécimal (et en little endian). Il ne nous reste plus qu’à remplacer le contenu de la variable par ‘61626364‘ pour valider le challenge:
Stack 0x2
Ce niveau est très similaire au précédent. Une variable doit être modifiée via un débordement mémoire. Cette fois-ci, le contenu de la variable n’est pas passé en argument, mais celui-ci correspond directement à une variable d’environnement: Je répète exactement le même processus que pour le niveau d’avant: Maintenant que l’emplacement de la variable dans la stack est connu, je passe la bonne chaine à la variable d’environnement cible, avant d’exécuter le programme et de terminer ce niveau:
Stack 0x3
Le troisième niveau demande de modifier le flow d’un programme de façon à écraser un pointeur de fonction pour le rediriger vers la fonction de notre choix. Ici, ce sera la fonction win(): L’exercice est très similaire à ce qui a été fait plus haut, à la différence prêt que l’adresse à laquelle nous cherchons à effectuer un jump n’est pas en dur dans le programme. Heureusement pour nous, il est très simple de trouver l’adresse mémoire d’un fonction. J’utiliserai GDB pour obtenir l’adresse de la fonction win(): Il ne reste plus qu’à écraser le contenu de la variable servant de pointeur avec l’adresse de cette fonction. Celle-ci sera ensuite directement appelée: Et voilà, une fois le pointeur détourné nous pouvons rediriger le programme vers une fonction initialement inatteignable.
Stack 0x4
Ce niveau consiste à contrôler le pointeur d’instruction d’un programme. Au lieu de passer par une variable tierce comme dans l’exercice précédent, il faudra ici contrôler le registre EIP en lui même. Le but final est de détourner le flow du programme pour exécuter une fonction isolée: Commençons par ouvrir le programme dans GDB, puis essayons de lever une erreur ‘segmentation-fault’ en passant de plus en plus de caractères au programme. Au bout de 80 lettres “A”, j’obtiens la fameuse erreure. En observant les registres, et notamment EIP, je remarque que celui-ci a été écrasé: Il nous faut donc diversifier le contenu de la chaine pour trouver la position exacte qui va écraser le pointeur d’instruction EIP. Je passe donc une chaine de 70 lettres “A” suivie de l’alphabet en majuscule, avant d’observer à nouveau de contenu de EIP: Il se trouve que EIP contient maintenant la valeur Nous commençons donc à écraser EIP à partir du 76ème bytes passé en input. Il ne reste qu’à récupérer l’adresse de la fonction win(): La charge final comporte donc 76 lettres “A”, puis l’adresse de la fonction win() en little endian:
Stack 0x5
Ce niveau est un buffer overflow classique complet, avec intégration d’un shellcode: Commençons par déterminer à partir de combien de bytes il est possible d’écraser EIP et de contrôler le flow du programme: Nous écrasons donc l’adresse de retours à partir du 77ème byte. Avant de pouvoir poursuivre, il est nécessaire de générer le payload qui sera exécuté. Nous utiliserons ici l’outil msfvenom, pour créer un shellcode: Le corps de l’exploit sera donc le suivant pour la suite de ce challenge: Le script python correspondant est le suivant: Le script est composé: (1) D’un padding de 76 caractères. (2) D’une “Nop-slide”. L’instruction “NOP”, pour “No-OPeration”, correspond à l’opcode “\x90”. Lorsque le programme rencontre cette instruction, il se contente de passer à la suivante. Cette suite de 16 opcodes “\x90” sera placée juste derrière l’EIP, et avant le corps du shellcode. De cette façon, la valeure d’EIP sera prise au milieu de cette nop-slide, de façon à amener au shellcode, même en cas de légère modification de l’adressage mémoire. Il ne reste qu’à déterminer une valeure d’EIP comprise dans la suite d’instructions NOPs. Pour ce faire, un breakpoint est placé juste après la fonction get du programme: Puis l’exploit actuel est passé en paramètre du programme vulnérable: Le registre EBP pointe sur l’adresse “0xbffff7b8”, ce qui confirme bien le placement de notre padding et du nouvel EIP: En sélectionnant l’adresse “0xbffff7c0” comment nouvelle valeure du pointeur EIP, le flow de programme sera détourné de façon à exécuter notre shellcode, et par extension, la commande
/usr/bin/whoami > /tmp/out
L’exécution de notre buffer-overflow donne le résultat suivant: Il est désormais possible d’exécuter des commandes root, en élevant nos privilèges utilisateurs via ce buffer-overflow.