Bins & Memory Allocations
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripciΓ³n!
- Γnete al π¬ grupo de Discord o al grupo de telegram o sΓguenos en Twitter π¦ @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
InformaciΓ³n BΓ‘sica
Para mejorar la eficiencia en cΓ³mo se almacenan los chunks, cada chunk no estΓ‘ solo en una lista enlazada, sino que hay varios tipos. Estos son los bins y hay 5 tipos de bins: 62 bins pequeΓ±os, 63 bins grandes, 1 bin no ordenado, 10 bins rΓ‘pidos y 64 bins tcache por hilo.
La direcciΓ³n inicial de cada bin no ordenado, pequeΓ±o y grande estΓ‘ dentro del mismo array. El Γndice 0 no se utiliza, 1 es el bin no ordenado, los bins 2-64 son bins pequeΓ±os y los bins 65-127 son bins grandes.
Bins Tcache (Cache por Hilo)
A pesar de que los hilos intentan tener su propio heap (ver Arenas y Subheaps), existe la posibilidad de que un proceso con muchos hilos (como un servidor web) termine compartiendo el heap con otros hilos. En este caso, la soluciΓ³n principal es el uso de lockers, que pueden retrasar significativamente los hilos.
Por lo tanto, un tcache es similar a un bin rΓ‘pido por hilo en el sentido de que es una lista enlazada simple que no fusiona chunks. Cada hilo tiene 64 bins tcache enlazados de forma simple. Cada bin puede tener un mΓ‘ximo de 7 chunks del mismo tamaΓ±o que van desde 24 a 1032B en sistemas de 64 bits y 12 a 516B en sistemas de 32 bits.
Cuando un hilo libera un chunk, si no es demasiado grande para ser asignado en el tcache y el bin tcache respectivo no estΓ‘ lleno (ya tiene 7 chunks), se asignarΓ‘ allΓ. Si no puede ir al tcache, tendrΓ‘ que esperar a que se desbloquee el heap para poder realizar la operaciΓ³n de liberaciΓ³n globalmente.
Cuando un chunk es asignado, si hay un chunk libre del tamaΓ±o necesario en el Tcache lo usarΓ‘, si no, tendrΓ‘ que esperar a que se desbloquee el heap para poder encontrar uno en los bins globales o crear uno nuevo.
TambiΓ©n hay una optimizaciΓ³n, en este caso, mientras tiene el lock del heap, el hilo llenarΓ‘ su Tcache con chunks del heap (7) del tamaΓ±o solicitado, asΓ que en caso de que necesite mΓ‘s, los encontrarΓ‘ en Tcache.
Agregar un ejemplo de chunk tcache
```c #includeint main(void) { char *chunk; chunk = malloc(24); printf(βAddress of the chunk: %p\nβ, (void *)chunk); gets(chunk); free(chunk); return 0; }
CompΓlalo y depΓΊralo con un punto de interrupciΓ³n en el opcode ret de la funciΓ³n main. Luego, con gef, puedes ver el tcache bin en 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)
Estructuras y Funciones de Tcache
En el siguiente cΓ³digo es posible ver los max bins y chunks por Γndice, la estructura tcache_entry creada para evitar dobles liberaciones y tcache_perthread_struct, una estructura que cada hilo utiliza para almacenar las direcciones de cada Γndice del bin.
tcache_entry y 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;
</details>
La funciΓ³n `__tcache_init` es la funciΓ³n que crea y asigna el espacio para el objeto `tcache_perthread_struct`.
<details>
<summary>cΓ³digo de tcache_init</summary>
```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));
}
}
Γndices de Tcache
El tcache tiene varios bins dependiendo del tamaΓ±o y los punteros iniciales al primer chunk de cada Γndice y la cantidad de chunks por Γndice se encuentran dentro de un chunk. Esto significa que al localizar el chunk con esta informaciΓ³n (generalmente el primero), es posible encontrar todos los puntos iniciales de tcache y la cantidad de chunks de Tcache.
Bins RΓ‘pidos
Los bins rΓ‘pidos estΓ‘n diseΓ±ados para acelerar la asignaciΓ³n de memoria para chunks pequeΓ±os al mantener chunks recientemente liberados en una estructura de acceso rΓ‘pido. Estos bins utilizan un enfoque de Γltimo en Entrar, Primero en Salir (LIFO), lo que significa que el chunk liberado mΓ‘s recientemente es el primero en ser reutilizado cuando hay una nueva solicitud de asignaciΓ³n. Este comportamiento es ventajoso para la velocidad, ya que es mΓ‘s rΓ‘pido insertar y eliminar desde la parte superior de una pila (LIFO) en comparaciΓ³n con una cola (FIFO).
AdemΓ‘s, los bins rΓ‘pidos utilizan listas enlazadas simples, no dobles, lo que mejora aΓΊn mΓ‘s la velocidad. Dado que los chunks en bins rΓ‘pidos no se fusionan con los vecinos, no hay necesidad de una estructura compleja que permita la eliminaciΓ³n desde el medio. Una lista enlazada simple es mΓ‘s simple y rΓ‘pida para estas operaciones.
BΓ‘sicamente, lo que sucede aquΓ es que el encabezado (el puntero al primer chunk a verificar) siempre apunta al ΓΊltimo chunk liberado de ese tamaΓ±o. AsΓ que:
- Cuando se asigna un nuevo chunk de ese tamaΓ±o, el encabezado apunta a un chunk libre para usar. Como este chunk libre apunta al siguiente para usar, esta direcciΓ³n se almacena en el encabezado para que la siguiente asignaciΓ³n sepa dΓ³nde obtener un chunk disponible.
- Cuando se libera un chunk, el chunk libre guardarΓ‘ la direcciΓ³n del chunk actualmente disponible y la direcciΓ³n de este chunk reciΓ©n liberado se pondrΓ‘ en el encabezado.
El tamaΓ±o mΓ‘ximo de una lista enlazada es 0x80 y estΓ‘n organizados de tal manera que un chunk de tamaΓ±o 0x20 estarΓ‘ en el Γndice 0, un chunk de tamaΓ±o 0x30 estarΓa en el Γndice 1β¦
Caution
Los chunks en bins rΓ‘pidos no se establecen como disponibles, por lo que se mantienen como chunks de bin rΓ‘pido durante algΓΊn tiempo en lugar de poder fusionarse con otros chunks libres que los rodean.
// 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)
Agregar un ejemplo de chunk fastbin
```c #includeint 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 cΓ³mo asignamos y liberamos 8 bloques del mismo tamaΓ±o para que llenen el tcache y el octavo se almacena en el fast chunk.
CompΓlalo y depΓΊralo con un punto de interrupciΓ³n en el opcode `ret` de la funciΓ³n `main`. Luego, con `gef`, puedes ver que el tcache bin estΓ‘ lleno y un bloque estΓ‘ en el 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
Unsorted bin
El unsorted bin es un cache utilizado por el administrador de memoria para hacer que la asignaciΓ³n de memoria sea mΓ‘s rΓ‘pida. AsΓ es como funciona: Cuando un programa libera un bloque, y si este bloque no puede ser asignado en un tcache o fast bin y no estΓ‘ colisionando con el top chunk, el administrador de memoria no lo coloca inmediatamente en un bin pequeΓ±o o grande especΓfico. En su lugar, primero intenta fusionarlo con cualquier bloque libre vecino para crear un bloque mΓ‘s grande de memoria libre. Luego, coloca este nuevo bloque en un bin general llamado βunsorted bin.β
Cuando un programa pide memoria, el administrador de memoria verifica el unsorted bin para ver si hay un bloque de tamaΓ±o suficiente. Si encuentra uno, lo utiliza de inmediato. Si no encuentra un bloque adecuado en el unsorted bin, mueve todos los bloques en esta lista a sus respectivos bins, ya sea pequeΓ±os o grandes, segΓΊn su tamaΓ±o.
Ten en cuenta que si un bloque mΓ‘s grande se divide en 2 mitades y el resto es mayor que MINSIZE, se volverΓ‘ a colocar en el unsorted bin.
AsΓ que, el unsorted bin es una forma de acelerar la asignaciΓ³n de memoria al reutilizar rΓ‘pidamente la memoria recientemente liberada y reducir la necesidad de bΓΊsquedas y fusiones que consumen tiempo.
Caution
Ten en cuenta que incluso si los bloques son de diferentes categorΓas, si un bloque disponible estΓ‘ colisionando con otro bloque disponible (incluso si originalmente pertenecen a diferentes bins), se fusionarΓ‘n.
Add a unsorted chunk example
```c #includeint 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 cΓ³mo asignamos y liberamos 9 bloques del mismo tamaΓ±o para que **llenan el tcache** y el octavo se almacena en el contenedor no ordenado porque es **demasiado grande para el fastbin** y el noveno no se libera, por lo que el noveno y el octavo **no se fusionan con el bloque superior**.
CompΓlalo y depΓΊralo con un punto de interrupciΓ³n en el opcode `ret` de la funciΓ³n `main`. Luego, con `gef`, puedes ver que el contenedor tcache estΓ‘ lleno y un bloque estΓ‘ en el contenedor no ordenado:
```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.
Small Bins
Los small bins son mΓ‘s rΓ‘pidos que los large bins pero mΓ‘s lentos que los fast bins.
Cada bin de los 62 tendrΓ‘ chunks del mismo tamaΓ±o: 16, 24, β¦ (con un tamaΓ±o mΓ‘ximo de 504 bytes en 32 bits y 1024 en 64 bits). Esto ayuda en la velocidad para encontrar el bin donde se debe asignar un espacio e insertar y eliminar entradas en estas listas.
AsΓ es como se calcula el tamaΓ±o del small bin segΓΊn el Γndice del bin:
- TamaΓ±o mΓ‘s pequeΓ±o: 2*4*Γndice (por ejemplo, Γndice 5 -> 40)
- TamaΓ±o mΓ‘s grande: 2*8*Γndice (por ejemplo, Γndice 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)
FunciΓ³n para elegir entre bins pequeΓ±os y grandes:
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
Agrega un pequeΓ±o ejemplo
```c #includeint 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; }
Note cΓ³mo asignamos y liberamos 9 trozos del mismo tamaΓ±o para que **llenan el tcache** y el octavo se almacena en el contenedor no ordenado porque es **demasiado grande para el fastbin** y el noveno no se libera, por lo que el noveno y el octavo **no se fusionan con el trozo superior**. Luego, asignamos un trozo mΓ‘s grande de 0x110, lo que hace que **el trozo en el contenedor no ordenado pase al contenedor pequeΓ±o**.
CompΓlalo y depΓΊralo con un punto de interrupciΓ³n en el opcode `ret` de la funciΓ³n `main`. Luego, con `gef`, puedes ver que el contenedor tcache estΓ‘ lleno y un trozo estΓ‘ en el contenedor pequeΓ±o:
```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 grandes
A diferencia de los bins pequeΓ±os, que gestionan trozos de tamaΓ±os fijos, cada bin grande maneja un rango de tamaΓ±os de trozos. Esto es mΓ‘s flexible, permitiendo que el sistema acomode varios tamaΓ±os sin necesidad de un bin separado para cada tamaΓ±o.
En un asignador de memoria, los bins grandes comienzan donde terminan los bins pequeΓ±os. Los rangos para los bins grandes crecen progresivamente, lo que significa que el primer bin podrΓa cubrir trozos de 512 a 576 bytes, mientras que el siguiente cubre de 576 a 640 bytes. Este patrΓ³n continΓΊa, con el bin mΓ‘s grande conteniendo todos los trozos por encima de 1MB.
Los bins grandes son mΓ‘s lentos de operar en comparaciΓ³n con los bins pequeΓ±os porque deben ordenar y buscar a travΓ©s de una lista de tamaΓ±os de trozos variables para encontrar el mejor ajuste para una asignaciΓ³n. Cuando un trozo se inserta en un bin grande, debe ser ordenado, y cuando se asigna memoria, el sistema debe encontrar el trozo correcto. Este trabajo extra los hace mΓ‘s lentos, pero dado que las asignaciones grandes son menos comunes que las pequeΓ±as, es un compromiso aceptable.
Hay:
- 32 bins de rango de 64B (colisionan con bins pequeΓ±os)
- 16 bins de rango de 512B (colisionan con bins pequeΓ±os)
- 8 bins de rango de 4096B (parte colisiona con bins pequeΓ±os)
- 4 bins de rango de 32768B
- 2 bins de rango de 262144B
- 1 bin para tamaΓ±os restantes
CΓ³digo de tamaΓ±os de bin grande
```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))
</details>
<details>
<summary>Agregar un ejemplo de un gran bloque</summary>
```c
#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;
}
Se realizan 2 asignaciones grandes, luego se libera una (colocΓ‘ndola en el contenedor no ordenado) y se realiza una asignaciΓ³n mΓ‘s grande (moviendo la libre del contenedor no ordenado al contenedor grande).
CompΓlalo y depΓΊralo con un punto de interrupciΓ³n en el opcode ret de la funciΓ³n main. Luego, con gef, puedes ver que el contenedor tcache estΓ‘ lleno y un chunk estΓ‘ en el contenedor 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.
Parte Superior
// 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))
BΓ‘sicamente, este es un chunk que contiene toda la memoria heap disponible actualmente. Cuando se realiza un malloc, si no hay ningΓΊn chunk libre disponible para usar, este top chunk reducirΓ‘ su tamaΓ±o para dar el espacio necesario.
El puntero al Top Chunk se almacena en la estructura malloc_state.
AdemΓ‘s, al principio, es posible usar el chunk no ordenado como el top chunk.
Observe el ejemplo del Top Chunk
```c #includeint main(void) { char *chunk; chunk = malloc(24); printf(βAddress of the chunk: %p\nβ, (void *)chunk); gets(chunk); return 0; }
DespuΓ©s de compilar y depurar con un punto de interrupciΓ³n en el opcode `ret` de `main`, vi que el malloc devolviΓ³ la direcciΓ³n `0xaaaaaaac12a0` y estos son los chunks:
```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
Donde se puede ver que el chunk superior estΓ‘ en la direcciΓ³n 0xaaaaaaac1ae0. Esto no es una sorpresa porque el ΓΊltimo chunk asignado estaba en 0xaaaaaaac12a0 con un tamaΓ±o de 0x410 y 0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0.
TambiΓ©n es posible ver la longitud del chunk superior en su encabezado de chunk:
gefβ€ x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000
0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000
Γltimo Resto
Cuando se utiliza malloc y un chunk se divide (por ejemplo, desde el bin no ordenado o desde el chunk superior), el chunk creado a partir del resto del chunk dividido se llama Γltimo Resto y su puntero se almacena en la estructura malloc_state.
Flujo de AsignaciΓ³n
Consulta:
Flujo de LiberaciΓ³n
Consulta:
Comprobaciones de Seguridad de Funciones de Heap
Revisa las comprobaciones de seguridad realizadas por funciones de uso intensivo en heap en:
Heap Functions Security Checks
Referencias
- 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
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripciΓ³n!
- Γnete al π¬ grupo de Discord o al grupo de telegram o sΓguenos en Twitter π¦ @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks

