Bins & Memory Allocations

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

InformaΓ§Γ΅es BΓ‘sicas

Para melhorar a eficiΓͺncia de como os chunks sΓ£o armazenados, cada chunk nΓ£o estΓ‘ apenas em uma lista encadeada, mas existem vΓ‘rios tipos. Estes sΓ£o os bins e hΓ‘ 5 tipos de bins: 62 bins pequenos, 63 bins grandes, 1 bin nΓ£o ordenado, 10 bins rΓ‘pidos e 64 bins tcache por thread.

O endereΓ§o inicial de cada bin nΓ£o ordenado, pequeno e grande estΓ‘ dentro do mesmo array. O Γ­ndice 0 nΓ£o Γ© utilizado, 1 Γ© o bin nΓ£o ordenado, os bins 2-64 sΓ£o bins pequenos e os bins 65-127 sΓ£o bins grandes.

Bins Tcache (Cache por Thread)

Embora as threads tentem ter seu prΓ³prio heap (veja Arenas e Subheaps), existe a possibilidade de que um processo com muitas threads (como um servidor web) acabe compartilhando o heap com outras threads. Nesse caso, a principal soluΓ§Γ£o Γ© o uso de lockers, que podem reduzir significativamente a velocidade das threads.

Portanto, um tcache Γ© semelhante a um bin rΓ‘pido por thread na forma de que Γ© uma lista encadeada simples que nΓ£o mescla chunks. Cada thread tem 64 bins tcache encadeados. Cada bin pode ter um mΓ‘ximo de 7 chunks do mesmo tamanho variando de 24 a 1032B em sistemas de 64 bits e 12 a 516B em sistemas de 32 bits.

Quando uma thread libera um chunk, se nΓ£o for muito grande para ser alocado no tcache e o respectivo bin tcache nΓ£o estiver cheio (jΓ‘ 7 chunks), ele serΓ‘ alocado lΓ‘. Se nΓ£o puder ir para o tcache, precisarΓ‘ esperar pelo bloqueio do heap para poder realizar a operaΓ§Γ£o de liberaΓ§Γ£o globalmente.

Quando um chunk Γ© alocado, se houver um chunk livre do tamanho necessΓ‘rio no Tcache, ele o usarΓ‘, se nΓ£o, precisarΓ‘ esperar pelo bloqueio do heap para poder encontrar um nos bins globais ou criar um novo.
HΓ‘ tambΓ©m uma otimizaΓ§Γ£o, nesse caso, enquanto tiver o bloqueio do heap, a thread preencherΓ‘ seu Tcache com chunks do heap (7) do tamanho solicitado, entΓ£o, caso precise de mais, os encontrarΓ‘ no Tcache.

Adicionar um exemplo 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; }

Compile-o e depure-o com um ponto de interrupΓ§Γ£o no opcode ret da funΓ§Γ£o main. EntΓ£o, com gef, vocΓͺ pode ver o tcache bin em 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)

Estruturas e FunΓ§Γ΅es Tcache

No cΓ³digo a seguir, Γ© possΓ­vel ver os max bins e chunks por Γ­ndice, a estrutura tcache_entry criada para evitar liberaΓ§Γ΅es duplas e tcache_perthread_struct, uma estrutura que cada thread usa para armazenar os endereΓ§os de cada Γ­ndice do 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;

</details>

A funΓ§Γ£o `__tcache_init` Γ© a funΓ§Γ£o que cria e aloca o espaΓ§o para o objeto `tcache_perthread_struct`

<details>

<summary>cΓ³digo 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 Tcache

O tcache possui vΓ‘rios bins dependendo do tamanho e os ponteiros iniciais para o primeiro chunk de cada Γ­ndice e a quantidade de chunks por Γ­ndice estΓ£o localizados dentro de um chunk. Isso significa que, ao localizar o chunk com essa informaΓ§Γ£o (geralmente o primeiro), Γ© possΓ­vel encontrar todos os pontos iniciais do tcache e a quantidade de chunks do Tcache.

Bins RΓ‘pidos

Bins rΓ‘pidos sΓ£o projetados para acelerar a alocaΓ§Γ£o de memΓ³ria para pequenos chunks mantendo chunks recentemente liberados em uma estrutura de acesso rΓ‘pido. Esses bins usam uma abordagem Last-In, First-Out (LIFO), o que significa que o chunk mais recentemente liberado Γ© o primeiro a ser reutilizado quando hΓ‘ um novo pedido de alocaΓ§Γ£o. Esse comportamento Γ© vantajoso para a velocidade, pois Γ© mais rΓ‘pido inserir e remover do topo de uma pilha (LIFO) em comparaΓ§Γ£o com uma fila (FIFO).

AlΓ©m disso, bins rΓ‘pidos usam listas encadeadas simples, nΓ£o duplamente encadeadas, o que melhora ainda mais a velocidade. Como os chunks em bins rΓ‘pidos nΓ£o sΓ£o mesclados com vizinhos, nΓ£o hΓ‘ necessidade de uma estrutura complexa que permita a remoΓ§Γ£o do meio. Uma lista encadeada simples Γ© mais simples e rΓ‘pida para essas operaΓ§Γ΅es.

Basicamente, o que acontece aqui Γ© que o cabeΓ§alho (o ponteiro para o primeiro chunk a ser verificado) estΓ‘ sempre apontando para o chunk mais recentemente liberado desse tamanho. EntΓ£o:

  • Quando um novo chunk Γ© alocado desse tamanho, o cabeΓ§alho estΓ‘ apontando para um chunk livre para usar. Como esse chunk livre estΓ‘ apontando para o prΓ³ximo a ser usado, esse endereΓ§o Γ© armazenado no cabeΓ§alho para que a prΓ³xima alocaΓ§Γ£o saiba onde obter um chunk disponΓ­vel.
  • Quando um chunk Γ© liberado, o chunk livre salvarΓ‘ o endereΓ§o do chunk atualmente disponΓ­vel e o endereΓ§o desse chunk recΓ©m-liberado serΓ‘ colocado no cabeΓ§alho.

O tamanho mΓ‘ximo de uma lista encadeada Γ© 0x80 e elas sΓ£o organizadas de modo que um chunk de tamanho 0x20 estarΓ‘ no Γ­ndice 0, um chunk de tamanho 0x30 estaria no Γ­ndice 1…

Caution

Chunks em bins rΓ‘pidos nΓ£o sΓ£o definidos como disponΓ­veis, portanto, eles sΓ£o mantidos como chunks de bin rΓ‘pido por algum tempo, em vez de poderem ser mesclados com outros chunks livres ao seu redor.

// 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)
Adicionar um exemplo 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; }

Observe como alocamos e liberamos 8 pedaΓ§os do mesmo tamanho para que preencham o tcache e o oitavo Γ© armazenado no fast chunk.

Compile-o e depure-o com um ponto de interrupΓ§Γ£o no opcode `ret` da funΓ§Γ£o `main`. EntΓ£o, com `gef`, vocΓͺ pode ver que o bin do tcache estΓ‘ cheio e um pedaΓ§o estΓ‘ no 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 nΓ£o classificado

O bin nΓ£o classificado Γ© um cache usado pelo gerenciador de heap para tornar a alocaΓ§Γ£o de memΓ³ria mais rΓ‘pida. Veja como funciona: Quando um programa libera um bloco, e se esse bloco nΓ£o pode ser alocado em um tcache ou bin rΓ‘pido e nΓ£o estΓ‘ colidindo com o bloco superior, o gerenciador de heap nΓ£o o coloca imediatamente em um bin pequeno ou grande especΓ­fico. Em vez disso, ele primeiro tenta mesclar com quaisquer blocos livres vizinhos para criar um bloco maior de memΓ³ria livre. Em seguida, coloca esse novo bloco em um bin geral chamado β€œbin nΓ£o classificado.”

Quando um programa pede memΓ³ria, o gerenciador de heap verifica o bin nΓ£o classificado para ver se hΓ‘ um bloco de tamanho suficiente. Se encontrar um, ele o utiliza imediatamente. Se nΓ£o encontrar um bloco adequado no bin nΓ£o classificado, ele move todos os blocos dessa lista para seus bins correspondentes, seja pequeno ou grande, com base em seu tamanho.

Observe que se um bloco maior for dividido em 2 metades e o restante for maior que MINSIZE, ele serΓ‘ colocado de volta no bin nΓ£o classificado.

Portanto, o bin nΓ£o classificado Γ© uma maneira de acelerar a alocaΓ§Γ£o de memΓ³ria reutilizando rapidamente a memΓ³ria recentemente liberada e reduzindo a necessidade de buscas e mesclagens demoradas.

Caution

Observe que mesmo que os blocos sejam de categorias diferentes, se um bloco disponΓ­vel estiver colidindo com outro bloco disponΓ­vel (mesmo que originalmente pertenΓ§am a bins diferentes), eles serΓ£o mesclados.

Adicionar um exemplo de bloco nΓ£o classificado ```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; }

Observe como alocamos e liberamos 9 chunks do mesmo tamanho para que eles **preencham o tcache** e o oitavo Γ© armazenado no bin nΓ£o ordenado porque Γ© **grande demais para o fastbin** e o nono nΓ£o Γ© liberado, entΓ£o o nono e o oitavo **nΓ£o sΓ£o mesclados com o chunk superior**.

Compile e depure com um ponto de interrupΓ§Γ£o no opcode `ret` da funΓ§Γ£o `main`. EntΓ£o, com `gef`, vocΓͺ pode ver que o bin do tcache estΓ‘ cheio e um chunk estΓ‘ no bin nΓ£o 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

Small bins sΓ£o mais rΓ‘pidos que large bins, mas mais lentos que fast bins.

Cada bin dos 62 terΓ‘ chunks do mesmo tamanho: 16, 24, … (com um tamanho mΓ‘ximo de 504 bytes em 32 bits e 1024 em 64 bits). Isso ajuda na velocidade de encontrar o bin onde um espaΓ§o deve ser alocado e na inserΓ§Γ£o e remoΓ§Γ£o de entradas nessas listas.

Assim Γ© como o tamanho do small bin Γ© calculado de acordo com o Γ­ndice do bin:

  • Tamanho menor: 2*4*index (por exemplo, Γ­ndice 5 -> 40)
  • Tamanho maior: 2*8*index (por exemplo, Γ­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)

FunΓ§Γ£o para escolher entre bins pequenos e grandes:

#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
Adicionar um pequeno exemplo de 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; }

Note como alocamos e liberamos 9 chunks do mesmo tamanho para que eles **preencham o tcache** e o oitavo Γ© armazenado no bin nΓ£o ordenado porque Γ© **grande demais para o fastbin** e o nono nΓ£o Γ© liberado, entΓ£o o nono e o oitavo **nΓ£o sΓ£o mesclados com o chunk superior**. Em seguida, alocamos um chunk maior de 0x110, o que faz com que **o chunk no bin nΓ£o ordenado vΓ‘ para o small bin**.

Compile e depure com um breakpoint no opcode `ret` da funΓ§Γ£o `main`. EntΓ£o, com `gef`, vocΓͺ pode ver que o bin do tcache estΓ‘ cheio e um chunk estΓ‘ no 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.

Grandes bins

Ao contrΓ‘rio das pequenas bins, que gerenciam pedaΓ§os de tamanhos fixos, cada grande bin gerencia uma faixa de tamanhos de pedaΓ§os. Isso Γ© mais flexΓ­vel, permitindo que o sistema acomode vΓ‘rios tamanhos sem precisar de uma bin separada para cada tamanho.

Em um alocador de memΓ³ria, as grandes bins comeΓ§am onde as pequenas bins terminam. As faixas para grandes bins crescem progressivamente, o que significa que a primeira bin pode cobrir pedaΓ§os de 512 a 576 bytes, enquanto a prΓ³xima cobre de 576 a 640 bytes. Esse padrΓ£o continua, com a maior bin contendo todos os pedaΓ§os acima de 1MB.

As grandes bins sΓ£o mais lentas para operar em comparaΓ§Γ£o com as pequenas bins porque elas devem classificar e pesquisar em uma lista de tamanhos de pedaΓ§os variados para encontrar o melhor ajuste para uma alocaΓ§Γ£o. Quando um pedaΓ§o Γ© inserido em uma grande bin, ele precisa ser classificado, e quando a memΓ³ria Γ© alocada, o sistema deve encontrar o pedaΓ§o certo. Esse trabalho extra as torna mais lentas, mas como grandes alocaΓ§Γ΅es sΓ£o menos comuns do que pequenas, Γ© uma troca aceitΓ‘vel.

Existem:

  • 32 bins de faixa de 64B (colidem com pequenas bins)
  • 16 bins de faixa de 512B (colidem com pequenas bins)
  • 8 bins de faixa de 4096B (parte colide com pequenas bins)
  • 4 bins de faixa de 32768B
  • 2 bins de faixa de 262144B
  • 1 bin para tamanhos restantes
CΓ³digo de tamanhos de grandes bins ```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>Adicionar um exemplo de grande bloco</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;
}

2 grandes alocaΓ§Γ΅es sΓ£o realizadas, entΓ£o uma Γ© liberada (colocando-a no bin nΓ£o ordenado) e uma alocaΓ§Γ£o maior Γ© feita (movendo a liberada do bin nΓ£o ordenado para o bin grande).

Compile e depure com um breakpoint no opcode ret da funΓ§Γ£o main. EntΓ£o, com gef, vocΓͺ pode ver que o bin tcache estΓ‘ cheio e um chunk estΓ‘ no 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))

Basicamente, este Γ© um chunk que contΓ©m todo o heap atualmente disponΓ­vel. Quando um malloc Γ© realizado, se nΓ£o houver nenhum chunk livre disponΓ­vel para usar, este top chunk reduzirΓ‘ seu tamanho, dando o espaΓ§o necessΓ‘rio.
O ponteiro para o Top Chunk Γ© armazenado na struct malloc_state.

AlΓ©m disso, no inΓ­cio, Γ© possΓ­vel usar o chunk nΓ£o ordenado como o top chunk.

Observe o exemplo do 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; }

ApΓ³s compilar e depurar com um ponto de interrupΓ§Γ£o no opcode `ret` de `main`, vi que o malloc retornou o endereΓ§o `0xaaaaaaac12a0` e estes sΓ£o os 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

Onde pode-se ver que o chunk superior estΓ‘ no endereΓ§o 0xaaaaaaac1ae0. Isso nΓ£o Γ© surpresa porque o ΓΊltimo chunk alocado estava em 0xaaaaaaac12a0 com um tamanho de 0x410 e 0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0.
TambΓ©m Γ© possΓ­vel ver o comprimento do chunk superior em seu cabeΓ§alho de chunk:

gef➀  x/8wx 0xaaaaaaac1ae0 - 16
0xaaaaaaac1ad0:	0x00000000	0x00000000	0x00020531	0x00000000
0xaaaaaaac1ae0:	0x00000000	0x00000000	0x00000000	0x00000000

Último Resto

Quando malloc é usado e um chunk é dividido (do bin não ordenado ou do chunk superior, por exemplo), o chunk criado a partir do restante do chunk dividido é chamado de Último Resto e seu ponteiro é armazenado na struct malloc_state.

Fluxo de AlocaΓ§Γ£o

Confira:

malloc & sysmalloc

Fluxo de LiberaΓ§Γ£o

Confira:

free

VerificaΓ§Γ΅es de SeguranΓ§a das FunΓ§Γ΅es de Heap

Verifique as verificaΓ§Γ΅es de seguranΓ§a realizadas por funΓ§Γ΅es amplamente utilizadas em heap em:

Heap Functions Security Checks

ReferΓͺncias

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks