Partie 8 - Les fastbins - fonctionnement interne (1/4)
Les fastbins : fonctionnement interne (1/4)
Et si nous regardions Ă quoi ressemble le prĂ©dĂ©cesseur du tcache ? Enfin, pas tout Ă fait son ancĂȘtre, puisquâils ne fonctionnent pas exactement de la mĂȘme maniĂšre. Quoi quâil en soit, ils coexistent paisiblement depuis la version 2.26 đ.
Par
fastbinsau pluriel, on entend âles diffĂ©rentes corbeilles de typefastbinâ. Encore un abus de langage qui, jâespĂšre, ne vous dĂ©rangera pas trop đ«Ł.
Utilisation
Les fastbins ont plusieurs points communs avec le tcache :
- elles peuvent gérer des blocs de petite taille ;
- les blocs libres sont liés sous forme de liste chaßnée ;
- la liste chaßnée est de type LIFO ;
- il y a moins de corbeilles de type
fastbinque de typetcache(respectivement10contre64) ; - la libc, plus prĂ©cisĂ©ment lâarĂšne, pointe vers le premier bloc seulement.
Quant aux différences, ce sont essentiellement les suivantes :
- le
tcacheest utilisĂ© avant lesfastbinstant quâil y a de la place pour un bloc libre ; - il nây a pas de limite au nombre de blocs dans une des corbeilles de type
fastbincontrairement autcachequi avait une limite de 7 blocs ; - la taille maximale des blocs gérés est plus petite, voir ci-dessous ;
- dans les
fastbins, le champfddâun bloc libre ne pointe pas vers le champfddu bloc suivant. Il pointe plutĂŽt vers le champprev_size, qui correspond au dĂ©but du bloc suivant, plus prĂ©cisĂ©ment au dĂ©but de ses mĂ©tadonnĂ©es ; - les blocs libres des
fastbinspeuvent ĂȘtre consolidĂ©s, sans pour autant utiliser le champprev_size. Contrairement aux blocs de plus grande taille, les blocs libres desfastbinsne sont pas immĂ©diatement consolidĂ©s. Leur consolidation a lieu lorsque la fonction malloc_consolidate est appelĂ©e, par exemple lorsquâun bloc de grande taille est allouĂ© et quâil y a des blocs libres adjacents dans lesfastbins; - les diffĂ©rentes corbeilles de type
fastbinsont gĂ©rĂ©es via lâarĂšne, ce qui nâest pas le cas dutcache.
Gestion des blocs libres
Taille des blocs gérés - résumé
Les tailles données ici sont celles retournées par
mallocaprĂšs avoir pris en compte lâalignement et lâespace nĂ©cessaire pour les mĂ©tadonnĂ©es.
Les tailles de blocs gérés par les fastbins sont les suivantes (en octets) :
| Architecture | Taille min dâun bloc libre | Taille max dâun bloc libre |
|---|---|---|
32 bits (anciennes versions * ) | 0x8 | 0x48 |
| 32 bits | 0x10 | 0x40 |
| 64 bits | 0x20 | 0x80 |
*Dans les anciennes versions de la glibc, comme la 2.5, la taille maximale dâun bloc libre est bien de0x48, cela est certain. Concernant la taille minimale, elle semblerait ĂȘtre de0x8, mais ce point reste Ă vĂ©rifier.
Vous lâaurez compris, les fastbins ne gĂšrent que les blocs de petite taille.
Taille des blocs gérés - détails
Et si on lisait un peu de code source de la glibc pour apprendre Ă trouver et comprendre ce type dâinformation ? Essayons notamment dâidentifier :
- le nombre de corbeilles de type
fastbins; - la taille de blocs gérée par ces corbeilles.
Tout dâabord, voyons comment sont gĂ©rĂ©es les fastbins depuis lâarĂšne (dont le nom de la structure est malloc_state) :
1
2
3
4
5
6
7
8
9
struct malloc_state
{
__libc_lock_define (, mutex);
int flags;
int have_fastchunks;
mfastbinptr fastbinsY[NFASTBINS]; // <-- Ici !
// (...)
}
Pour rappel, une arÚne est une sorte de déchetterie intelligente qui gÚre les différentes corbeilles.
Le membre fastbinsY est la liste des différentes fastbins. Il y en a exactement NFASTBINS.
Cela ne nous avance pas, je ne comprends toujours pas combien il y a de
fastbins?
Pour trouver la valeur exacte de NFASTBINS, on ne va pas se mentir, ce nâest pas si trivial que ça. Utilisons le site elixir.bootlin.com pour naviguer dans le code source de la glibc afin de dĂ©cortiquer tout cela, notamment les lignes suivantes :
1
2
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)
Pour résumer, voici à quoi servent les fonctions fastbin_index et request2size :
fastbin_index: initialement, cette fonction permet de trouver la corbeille adĂ©quate pour un bloc libĂ©rĂ© dâune taille donnĂ©e ;request2size: permet de trouver la taille de bloc Ă utiliser en prenant en compte les mĂ©tadonnĂ©es.
Je vous Ă©pargne les dĂ©tails des calculs đ„”, voici comment sont agencĂ©es les 10 fastbins en fonction de lâarchitecture :
| Index | Taille de blocs gérée (32 bits) | Taille de blocs gérée (64 bits) |
|---|---|---|
| 0 | 0x10 | 0x20 |
| 1 | 0x18 | 0x30 |
| 2 | 0x20 | 0x40 |
| 3 | 0x28 | 0x50 |
| 4 | 0x30 | 0x60 |
| 5 | 0x38 | 0x70 |
| 6 | 0x40 | 0x80 |
7* | 0x48 | 0x90 |
8* | 0x50 | 0xa0 |
9* | 0x58 | 0xb0 |
Dans les derniÚres versions de la glibc, la taille des blocs est toujours alignée sur
0x10octets et ce, mĂȘme en 32 bits. Ce qui implique que seule une corbeille sur deux est utilisĂ©e en 32 bits dans les versions rĂ©centes. Oui, câest chelou đ.
Les trois derniĂšres corbeilles ne sont gĂ©nĂ©ralement jamais utilisĂ©es. Pourtant, la macro MAX_FAST_SIZE vaut bien 0x50 et 0xa0 en 32 et 64 bits đ¶. Pour comprendre pourquoi un tel comportement est observĂ©, analysons de plus prĂšs la maniĂšre dont est utilisĂ©e la fonction get_max_fast lorsquâun bloc est libĂ©rĂ© via free :
1
2
3
4
5
6
7
8
9
10
11
static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
// (...)
/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
// (...)
Avant dâinsĂ©rer un bloc libre dans une fastbin, sa taille est comparĂ©e au rĂ©sultat de get_max_fast. Cette fonction ne fait que retourner la valeur de la variable globale global_max_fast. Que vaut cette variable ? OĂč a-t-elle Ă©tĂ© initialisĂ©e pour la premiĂšre fois ?
En utilisant le systĂšme de recherche de rĂ©fĂ©rence de bootlin, nous constatons que global_max_fast est initialisĂ©e lors de lâappel de set_max_fast (DEFAULT_MXFAST); .
Quant Ă la macro DEFAULT_MXFAST, elle vaut 0x80 en 64 bits et 0x40 en 32 bits. Ainsi, la prĂ©cĂ©dente vĂ©rification avant dâinsĂ©rer un bloc dans une fastbin est Ă©quivalente Ă :
- en 32 bits :
if ((unsigned long)(size) <= 0x40); - en 64 bits :
if ((unsigned long)(size) <= 0x80).
Câest pourquoi les 3 derniĂšres fastbins ne sont pas utilisĂ©es.
Ă quoi ça sert alors dâen mettre 10 si seulement 7 sont utilisĂ©es ?
HonnĂȘtement je nâen ai aucune idĂ©e đ
. Peut-ĂȘtre par souci de performance sachant que le dĂ©veloppeur (ou hackeur đ) peut modifier la valeur de global_max_fast afin de pouvoir utiliser les trois derniĂšres fastbins.
De temps Ă autre, nâhĂ©sitez pas Ă jeter un Ćil au code source de la glibc. Ce nâest pas trĂšs compliquĂ© Ă comprendre et cela permet de mieux cerner le fonctionnement du tas ainsi que des vulnĂ©rabilitĂ©s qui ont pu ĂȘtre prĂ©sentes au fil des versions.
Organisation des corbeilles
Liste chaßnée de type LIFO
Bonne nouvelle : si vous avez bien saisi le fonctionnement du tcache vous nâaurez aucun souci Ă comprendre celui des fastbins đ.
En effet, ce sont :
- des listes LIFO : le dernier bloc libre insĂ©rĂ© sera le premier Ă ĂȘtre utilisĂ© ;
- des listes chaßnées : chaque bloc pointe vers le suivant.
Néanmoins, quelques différences subsistent :
- il nây a pas de limites de blocs libres dans une
fastbin; - le membre
fdpointe vers le champprev_sizedu prochain bloc ; - câest lâarĂšne qui gĂšre directement les
fastbins.
Imaginons que trois blocs de 0x20 octets A, B et C soient libérés respectivement dans cet ordre. En supposant que ces blocs aillent directement dans une fastbin, et non dans le tcache, la liste chaßnée a cette forme :
Le champ
prev_sizenâest pas utilisĂ© en tant que tel dans lesfastbins. La seule raison pour laquelle nous lâavons reprĂ©sentĂ© dans chaque bloc est que le champfddâun bloc libre dâunefastbinpointe vers lâadresse du bloc suivant, ce qui revient Ă pointer versprev_size.
Gestion des différentes fastbins
Je vous propose de compiler ce programme afin dâavoir une vue panoramique sur ces fastbins dans gdb :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdlib.h>
int main()
{
void *a = malloc(0x20-8);
void *b = malloc(0x30-8);
void *c = malloc(0x40-8);
void *d = malloc(0x50-8);
void *e = malloc(0x60-8);
void *f = malloc(0x70-8);
void *g = malloc(0x80-8);
void *h = malloc(0x90-8);
void *temp = malloc(1);
free(a);
free(b);
free(c);
free(d);
free(e);
free(f);
free(g);
free(h);
return 0;
}
Pour éviter que les blocs libres aillent dans le
tcache, compilez le programme avec une version de la glibc antĂ©rieure Ă la version 2.26. En lâoccurrence, nous avons utilisĂ© la version2.24-9ubuntu2.2_amd64.
AccĂšs au conteneur Docker :
- âŹïž TĂ©lĂ©chargement : pwn-fastbin-exemple-1.zip
- đ SHA256 & Analyse Virus Total : 66b9d5fc955217436b8e767a06b711bbbb8e6a9162cef2e9add50b809ff53d04
- âïž Construction et lancement du conteneur :
1
2
docker build -t pwn-fastbin-exemple-1 .
docker run -it --rm -p 1234:1234 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined pwn-fastbin-exemple-1
Le programme, compilé en 64 bits, réalise ceci :
- allocation de 8 blocs par pas de
0x10octets ; - allocation dâun bloc
tempafin dâĂ©viter une consolidation avec le bloc du sommet lors de la libĂ©ration du bloch; - libĂ©ration des 8 premiers blocs.
Ouvrons le programme avec gdb-gef++ et exĂ©cutons le programme jusquâau return 0; du main.
Affichons le contenu de toutes les corbeilles avec la commande bins :
Comme cela a Ă©tĂ© expliquĂ© prĂ©cĂ©demment, seules les 7 premiĂšres fastbins sont rĂ©ellement utilisĂ©es ; le 8Ăšme bloc est envoyĂ© dans la unsorted bin, une corbeille fourre-tout dont on aura lâoccasion de parler ultĂ©rieurement.
Voyons Ă prĂ©sent comment sont gĂ©rĂ©es les fastbins au niveau de lâarĂšne grĂące Ă la commande arena :
Rien de bien compliquĂ© : fastbinsY est un tableau qui pointe vers le premier bloc de chaque fastbin. Les 3 derniĂšres nâĂ©tant pas utilisĂ©es, leur contenu dans le tableau fastbinsY est nul.
Que représente le
YdansfastbinsY?
Je nâen ai aucune idĂ©e đ , si vous avez la rĂ©ponse, faites-moi signe.
Structure et mĂ©tadonnĂ©es dâun bloc issu dâune fastbin
Les schémas utilisés ci-dessous représentent des blocs de
0x20mais ce nâest Ă©videmment pas la seule taille gĂ©rĂ©e par lesfastbins.
La structure des blocs libres des fastbins est plus simple que ceux du tcache car le champ bk nâest jamais utilisĂ©.
Avant la version 2.32
Avec :
fd: pointeur vers le prochain bloc de la mĂȘme corbeille de typefastbin.
Le champ
prev_sizeest reprĂ©sentĂ© ici mais nâest pas utilisĂ© par lesfastbins.
AprĂšs la version 2.32
Les fastbins nâont pas Ă©chappĂ© au systĂšme de safe linking. Le champ fd est donc âchiffrĂ©â via la macro PROTECT_PTR :
Avec :
fd': résultat issu de la macroPROTECT_PTRappliquée sur la valeur initiale defdainsi que son adresse&fd.





