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

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 #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; }

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 #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 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 #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 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 #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; }

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 #include #include

int 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:

malloc & sysmalloc

Flujo de LiberaciΓ³n

Consulta:

free

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

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