# Bins & Assegnazioni di Memoria {% hint style="success" %} Impara e pratica l'Hacking su AWS:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Impara e pratica l'Hacking su GCP: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Sostieni HackTricks * Controlla i [**piani di abbonamento**](https://github.com/sponsors/carlospolop)! * **Unisciti al** πŸ’¬ [**gruppo Discord**](https://discord.gg/hRep4RUj7f) o al [**gruppo telegram**](https://t.me/peass) o **seguici** su **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Condividi trucchi di hacking inviando PR a** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repos di github.
{% endhint %} ## Informazioni di Base Per migliorare l'efficienza su come i chunk sono memorizzati, ogni chunk non è solo in una lista concatenata, ma ci sono diversi tipi. Questi sono i bins e ci sono 5 tipi di bins: [62](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) small bins, 63 large bins, 1 unsorted bin, 10 fast bins e 64 tcache bins per thread. L'indirizzo iniziale di ogni bin non ordinato, small e large è all'interno dello stesso array. L'indice 0 non è utilizzato, 1 è il bin non ordinato, i bins 2-64 sono small bins e i bins 65-127 sono large bins. ### Bins Tcache (Cache Per-Thread) Anche se i thread cercano di avere il proprio heap (vedi [Arenas](bins-and-memory-allocations.md#arenas) e [Subheaps](bins-and-memory-allocations.md#subheaps)), c'è la possibilità che un processo con molti thread (come un server web) **finirà per condividere l'heap con altri thread**. In questo caso, la soluzione principale è l'uso di **lock**, che potrebbero **rallentare significativamente i thread**. Pertanto, un tcache è simile a un bin veloce per thread nel senso che è una **lista concatenata singola** 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](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323) che vanno da [24 a 1032B su sistemi a 64 bit e da 12 a 516B su sistemi a 32 bit](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315). Quando un thread libera un chunk, se non è troppo grande da essere allocato nel tcache e il rispettivo bin tcache **non è pieno** (già 7 chunk), **verrà allocato lì**. Se non può andare nel tcache, dovrà aspettare che il lock dell'heap sia in grado di eseguire l'operazione di liberazione globalmente. Quando un **chunk viene allocato**, se c'è un chunk libero della dimensione necessaria nel **Tcache lo utilizzerà**, altrimenti dovrà aspettare che il lock dell'heap sia in grado di trovarne uno nei bin globali o crearne uno nuovo.\ C'è anche un'ottimizzazione, in questo caso, avendo il lock dell'heap, il thread **riempirà il suo Tcache con chunk dell'heap (7) della dimensione richiesta**, quindi nel caso ne abbia bisogno di più, li troverà nel Tcache.
Aggiungi un esempio di chunk tcache ```c #include #include 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 debuggalo con un breakpoint nell'opcode ret dalla funzione main. Poi con gef puoi vedere il bin tcache in uso: ```bash 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 codice seguente Γ¨ possibile vedere i **max bins** e i **chunks per indice**, la struct **`tcache_entry`** creata per evitare doppie liberazioni e **`tcache_perthread_struct`**, una struct che ogni thread utilizza per memorizzare gli indirizzi di ciascun indice del bin.
tcache_entry e tcache_perthread_struct ```c // 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 di tcache_init ```c // 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 blocchi a seconda della dimensione e i puntatori iniziali al **primo chunk di ciascun indice e la quantitΓ  di chunk per indice sono situati all'interno di un chunk**. CiΓ² significa che individuando il chunk con queste informazioni (di solito il primo), Γ¨ possibile trovare tutti i punti iniziali del tcache e la quantitΓ  di chunk del Tcache. ### Fast bins I fast bins sono progettati per **accelerare l'allocazione di memoria per piccoli chunk** mantenendo i chunk liberati di recente in una struttura di accesso rapido. Questi blocchi utilizzano un approccio Last-In, First-Out (LIFO), il che significa che il **chunk liberato piΓΉ di recente Γ¨ il primo** ad 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 singolarmente**, 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 singolarmente Γ¨ 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 viene allocato un nuovo chunk 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 prossima allocazione sappia dove ottenere un chunk disponibile. * Quando un chunk viene liberato, il chunk libero salverΓ  l'indirizzo al chunk disponibile corrente e l'indirizzo a questo nuovo chunk liberato verrΓ  inserito nell'intestazione. La dimensione massima di una lista collegata Γ¨ `0x80` e sono organizzate in modo che un chunk di dimensione `0x20` sarΓ  nell'indice `0`, un chunk di dimensione `0x30` sarebbe nell'indice `1`... {% hint style="danger" %} I chunk nei fast bins non vengono impostati come disponibili, quindi vengono mantenuti come chunk fast bin per un certo periodo invece di poter essere uniti con altri chunk liberi circostanti. {% endhint %} ```c // 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 ```c #include #include 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 allocare e liberare 8 chunk dello stesso size in modo che riempiano il tcache e l'ottavo sia memorizzato nel fast chunk. Compilalo e debuggalo con un breakpoint nell'opcode `ret` dalla funzione `main`. Poi con `gef` puoi vedere che il bin tcache Γ¨ pieno e un chunk Γ¨ nel fast bin: ```bash 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 ordinato Il bin non ordinato Γ¨ una **cache** utilizzata dal gestore dell'heap per rendere piΓΉ veloce l'allocazione di 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 entra in collisione con il top chunk, il gestore dell'heap non lo mette immediatamente in un bin specifico piccolo o grande. Invece, prima cerca di **fonderlo con eventuali chunk liberi vicini** per creare un blocco piΓΉ grande di memoria libera. Successivamente, posiziona questo nuovo chunk in un bin generale chiamato "bin non ordinato". Quando un programma **richiede memoria**, il gestore dell'heap **controlla il bin non ordinato** per vedere se c'Γ¨ un chunk di dimensioni adeguate. Se ne trova uno, lo utilizza immediatamente. Se non trova un chunk adatto nel bin non ordinato, sposta tutti i chunk in questo elenco nei rispettivi bin, piccoli o grandi, in base alle loro dimensioni. Si noti che se un chunk piΓΉ grande viene diviso in 2 metΓ  e il resto Γ¨ piΓΉ grande di MINSIZE, verrΓ  riposizionato nel bin non ordinato. Quindi, il bin non ordinato Γ¨ un modo per velocizzare l'allocazione di memoria riutilizzando rapidamente la memoria liberata di recente e riducendo la necessitΓ  di ricerche e fusioni che richiedono tempo. {% hint style="danger" %} Si noti che anche se i chunk sono di categorie diverse, se un chunk disponibile entra in collisione con un altro chunk disponibile (anche se appartengono originariamente a bin diversi), verranno fusi. {% endhint %}
Aggiungi un esempio di chunk non ordinato ```c #include #include 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 allocare e liberare 9 chunk dello stesso size in modo che **riempiano il tcache** e l'ottavo sia memorizzato nell'unsorted bin perchΓ© Γ¨ **troppo grande per il fastbin** e il nono non Γ¨ liberato quindi il nono e l'ottavo **non vengono uniti con il top chunk**. Compilalo e debuggalo con un breakpoint nell'opcode `ret` dalla funzione `main`. Poi con `gef` puoi vedere che il tcache bin Γ¨ pieno e un chunk Γ¨ nell'unsorted bin: ```bash 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. ```
### Bins Piccoli I bins piccoli sono piΓΉ veloci dei bins grandi ma piΓΉ lenti dei fast bins. 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Γ  nel trovare il bin in cui dovrebbe essere allocato uno spazio e nell'inserimento e rimozione delle voci in queste liste. Ecco come viene calcolata la dimensione del bin piccolo in base all'indice del bin: * Dimensione piΓΉ piccola: 2\*4\*indice (es. indice 5 -> 40) * Dimensione piΓΉ grande: 2\*8\*indice (es. indice 5 -> 80) ```c // 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 i bin piccoli e grandi: ```c #define bin_index(sz) \ ((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz)) ``` Aggiungi un esempio di piccolo chunk ```c #include #include 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 allocare e liberare 9 chunk dello stesso size in modo che **riempiano il tcache** e l'ottavo sia memorizzato nell'unsorted bin perchΓ© Γ¨ **troppo grande per il fastbin** e il nono non Γ¨ liberato quindi il nono e l'ottavo **non vengono uniti con il top chunk**. Successivamente allocare un chunk piΓΉ grande di 0x110 che fa sΓ¬ che **il chunk nell'unsorted bin vada al small bin**. Compilalo e debuggalo con un breakpoint nell'opcode `ret` dalla funzione `main`. Poi con `gef` puoi vedere che il tcache bin Γ¨ pieno e un chunk Γ¨ nel small bin: ```bash 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 grandi A differenza dei blocchi piccoli, che gestiscono pezzi di dimensioni fisse, **ogni bin grande gestisce un intervallo di dimensioni di pezzi**. Questo Γ¨ piΓΉ flessibile, permettendo al sistema di gestire **varie dimensioni** senza la necessitΓ  di avere un bin separato per ogni dimensione. In un allocatore di memoria, i bin grandi iniziano dove finiscono i bin piccoli. Gli intervalli per i bin grandi crescono progressivamente, il che significa che il primo bin potrebbe coprire pezzi da 512 a 576 byte, mentre il successivo copre da 576 a 640 byte. Questo modello continua, con il bin piΓΉ grande che contiene tutti i pezzi sopra 1MB. I bin grandi sono piΓΉ lenti da gestire rispetto ai bin piccoli perchΓ© devono **ordinare e cercare attraverso un elenco di dimensioni di pezzi variabili per trovare la migliore corrispondenza** per un'allocazione. Quando un pezzo viene inserito in un bin grande, deve essere ordinato e quando la memoria viene allocata, il sistema deve trovare il pezzo giusto. Questo lavoro aggiuntivo 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 bin piccoli) * 16 bin di intervallo 512B (collidono con i bin piccoli) * 8 bin di intervallo 4096B (parte collidono con i bin piccoli) * 4 bin di intervallo 32768B * 2 bin di intervallo 262144B * 1 bin per le dimensioni rimanenti
Codice delle dimensioni dei bin grandi ```c // 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 chunk grande ```c #include #include int main(void) { char *chunks[2]; chunks[0] = malloc(0x1500); chunks[1] = malloc(0x1500); free(chunks[0]); chunks[0] = malloc(0x2000); return 0; } ``` Due grandi allocazioni vengono eseguite, poi una viene liberata (mettendola nel bin non ordinato) e viene effettuata un'allocazione piΓΉ grande (spostando quella liberata dal bin non ordinato al bin grande). Compilalo e debuggalo con un breakpoint nell'opcode `ret` dalla funzione `main`. Poi con `gef` puoi vedere che il bin tcache Γ¨ pieno e un chunk Γ¨ nel bin grande: ```bash 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. ```
### Chunk Principale ```c // 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 fornendo lo spazio necessario.\ Il puntatore al Top Chunk Γ¨ memorizzato nella struttura `malloc_state`. Inoltre, all'inizio, Γ¨ possibile utilizzare lo chunk non ordinato come top chunk.
Osserva l'esempio del Top Chunk ```c #include #include 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 eseguito il debug con un punto di interruzione nell'opcode `ret` di `main`, ho visto che la malloc ha restituito l'indirizzo `0xaaaaaaac12a0` e questi sono i chunk: ```bash 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`. Questo 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 chunk superiore nell'intestazione del chunk: ```bash gef➀ x/8wx 0xaaaaaaac1ae0 - 16 0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000 0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000 ```
### Ultimo Resto Quando viene utilizzato malloc e un chunk viene diviso (dalla lista non ordinata o dal chunk superiore per esempio), il chunk creato dal resto del chunk diviso Γ¨ chiamato Ultimo Resto e il suo puntatore Γ¨ memorizzato nella struttura `malloc_state`. ## Flusso di Allocazione Consulta: {% content-ref url="heap-memory-functions/malloc-and-sysmalloc.md" %} [malloc-and-sysmalloc.md](heap-memory-functions/malloc-and-sysmalloc.md) {% endcontent-ref %} ## Flusso di Liberazione Consulta: {% content-ref url="heap-memory-functions/free.md" %} [free.md](heap-memory-functions/free.md) {% endcontent-ref %} ## Controlli di Sicurezza delle Funzioni di Heap Controlla i controlli di sicurezza eseguiti dalle funzioni ampiamente utilizzate nell'heap in: {% content-ref url="heap-memory-functions/heap-functions-security-checks.md" %} [heap-functions-security-checks.md](heap-memory-functions/heap-functions-security-checks.md) {% endcontent-ref %} ## Riferimenti * [https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/](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://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/) * [https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions) * [https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/implementation/tcache/) {% hint style="success" %} Impara e pratica l'Hacking su AWS:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Impara e pratica l'Hacking su GCP: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Sostieni HackTricks * Controlla i [**piani di abbonamento**](https://github.com/sponsors/carlospolop)! * **Unisciti al** πŸ’¬ [**gruppo Discord**](https://discord.gg/hRep4RUj7f) o al [**gruppo telegram**](https://t.me/peass) o **seguici** su **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Condividi trucchi di hacking inviando PR ai** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repository di Github.
{% endhint %}