Bins & Memory Allocations
Reading time: 23 minutes
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Soutenir HackTricks
- Vรฉrifiez les plans d'abonnement !
- Rejoignez le ๐ฌ groupe Discord ou le groupe telegram ou suivez nous sur Twitter ๐ฆ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dรฉpรดts github.
Informations de base
Afin d'amรฉliorer l'efficacitรฉ de la maniรจre dont les chunks sont stockรฉs, chaque chunk n'est pas seulement dans une liste chaรฎnรฉe, mais il existe plusieurs types. Ce sont les bins et il y a 5 types de bins : 62 petits bins, 63 grands bins, 1 bin non triรฉ, 10 fast bins et 64 tcache bins par thread.
L'adresse initiale de chaque bin non triรฉ, petit et grand est ร l'intรฉrieur du mรชme tableau. L'index 0 est inutilisรฉ, 1 est le bin non triรฉ, les bins 2-64 sont des petits bins et les bins 65-127 sont des grands bins.
Tcache (Cache par thread) Bins
Bien que les threads essaient d'avoir leur propre heap (voir Arenas et Subheaps), il est possible qu'un processus avec beaucoup de threads (comme un serveur web) finisse par partager le heap avec d'autres threads. Dans ce cas, la solution principale est l'utilisation de lockers, ce qui pourrait ralentir considรฉrablement les threads.
Par consรฉquent, un tcache est similaire ร un fast bin par thread en ce sens que c'est une liste chaรฎnรฉe simple qui ne fusionne pas les chunks. Chaque thread a 64 tcache bins chaรฎnรฉs. Chaque bin peut avoir un maximum de 7 chunks de mรชme taille allant de 24 ร 1032B sur les systรจmes 64 bits et de 12 ร 516B sur les systรจmes 32 bits.
Lorsqu'un thread libรจre un chunk, s'il n'est pas trop grand pour รชtre allouรฉ dans le tcache et que le bin tcache respectif n'est pas plein (dรฉjร 7 chunks), il sera allouรฉ lร -dedans. S'il ne peut pas aller dans le tcache, il devra attendre le verrou du heap pour pouvoir effectuer l'opรฉration de libรฉration globalement.
Lorsqu'un chunk est allouรฉ, s'il y a un chunk libre de la taille nรฉcessaire dans le Tcache, il l'utilisera, sinon, il devra attendre le verrou du heap pour pouvoir en trouver un dans les bins globaux ou en crรฉer un nouveau.
Il y a aussi une optimisation, dans ce cas, tout en ayant le verrou du heap, le thread remplira son Tcache avec des chunks de heap (7) de la taille demandรฉe, donc s'il en a besoin de plus, il les trouvera dans le Tcache.
Ajouter un exemple de chunk tcache
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunk;
chunk = malloc(24);
printf("Address of the chunk: %p\n", (void *)chunk);
gets(chunk);
free(chunk);
return 0;
}
Compilez-le et dรฉboguez-le avec un point d'arrรชt dans l'opcode ret de la fonction main. Ensuite, avec gef, vous pouvez voir le tcache bin en cours d'utilisation :
gefโค heap bins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Tcachebins for thread 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tcachebins[idx=0, size=0x20, count=1] โ Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Structures et Fonctions Tcache
Dans le code suivant, il est possible de voir les max bins et chunks par index, la structure tcache_entry
crรฉรฉe pour รฉviter les doubles libรฉrations et tcache_perthread_struct
, une structure que chaque thread utilise pour stocker les adresses de chaque index du bin.
tcache_entry
et tcache_perthread_struct
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c
/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */
# define TCACHE_MAX_BINS 64
# define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1)
/* Only used to pre-fill the tunables. */
# define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)
/* When "x" is from chunksize(). */
# define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
/* When "x" is a user-provided size. */
# define usize2tidx(x) csize2tidx (request2size (x))
/* With rounding and alignment, the bins are...
idx 0 bytes 0..24 (64-bit) or 0..12 (32-bit)
idx 1 bytes 25..40 or 13..20
idx 2 bytes 41..56 or 21..28
etc. */
/* This is another arbitrary limit, which tunables can change. Each
tcache bin will hold at most this number of chunks. */
# define TCACHE_FILL_COUNT 7
/* Maximum chunks in tcache bins for tunables. This value must fit the range
of tcache->counts[] entries, else they may overflow. */
# define MAX_TCACHE_COUNT UINT16_MAX
[...]
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
uintptr_t key;
} tcache_entry;
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
La fonction __tcache_init
est la fonction qui crรฉe et alloue l'espace pour l'objet tcache_perthread_struct
.
code tcache_init
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3241C1-L3274C2
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory
- in which case, we just keep trying later. However, we
typically do this very early, so either there is sufficient
memory, or there isn't enough memory to do non-trivial
allocations anyway. */
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
Indexes Tcache
Le tcache a plusieurs bins en fonction de la taille et des pointeurs initiaux vers le premier chunk de chaque index et la quantitรฉ de chunks par index sont situรฉs ร l'intรฉrieur d'un chunk. Cela signifie qu'en localisant le chunk avec ces informations (gรฉnรฉralement le premier), il est possible de trouver tous les points initiaux du tcache et la quantitรฉ de chunks Tcache.
Fast bins
Les fast bins sont conรงus pour accรฉlรฉrer l'allocation de mรฉmoire pour de petits chunks en gardant les chunks rรฉcemment libรฉrรฉs dans une structure d'accรจs rapide. Ces bins utilisent une approche Last-In, First-Out (LIFO), ce qui signifie que le chunk le plus rรฉcemment libรฉrรฉ est le premier ร รชtre rรฉutilisรฉ lorsqu'il y a une nouvelle demande d'allocation. Ce comportement est avantageux pour la vitesse, car il est plus rapide d'insรฉrer et de retirer du haut d'une pile (LIFO) par rapport ร une file d'attente (FIFO).
De plus, les fast bins utilisent des listes simplement chaรฎnรฉes, et non des listes doublement chaรฎnรฉes, ce qui amรฉliore encore la vitesse. รtant donnรฉ que les chunks dans les fast bins ne sont pas fusionnรฉs avec leurs voisins, il n'est pas nรฉcessaire d'avoir une structure complexe permettant de retirer du milieu. Une liste simplement chaรฎnรฉe est plus simple et plus rapide pour ces opรฉrations.
En gros, ce qui se passe ici, c'est que l'en-tรชte (le pointeur vers le premier chunk ร vรฉrifier) pointe toujours vers le dernier chunk libรฉrรฉ de cette taille. Donc :
- Lorsqu'un nouveau chunk est allouรฉ de cette taille, l'en-tรชte pointe vers un chunk libre ร utiliser. Comme ce chunk libre pointe vers le suivant ร utiliser, cette adresse est stockรฉe dans l'en-tรชte afin que la prochaine allocation sache oรน obtenir un chunk disponible.
- Lorsqu'un chunk est libรฉrรฉ, le chunk libre enregistrera l'adresse du chunk actuellement disponible et l'adresse de ce chunk nouvellement libรฉrรฉ sera mise dans l'en-tรชte.
La taille maximale d'une liste chaรฎnรฉe est 0x80
et elles sont organisรฉes de sorte qu'un chunk de taille 0x20
sera ร l'index 0
, un chunk de taille 0x30
serait ร l'index 1
...
caution
Les chunks dans les fast bins ne sont pas dรฉfinis comme disponibles, donc ils sont conservรฉs comme chunks de fast bin pendant un certain temps au lieu de pouvoir fusionner avec d'autres chunks libres les entourant.
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
/*
Fastbins
An array of lists holding recently freed small chunks. Fastbins
are not doubly linked. It is faster to single-link them, and
since chunks are never removed from the middles of these lists,
double linking is not necessary. Also, unlike regular bins, they
are not even processed in FIFO order (they use faster LIFO) since
ordering doesn't much matter in the transient contexts in which
fastbins are normally used.
Chunks in fastbins keep their inuse bit set, so they cannot
be consolidated with other free chunks. malloc_consolidate
releases all chunks in fastbins and consolidates them with
other free chunks.
*/
typedef struct malloc_chunk *mfastbinptr;
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])
/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)
Ajouter un exemple de chunk fastbin
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[8];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 8; i++) {
chunks[i] = malloc(24);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
return 0;
}
Notez comment nous allouons et libรฉrons 8 morceaux de la mรชme taille afin qu'ils remplissent le tcache et que le huitiรจme soit stockรฉ dans le fast chunk.
Compilez-le et dรฉboguez-le avec un point d'arrรชt dans l'opcode ret
de la fonction main
. Ensuite, avec gef
, vous pouvez voir que le tcache bin est plein et qu'un morceau est dans le fast bin :
gefโค heap bins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Tcachebins for thread 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tcachebins[idx=0, size=0x20, count=7] โ Chunk(addr=0xaaaaaaac1770, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac1750, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac1730, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac1710, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac16f0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac16d0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Fastbins for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fastbins[idx=0, size=0x20] โ Chunk(addr=0xaaaaaaac1790, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Fastbins[idx=1, size=0x30] 0x00
Bin non triรฉ
Le bin non triรฉ est un cache utilisรฉ par le gestionnaire de tas pour rendre l'allocation de mรฉmoire plus rapide. Voici comment cela fonctionne : Lorsqu'un programme libรจre un morceau, et si ce morceau ne peut pas รชtre allouรฉ dans un tcache ou un fast bin et ne se heurte pas au top chunk, le gestionnaire de tas ne le place pas immรฉdiatement dans un bin spรฉcifique, petit ou grand. Au lieu de cela, il essaie d'abord de le fusionner avec des morceaux libres voisins pour crรฉer un bloc de mรฉmoire libre plus grand. Ensuite, il place ce nouveau morceau dans un bin gรฉnรฉral appelรฉ "bin non triรฉ".
Lorsque qu'un programme demande de la mรฉmoire, le gestionnaire de tas vรฉrifie le bin non triรฉ pour voir s'il y a un morceau de taille suffisante. S'il en trouve un, il l'utilise immรฉdiatement. S'il ne trouve pas de morceau appropriรฉ dans le bin non triรฉ, il dรฉplace tous les morceaux de cette liste vers leurs bins correspondants, soit petits soit grands, en fonction de leur taille.
Notez que si un morceau plus grand est divisรฉ en 2 moitiรฉs et que le reste est plus grand que MINSIZE, il sera remis dans le bin non triรฉ.
Ainsi, le bin non triรฉ est un moyen d'accรฉlรฉrer l'allocation de mรฉmoire en rรฉutilisant rapidement la mรฉmoire rรฉcemment libรฉrรฉe et en rรฉduisant le besoin de recherches et de fusions chronophages.
caution
Notez que mรชme si les morceaux appartiennent ร diffรฉrentes catรฉgories, si un morceau disponible se heurte ร un autre morceau disponible (mรชme s'ils appartiennent ร l'origine ร des bins diffรฉrents), ils seront fusionnรฉs.
Ajouter un exemple de morceau non triรฉ
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[9];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 9; i++) {
chunks[i] = malloc(0x100);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
return 0;
}
Notez comment nous allouons et libรฉrons 9 morceaux de la mรชme taille afin qu'ils remplissent le tcache et le huitiรจme est stockรฉ dans le tas non triรฉ car il est trop grand pour le fastbin et le neuviรจme n'est pas libรฉrรฉ, donc le neuviรจme et le huitiรจme ne sont pas fusionnรฉs avec le morceau supรฉrieur.
Compilez-le et dรฉboguez-le avec un point d'arrรชt dans l'opcode ret
de la fonction main
. Ensuite, avec gef
, vous pouvez voir que le tas tcache est plein et qu'un morceau est dans le tas non triรฉ :
gefโค heap bins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Tcachebins for thread 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tcachebins[idx=15, size=0x110, count=7] โ Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Fastbins for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Unsorted Bin for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+] unsorted_bins[0]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
โ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in unsorted bin.
Petits Bins
Les petits bins sont plus rapides que les grands bins mais plus lents que les bins rapides.
Chaque bin des 62 aura des morceaux de la mรชme taille : 16, 24, ... (avec une taille maximale de 504 octets en 32 bits et 1024 en 64 bits). Cela aide ร la rapiditรฉ pour trouver le bin oรน un espace doit รชtre allouรฉ et pour insรฉrer et supprimer des entrรฉes dans ces listes.
Voici comment la taille du petit bin est calculรฉe en fonction de l'index du bin :
- Taille minimale : 2*4*index (par exemple, index 5 -> 40)
- Taille maximale : 2*8*index (par exemple, index 5 -> 80)
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > CHUNK_HDR_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
#define in_smallbin_range(sz) \
((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
#define smallbin_index(sz) \
((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))\
+ SMALLBIN_CORRECTION)
Fonction pour choisir entre de petits et de grands bins :
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
Ajouter un petit exemple de morceau
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[10];
int i;
// Loop to allocate memory 8 times
for (i = 0; i < 9; i++) {
chunks[i] = malloc(0x100);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i < 8; i++) {
free(chunks[i]);
}
chunks[9] = malloc(0x110);
return 0;
}
Notez comment nous allouons et libรฉrons 9 morceaux de la mรชme taille afin qu'ils remplissent le tcache et le huitiรจme est stockรฉ dans le tas non triรฉ car il est trop grand pour le fastbin et le neuviรจme n'est pas libรฉrรฉ, donc le neuviรจme et le huitiรจme ne sont pas fusionnรฉs avec le morceau supรฉrieur. Ensuite, nous allouons un morceau plus grand de 0x110, ce qui fait que le morceau dans le tas non triรฉ va dans le petit tas.
Compilez-le et dรฉboguez-le avec un point d'arrรชt dans l'opcode ret
de la fonction main
. Ensuite, avec gef
, vous pouvez voir que le tas tcache est plein et qu'un morceau est dans le petit tas :
gefโค heap bins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Tcachebins for thread 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tcachebins[idx=15, size=0x110, count=7] โ Chunk(addr=0xaaaaaaac1d10, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac1c00, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac1af0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac19e0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac18d0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac17c0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ Chunk(addr=0xaaaaaaac12a0, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Fastbins for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Unsorted Bin for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+] Found 0 chunks in unsorted bin.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Small Bins for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+] small_bins[16]: fw=0xaaaaaaac1e10, bk=0xaaaaaaac1e10
โ Chunk(addr=0xaaaaaaac1e20, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in 1 small non-empty bins.
Bins larges
Contrairement aux petits bins, qui gรจrent des morceaux de tailles fixes, chaque bin large gรจre une gamme de tailles de morceaux. Cela est plus flexible, permettant au systรจme d'accommoder diverses tailles sans avoir besoin d'un bin sรฉparรฉ pour chaque taille.
Dans un allocateur de mรฉmoire, les bins larges commencent lร oรน les petits bins se terminent. Les plages pour les bins larges deviennent progressivement plus grandes, ce qui signifie que le premier bin peut couvrir des morceaux de 512 ร 576 octets, tandis que le suivant couvre de 576 ร 640 octets. Ce schรฉma se poursuit, le plus grand bin contenant tous les morceaux au-dessus de 1 Mo.
Les bins larges sont plus lents ร fonctionner par rapport aux petits bins car ils doivent trier et rechercher ร travers une liste de tailles de morceaux variรฉes pour trouver le meilleur ajustement pour une allocation. Lorsqu'un morceau est insรฉrรฉ dans un bin large, il doit รชtre triรฉ, et lorsque la mรฉmoire est allouรฉe, le systรจme doit trouver le bon morceau. Ce travail supplรฉmentaire les rend plus lents, mais comme les grandes allocations sont moins courantes que les petites, c'est un compromis acceptable.
Il y a :
- 32 bins de plage 64B (collision avec les petits bins)
- 16 bins de plage 512B (collision avec les petits bins)
- 8 bins de plage 4096B (partiellement en collision avec les petits bins)
- 4 bins de plage 32768B
- 2 bins de plage 262144B
- 1 bin pour les tailles restantes
Code des tailles de bin large
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
#define largebin_index_32(sz) \
(((((unsigned long) (sz)) >> 6) <= 38) ? 56 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
#define largebin_index_32_big(sz) \
(((((unsigned long) (sz)) >> 6) <= 45) ? 49 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
// XXX It remains to be seen whether it is good to keep the widths of
// XXX the buckets the same or whether it should be scaled by a factor
// XXX of two as well.
#define largebin_index_64(sz) \
(((((unsigned long) (sz)) >> 6) <= 48) ? 48 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
#define largebin_index(sz) \
(SIZE_SZ == 8 ? largebin_index_64 (sz) \
: MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz) \
: largebin_index_32 (sz))
Ajouter un exemple de gros morceau
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[2];
chunks[0] = malloc(0x1500);
chunks[1] = malloc(0x1500);
free(chunks[0]);
chunks[0] = malloc(0x2000);
return 0;
}
2 grandes allocations sont effectuรฉes, puis l'une est libรฉrรฉe (la plaรงant dans le bin non triรฉ) et une plus grande allocation est faite (dรฉplaรงant la libรฉrรฉe du bin non triรฉ vers le bin large).
Compilez-le et dรฉboguez-le avec un point d'arrรชt dans l'opcode ret
de la fonction main
. Ensuite, avec gef
, vous pouvez voir que le bin tcache est plein et qu'un chunk est dans le bin large :
gefโค heap bin
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Tcachebins for thread 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
All tcachebins are empty
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Fastbins for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Unsorted Bin for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+] Found 0 chunks in unsorted bin.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Small Bins for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+] Found 0 chunks in 0 small non-empty bins.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Large Bins for arena at 0xfffff7f90b00 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+] large_bins[100]: fw=0xaaaaaaac1290, bk=0xaaaaaaac1290
โ Chunk(addr=0xaaaaaaac12a0, size=0x1510, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in 1 large non-empty bins.
Partie supรฉrieure
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
/*
Top
The top-most available chunk (i.e., the one bordering the end of
available memory) is treated specially. It is never included in
any bin, is used only if no other chunk is available, and is
released back to the system if it is very large (see
M_TRIM_THRESHOLD). Because top initially
points to its own bin with initial zero size, thus forcing
extension on the first malloc request, we avoid having any special
code in malloc to check whether it even exists yet. But we still
need to do so when getting memory from system, so we make
initial_top treat the bin as a legal but unusable chunk during the
interval between initialization and the first call to
sysmalloc. (This is somewhat delicate, since it relies on
the 2 preceding words to be zero during this interval as well.)
*/
/* Conveniently, the unsorted bin can be used as dummy top on first call */
#define initial_top(M) (unsorted_chunks (M))
En gros, il s'agit d'un morceau contenant tout le tas actuellement disponible. Lorsqu'un malloc est effectuรฉ, s'il n'y a pas de morceau libre disponible ร utiliser, ce morceau supรฉrieur rรฉduira sa taille pour donner l'espace nรฉcessaire.
Le pointeur vers le Top Chunk est stockรฉ dans la structure malloc_state
.
De plus, au dรฉbut, il est possible d'utiliser le morceau non triรฉ comme le morceau supรฉrieur.
Observez l'exemple du Top Chunk
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunk;
chunk = malloc(24);
printf("Address of the chunk: %p\n", (void *)chunk);
gets(chunk);
return 0;
}
Aprรจs avoir compilรฉ et dรฉboguรฉ avec un point d'arrรชt dans l'opcode ret
de main
, j'ai vu que le malloc a renvoyรฉ l'adresse 0xaaaaaaac12a0
et voici les chunks :
gefโค heap chunks
Chunk(addr=0xaaaaaaac1010, size=0x290, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac1010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
Chunk(addr=0xaaaaaaac12a0, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12a0 41 41 41 41 41 41 41 00 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac12c0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac12c0 41 64 64 72 65 73 73 20 6f 66 20 74 68 65 20 63 Address of the c]
Chunk(addr=0xaaaaaaac16d0, size=0x410, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[0x0000aaaaaaac16d0 41 41 41 41 41 41 41 0a 00 00 00 00 00 00 00 00 AAAAAAA.........]
Chunk(addr=0xaaaaaaac1ae0, size=0x20530, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ top chunk
Oรน l'on peut voir que le top chunk est ร l'adresse 0xaaaaaaac1ae0
. Ce n'est pas une surprise car le dernier chunk allouรฉ รฉtait ร 0xaaaaaaac12a0
avec une taille de 0x410
et 0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0
.
Il est รฉgalement possible de voir la longueur du Top chunk sur son en-tรชte de chunk :
gefโค x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000
0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000
Dernier Reste
Lorsque malloc est utilisรฉ et qu'un morceau est divisรฉ (ร partir du tas non triรฉ ou du morceau supรฉrieur par exemple), le morceau crรฉรฉ ร partir du reste du morceau divisรฉ est appelรฉ Dernier Reste et son pointeur est stockรฉ dans la structure malloc_state
.
Flux d'Allocation
Consultez :
Flux de Libรฉration
Consultez :
Vรฉrifications de Sรฉcuritรฉ des Fonctions de Tas
Vรฉrifiez les vรฉrifications de sรฉcuritรฉ effectuรฉes par les fonctions largement utilisรฉes dans le tas dans :
Heap Functions Security Checks
Rรฉfรฉrences
- https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/
- https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/
- https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/core_functions
- https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/
tip
Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)
Soutenir HackTricks
- Vรฉrifiez les plans d'abonnement !
- Rejoignez le ๐ฌ groupe Discord ou le groupe telegram ou suivez nous sur Twitter ๐ฆ @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dรฉpรดts github.