C’est une vulnérabilité dont l’origine est un manque de rigueur dans la programmation. Lorsque qu’un développeur désire effectuer une copie de données à destination d’un “buffer” mais que la capacité de celui-ci est a priori insuffisante pour contenir la quantité de données souhaitées alors il y a débordement de la pile et on parle ainsi de débordement de tampon ou “buffer overflow”.
Contexte
Les fonctions qu’utilisent nos programmes ont besoin d’allouer de l’espace sur la pile (stack) pour leurs variables locales ainsi que le cadre de pile (stack frame) de ces fonctions. La stack frame d’une fonction est une zone mémoire, dans la pile, dans laquelle toutes les informations, nécessaires à l’appel de cette fonction, sont stockées. S’y trouvent également les variables locales de la fonction.
Prenons l’exemple d’allocation de la pile d’un tableau dans une fonction :
On obtient le schéma suivant pour représenter la stack :
Maintenant allouons un tableau de caractère à cette variable locale de manière suivante :
Alors “uneChaine” sera copié dans la pile à l’espace alloué, et ce en partant de l’adresse pointé par ESP puis en descendant dans la pile. Si on prend l’exemple d’une chaine remplie de « A » d’une longueur supérieur à 24 octets.
La stack se présente comme suit :
Le développeur a pu réécrire sur la valeur de retour que le processeur récupérera à la fin de la fonction. Dans l’état actuel, à la fin du programme, il va tenter d’aller à l’adresse «AAAA» qui, en hexadécimal est 0x41414141
. Comme il n’a pas le droit d’accéder à cette case mémoire, ou que cette zone mémoire n’est pas mappée, nous obtenons un
SEGFAULT
.
Exploitation sur un programme
Prenons l’exemple d’un programme qui prend en entrée un argument qui est un string ou plus précisément un tableau de caractères. Cet argument est passé tel quel à la fonction func. La fonction prévoit de la place sur la pile en allouant 64 octets. Le programme copie le contenu de la chaîne de caractères dans ce buffer, sans aucune vérification de taille, et en fin affiche le contenu du buffer.
Après compilation et test:
Dans un premier cas nous avons passer notre programme la chaine de caractère “AAAA” : il l’affiche correctement et dans un second cas nous l’avons passé un long chaine et nous retrouvons face à une erreur de type “segmentation fault“. Nous allons tenter de comprendre pourquoi, en suivant pas à pas le fonctionnement du programme lors de son exécution.
En utilisant le gdb sur notre binaire bufferOverflow:
> gdb bufferOverflow
Le binaire tente d’accéder à l’adresse
0x41414141
qui est le code ASCII «AAAA» et que cette adresse n’est pas valide donc le programme plante. Si on désassemble le code la fonction “main” ainsi que le code de notre fonction “func“, pour comprendre pourquoi et où le binaire a planté ?
La première partie de ce code correspond à la fonction “main” et la deuxième partie correspond à la fonction “func“.
L’appel de la fonction “func” se fait à l’instruction située à l’adresse 0X00000000004005d6
de la fonction “main”. Lorsque nous rentrons dans func, la troisième ligne correspond à l’allocation du buffer 0x40
. Ensuite, à l’adresse 0X00000000004005ff
se trouve l’appel système pour copier le contenu de la variable dans le buffer. L’instruction à l’adresse 0X000000000040060b
fait l’appel à puts qui permet d’afficher un tableau de caractère sur la sortie standard, et enfin nous avons l’instruction de retour à l’adresse 0X0000000000400626
.
Pour mieux comprendre le fonctionnement et l’exécution du code, on pourrait placer des breakpoints à des endroits stratégiques.
Malick Gueye, Matthieu Jolo, Jean Querol, Yogaratnam Yugansan