Bins & Memory Allocations
Reading time: 22 minutes
tip
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al π¬ gruppo Discord o al gruppo telegram o seguici su Twitter π¦ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.
Informazioni di Base
Per migliorare l'efficienza su come i chunk sono memorizzati, ogni chunk non Γ¨ solo in una lista collegata, ma ci sono diversi tipi. Questi sono i bins e ci sono 5 tipi di bins: 62 small bins, 63 large bins, 1 unsorted bin, 10 fast bins e 64 tcache bins per thread.
L'indirizzo iniziale di ciascun unsorted, small e large bins Γ¨ all'interno dello stesso array. L'indice 0 Γ¨ inutilizzato, 1 Γ¨ l'unsorted bin, i bins 2-64 sono small bins e i bins 65-127 sono large bins.
Tcache (Cache per Thread) Bins
Anche se i thread cercano di avere il proprio heap (vedi Arenas e Subheaps), c'Γ¨ la possibilitΓ che un processo con molti thread (come un server web) condividerΓ l'heap con altri thread. In questo caso, la soluzione principale Γ¨ l'uso di lockers, che potrebbero rallentare significativamente i thread.
Pertanto, un tcache Γ¨ simile a un fast bin per thread nel modo in cui Γ¨ una singola lista collegata che non unisce i chunk. Ogni thread ha 64 tcache bins collegati singolarmente. Ogni bin puΓ² avere un massimo di 7 chunk della stessa dimensione che vanno da 24 a 1032B su sistemi a 64 bit e da 12 a 516B su sistemi a 32 bit.
Quando un thread libera un chunk, se non è troppo grande per essere allocato nel tcache e il rispettivo tcache bin non è pieno (già 7 chunk), verrà allocato lì. Se non può andare nel tcache, dovrà aspettare il lock dell'heap per poter eseguire l'operazione di liberazione globalmente.
Quando un chunk Γ¨ allocato, se c'Γ¨ un chunk libero della dimensione necessaria nel Tcache lo utilizzerΓ , altrimenti dovrΓ aspettare il lock dell'heap per poter trovarne uno nei bins globali o crearne uno nuovo.
C'è anche un'ottimizzazione, in questo caso, mentre ha il lock dell'heap, il thread riempirà il suo Tcache con chunk dell'heap (7) della dimensione richiesta, così nel caso ne avesse bisogno di più, li troverà nel Tcache.
Aggiungi un esempio di 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;
}
Compilalo e debugga con un breakpoint nell'istruzione ret della funzione main. Poi con gef puoi vedere il tcache bin in uso:
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)
Strutture e Funzioni Tcache
Nel seguente codice Γ¨ possibile vedere i max bins e i chunks per index, la struttura tcache_entry
creata per evitare doppie liberazioni e tcache_perthread_struct
, una struttura che ogni thread utilizza per memorizzare gli indirizzi di ciascun indice del bin.
tcache_entry
e 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 funzione __tcache_init
Γ¨ la funzione che crea e alloca lo spazio per l'oggetto tcache_perthread_struct
.
codice 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));
}
}
Indici Tcache
Il tcache ha diversi bin a seconda della dimensione e i puntatori iniziali al primo chunk di ciascun indice e la quantitΓ di chunk per indice si trovano all'interno di un chunk. Questo significa che localizzando il chunk con queste informazioni (di solito il primo), Γ¨ possibile trovare tutti i punti iniziali del tcache e la quantitΓ di chunk Tcache.
Fast bins
I fast bins sono progettati per accelerare l'allocazione di memoria per piccoli chunk mantenendo i chunk recentemente liberati in una struttura di accesso rapido. Questi bin utilizzano un approccio Last-In, First-Out (LIFO), il che significa che il chunk liberato piΓΉ recentemente Γ¨ il primo a essere riutilizzato quando c'Γ¨ una nuova richiesta di allocazione. Questo comportamento Γ¨ vantaggioso per la velocitΓ , poichΓ© Γ¨ piΓΉ veloce inserire e rimuovere dalla cima di uno stack (LIFO) rispetto a una coda (FIFO).
Inoltre, i fast bins utilizzano liste collegate semplici, non doppie, il che migliora ulteriormente la velocitΓ . PoichΓ© i chunk nei fast bins non vengono uniti con i vicini, non c'Γ¨ bisogno di una struttura complessa che consenta la rimozione dal mezzo. Una lista collegata semplice Γ¨ piΓΉ semplice e veloce per queste operazioni.
Fondamentalmente, ciΓ² che accade qui Γ¨ che l'intestazione (il puntatore al primo chunk da controllare) punta sempre all'ultimo chunk liberato di quella dimensione. Quindi:
- Quando un nuovo chunk viene allocato di quella dimensione, l'intestazione punta a un chunk libero da utilizzare. PoichΓ© questo chunk libero punta al successivo da utilizzare, questo indirizzo viene memorizzato nell'intestazione in modo che la successiva allocazione sappia dove ottenere un chunk disponibile
- Quando un chunk viene liberato, il chunk libero salverΓ l'indirizzo del chunk attualmente disponibile e l'indirizzo di questo chunk appena liberato verrΓ messo nell'intestazione
La dimensione massima di una lista collegata Γ¨ 0x80
e sono organizzate in modo che un chunk di dimensione 0x20
si trovi nell'indice 0
, un chunk di dimensione 0x30
si troverebbe nell'indice 1
...
caution
I chunk nei fast bins non sono impostati come disponibili, quindi vengono mantenuti come chunk di fast bin per un certo periodo invece di poter essere uniti con altri chunk liberi circostanti.
// 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)
Aggiungi un esempio di 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;
}
Nota come allochiamo e liberiamo 8 chunk della stessa dimensione in modo che riempiano il tcache e l'ottavo sia memorizzato nel fast chunk.
Compilalo e debuggalo con un breakpoint nell'operazione ret
dalla funzione main
. Poi con gef
puoi vedere che il tcache bin Γ¨ pieno e un chunk Γ¨ nel 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
Unsorted bin
Il bin non ordinato Γ¨ una cache utilizzata dal gestore della memoria per rendere piΓΉ veloce l'allocazione della memoria. Ecco come funziona: Quando un programma libera un chunk, e se questo chunk non puΓ² essere allocato in un tcache o fast bin e non collide con il top chunk, il gestore della memoria non lo mette immediatamente in un bin specifico piccolo o grande. Invece, prima cerca di fonderlo con eventuali chunk liberi adiacenti per creare un blocco piΓΉ grande di memoria libera. Poi, posiziona questo nuovo chunk in un bin generale chiamato "unsorted bin."
Quando un programma richiede memoria, il gestore della memoria controlla il bin non ordinato per vedere se c'Γ¨ un chunk di dimensioni sufficienti. Se ne trova uno, lo utilizza immediatamente. Se non trova un chunk adatto nel bin non ordinato, sposta tutti i chunk in questa lista nei loro bin corrispondenti, sia piccoli che grandi, in base alle loro dimensioni.
Nota che se un chunk piΓΉ grande viene diviso in 2 metΓ e il resto Γ¨ piΓΉ grande di MINSIZE, verrΓ rimesso nel bin non ordinato.
Quindi, il bin non ordinato Γ¨ un modo per accelerare l'allocazione della memoria riutilizzando rapidamente la memoria recentemente liberata e riducendo la necessitΓ di ricerche e fusioni che richiedono tempo.
caution
Nota che anche se i chunk appartengono a categorie diverse, se un chunk disponibile collide con un altro chunk disponibile (anche se originariamente appartengono a bin diversi), verranno fusi.
Aggiungi un esempio di chunk non ordinato
#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;
}
Nota come allochiamo e liberiamo 9 chunk della stessa dimensione in modo che riempiano il tcache e l'ottavo Γ¨ memorizzato nel bin non ordinato perchΓ© Γ¨ troppo grande per il fastbin e il nono non Γ¨ liberato, quindi il nono e l'ottavo non vengono uniti con il chunk superiore.
Compilalo e debuggalo con un breakpoint nell'operazione ret
dalla funzione main
. Poi con gef
puoi vedere che il bin del tcache Γ¨ pieno e un chunk Γ¨ nel bin non ordinato:
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.
Small Bins
I piccoli bin sono piΓΉ veloci dei grandi bin ma piΓΉ lenti dei fast bin.
Ogni bin dei 62 avrΓ chunk della stessa dimensione: 16, 24, ... (con una dimensione massima di 504 byte in 32 bit e 1024 in 64 bit). Questo aiuta nella velocitΓ di trovare il bin dove dovrebbe essere allocato uno spazio e nell'inserimento e rimozione di voci in queste liste.
Questo Γ¨ come viene calcolata la dimensione del piccolo bin in base all'indice del bin:
- Dimensione piΓΉ piccola: 2*4*indice (ad es. indice 5 -> 40)
- Dimensione piΓΉ grande: 2*8*indice (ad es. indice 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)
Funzione per scegliere tra piccoli e grandi bin:
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
Aggiungi un piccolo esempio di chunk
#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;
}
Nota come allochiamo e liberiamo 9 chunk della stessa dimensione in modo che riempiano il tcache e l'ottavo Γ¨ memorizzato nel bin non ordinato perchΓ© Γ¨ troppo grande per il fastbin e il nono non Γ¨ liberato, quindi il nono e l'ottavo non vengono uniti con il chunk superiore. Poi allochiamo un chunk piΓΉ grande di 0x110 che fa andare il chunk nel bin non ordinato nel small bin.
Compilalo e debuggalo con un breakpoint nell'operazione ret
dalla funzione main
. Poi con gef
puoi vedere che il bin tcache Γ¨ pieno e un chunk Γ¨ nel small bin:
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.
Grandi bin
A differenza dei piccoli bin, che gestiscono chunk di dimensioni fisse, ogni grande bin gestisce un intervallo di dimensioni dei chunk. Questo Γ¨ piΓΉ flessibile, permettendo al sistema di accogliere varie dimensioni senza la necessitΓ di un bin separato per ogni dimensione.
In un allocatore di memoria, i grandi bin iniziano dove finiscono i piccoli bin. Gli intervalli per i grandi bin crescono progressivamente, il che significa che il primo bin potrebbe coprire chunk da 512 a 576 byte, mentre il successivo copre da 576 a 640 byte. Questo schema continua, con il bin piΓΉ grande che contiene tutti i chunk sopra 1MB.
I grandi bin sono piΓΉ lenti da operare rispetto ai piccoli bin perchΓ© devono ordinare e cercare attraverso un elenco di dimensioni di chunk variabili per trovare la migliore corrispondenza per un'allocazione. Quando un chunk viene inserito in un grande bin, deve essere ordinato, e quando la memoria viene allocata, il sistema deve trovare il chunk giusto. Questo lavoro extra li rende piΓΉ lenti, ma poichΓ© le allocazioni grandi sono meno comuni di quelle piccole, Γ¨ un compromesso accettabile.
Ci sono:
- 32 bin di intervallo 64B (collidono con i piccoli bin)
- 16 bin di intervallo 512B (collidono con i piccoli bin)
- 8 bin di intervallo 4096B (parzialmente collidono con i piccoli bin)
- 4 bin di intervallo 32768B
- 2 bin di intervallo 262144B
- 1 bin per le dimensioni rimanenti
Codice delle dimensioni dei grandi bin
// 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))
Aggiungi un esempio di grande dimensione
#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;
}
Vengono eseguite 2 allocazioni grandi, poi una viene liberata (mettendola nel bin non ordinato) e viene effettuata un'allocazione piΓΉ grande (spostando quella libera dal bin non ordinato al bin grande).
Compilalo e debugga con un breakpoint nell'operazione ret
dalla funzione main
. Poi con gef
puoi vedere che il bin tcache Γ¨ pieno e un chunk Γ¨ nel bin grande:
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.
Top Chunk
// 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))
Fondamentalmente, questo Γ¨ un chunk che contiene tutto l'heap attualmente disponibile. Quando viene eseguito un malloc, se non c'Γ¨ alcun chunk libero disponibile da utilizzare, questo top chunk ridurrΓ la sua dimensione per dare lo spazio necessario.
Il puntatore al Top Chunk Γ¨ memorizzato nella struct malloc_state
.
Inoltre, all'inizio, Γ¨ possibile utilizzare il chunk non ordinato come top chunk.
Osserva l'esempio del 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;
}
Dopo aver compilato e debuggato con un punto di interruzione nell'opcode ret
di main
, ho visto che il malloc ha restituito l'indirizzo 0xaaaaaaac12a0
e questi sono i chunk:
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
Dove si puΓ² vedere che il chunk superiore si trova all'indirizzo 0xaaaaaaac1ae0
. Non Γ¨ una sorpresa perchΓ© l'ultimo chunk allocato era in 0xaaaaaaac12a0
con una dimensione di 0x410
e 0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0
.
Γ anche possibile vedere la lunghezza del Top chunk nell'intestazione del suo chunk:
gefβ€ x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000
0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000
Ultimo Rimanente
Quando viene utilizzato malloc e un chunk viene diviso (ad esempio dal bin non ordinato o dal chunk superiore), il chunk creato dal resto del chunk diviso Γ¨ chiamato Ultimo Rimanente e il suo puntatore Γ¨ memorizzato nella struct malloc_state
.
Flusso di Allocazione
Controlla:
{{#ref}} heap-memory-functions/malloc-and-sysmalloc.md {{#endref}}
Flusso di Liberazione
Controlla:
{{#ref}} heap-memory-functions/free.md {{#endref}}
Controlli di Sicurezza delle Funzioni Heap
Controlla i controlli di sicurezza eseguiti dalle funzioni ampiamente utilizzate nell'heap in:
{{#ref}} heap-memory-functions/heap-functions-security-checks.md {{#endref}}
Riferimenti
- 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
Impara e pratica l'Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica l'Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al π¬ gruppo Discord o al gruppo telegram o seguici su Twitter π¦ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos di github.