List Dynamic Dependencies : ldd
Get version of libc : file /lib/x86_64-linux-gnu/libc.so.6
La taille minimum d'un chunk est de 24 bytes.
En bleu c'est le chunk alloué par malloc(), on peut voir que malloc() alloue 24 bytes sur la heap.
Tout les chunks contiennent une metadonnée au début du chunk :
C'est la chunk size, dans notre cas la taille du chunk est de 8 bytes * 4 soit 32 bytes ou 0x20 en hexadécimal. Le dernier byte du chunk size est 1 ce qui signifie que le chunk précédent est utilisé par le programme.
Le top chunk est le gros chunk qui est segmenté à chaque appel de malloc. Il a aussi une métadonnée avec la taille du chunk.
Mais dans beaucoup de libc le top chunk n'est pas sujet à une vérification d'intégrité ce qui nous permet d'introduire la technique de l'House of Force :
On nous donne un binaire qui nous donne l'adresse de puts() dans la glibc ainsi que la start adresse de la heap :
On peut facilement retrouver la base adresse de la libc :
Il faut donc bien comprendre ce schéma pour pouvoir comprendre l'exploitation :
La vulnérabilité réside dans le fait que la taille du top chunk n'est pas vérifiée. Cela permet donc de pouvoir avoir un top chunk très grand qui lorsque l'on allouera une grosse valeur nous permettra d'écrire à l'adresse de notre choix dans le programme.
Solution :
Avec cette technique on peut écraser où l'on le souhaite dans la mémoire, maintenant pour avoir une exécution de code on a plusieurs manière de overwrite :
Pour avoir une explication plus poussé sur l'utilité de la PLT et de la GOT se référer à la section dédiée. Malheureusement aucunes de ces options marcheront notamment à cause de l'ASLR et du RELRO.
Pour palier à ce problème on va utiliser une technique très connu lors de l'exploitation de format string :
Notre objectif va donc être de réécrire __malloc_hook afin de pouvoir pointer où on le souhaite comme par exemple notre shellcode si notre programme fait appel à malloc() plus tard.
Mais dans notre cas nous allons préféré écrasé notre pointeur avec l'adresse de system() puisqu'on a la base adresse de la libc. La fonction system() prend en argument une string, vu que malloc prend un argument on pourra donc appeler notre malloc() qui prend notre SIZE normalement sauf que cette fois-ci ce sera l'adresse de notre /bin/sh\x00
qui sera sur la heap à l'adresse START_HEAP + 0x30
soit juste après notre TOP_CHUNK lors du premier malloc().
Reprenons donc notre petit schéma habituel :
Voilà maintenant nous avons un shell !
Cette technique marche pour la GLIBC 2.28 et en dessous. Une protéction a été mise en place pour éviter cela en comparant la taille du top chunk à une variable system_mem.
La fonction free() prend en argument un pointeur vers un chunk et va donc le mettre (le linker) dans une des plusieurs listes de free appelé bins. Un de ces bins est le fastbin, les chunks y sont recyclés vite et ce seront des chunks de taille spécifique qui y seront stockés.
Maintenant il nous faut comprendre dans quel contexte et comment les fastbins fonctionnent.
Tout d'abord on doit comprendre que lorsque l'on alloue un chunk et qu'on le free, celui-ci va être mis dans le fastbin correspondant à la taille de son chunk.
C'est à dire que pour un chunk de 0x20 bytes il va être donc placé dans le fastbin des chunks de 0x20 bytes. Les fastbins fonctionnent à la manière LIFO (Last In First Out).
Pour mieux comprendre voici un exemple :
Ici on va allouer trois chunks de taille minimale soit 0x20 bytes, la heap va donc ressembler à cela après ces trois malloc() :
Nous avons donc :
Maintenant que se passera-t-il si l'on free un chunk, et bien regardons cela :
On free donc le chunk A ce qui nous donne cette heap :
Ce qui s'est passé c'est que le chunk A a été mis dans le fastbin pour les chunks 0x20. A chaque fois qu'un chunk sera free, son adresse est écrite à la tête du fastbin correspondant.
Maintenant on peut se demander comment on sait que c'est un fastbin puisqu'il n'y a rien qui nous le dit en regardant seulement la heap, c'est pour ça que l'on va regarder une structure qui est situé dans la section .data dans la libc qui est la main_arena.
Chose importante à savoir c'est que chaque heap à son arena et celle du thread principal est appelé la main_arena.
Les arenas ont été mise en place pour permettre le multi-threading et donc permettre à plusieurs régions dans la mémoire d'être utilisées en même temps sans interférer entre eux.
Le nombre maximal d'arena par défaut sur un CPU 64bits est le nombre de CPU x 8, tandis que pour un CPU 32bits c'est le nombre de CPU x 2. Si l'on souhaite modifier cela on peut utiliser la fonction mallopt().
On va donc voir en détails ce qui compose cette structure (notez que toutes les informations que je donne n'ont pas forcément de rapport avec les fastbins mais plus pour comprendre le fonctionnement) :
Reprenons la suite du programme, nous allons free() une deuxième fois maintenant :
Le chunk B étant free() il va contenir un FORWARD_POINTER (ou FD) qui va pointer vers le chunk A qui lui même contient un FD nul ce qui signifie que c'est la fin de la liste.
De ce fait seulement l'adresse du chunk B sera contenu dans la main arena :
⚠️ Chose importante c'est que les fastbins sont la seule exception à la règle du PREV_INUSE.
Petit schéma pour récapituler ce qui se passe après avoir free tous les chunks :
Maintenant que se passera-t-il si nous faisons un malloc() :
Le dernier fastbin va être utilisé et maintenant le fastbin va contenir l'adresse du chunk B. On peut donc justifier le fonctionnement en LIFO.
C'est une feature de malloc introduite depuis la glibc 2.26.
La PLT ou Procedure Linkage Table est un tableau de pointeurs sur fonction. Chaques fonctions que le programme appelle et qui sont dans une librairie externe comme la GLIBC vont apparaître dans la PLT.
Une entrée dans la PLT d'une fonction va contenir un JMP vers la GOT (Global Offset Table)
Cette section est writable pendant l'exécution d'un programme à cause de ce qu'on appelle le Lazy Linking puisque que l'adresse d'une fonction est résolue seulement quand elle est appelé la première fois.
Cette section est un tableau de pointeurs sur fonctions qui lorsque le programme va exit, vont être exécutées.
Protection mise en place pour empêcher d'écrire sur les sections comme la PLT, la GOT et fini_array dès qu'elles sont initialisés.
Restrict display to only code section : set context-sections code
Visualize heap chunks : vis_heap_chunks | vis
Extended information for virtual addresses : xinfo target
https://www.segmentationfault.fr/linux/role-plt-got-ld-so/
https://sourceware.org/glibc/wiki/MallocInternals
Format strings :
Linux Kernel :
SLUB/SLAB :
Stack based :
Windows Kernel :
Linux Heap Exploitation :