mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-25 12:33:39 +00:00
628 lines
37 KiB
Markdown
628 lines
37 KiB
Markdown
# Bins & Memory Allocations
|
|
|
|
{% hint style="success" %}
|
|
Learn & practice AWS Hacking:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
|
Learn & practice GCP Hacking: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
|
|
|
<details>
|
|
|
|
<summary>Support HackTricks</summary>
|
|
|
|
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
|
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
|
|
|
</details>
|
|
{% endhint %}
|
|
|
|
## Basic Information
|
|
|
|
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](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) bins pequenos, 63 bins grandes, 1 bin não ordenado, 10 bins rápidos e 64 bins tcache por thread.
|
|
|
|
O endereço inicial para cada bin não ordenado, pequeno e grande está dentro do mesmo array. O índice 0 não é usado, 1 é o bin não ordenado, bins 2-64 são bins pequenos e bins 65-127 são bins grandes.
|
|
|
|
### Tcache (Per-Thread Cache) Bins
|
|
|
|
Embora as threads tentem ter seu próprio heap (veja [Arenas](bins-and-memory-allocations.md#arenas) e [Subheaps](bins-and-memory-allocations.md#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 como é 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](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323) variando de [24 a 1032B em sistemas de 64 bits e 12 a 516B em sistemas de 32 bits](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315).
|
|
|
|
**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**, para que, caso precise de mais, os encontre no Tcache.
|
|
|
|
<details>
|
|
|
|
<summary>Add a tcache chunk example</summary>
|
|
```c
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
int main(void)
|
|
{
|
|
char *chunk;
|
|
chunk = malloc(24);
|
|
printf("Address of the chunk: %p\n", (void *)chunk);
|
|
gets(chunk);
|
|
free(chunk);
|
|
return 0;
|
|
}
|
|
```
|
|
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 bin tcache 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)
|
|
```
|
|
</details>
|
|
|
|
#### 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 double frees e **`tcache_perthread_struct`**, uma estrutura que cada thread usa para armazenar os endereços de cada índice do bin.
|
|
|
|
<details>
|
|
|
|
<summary><code>tcache_entry</code> e <code>tcache_perthread_struct</code></summary>
|
|
```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));
|
|
}
|
|
|
|
}
|
|
```
|
|
</details>
|
|
|
|
#### Í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
|
|
|
|
Os 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 último chunk liberado daquele 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`...
|
|
|
|
{% hint style="danger" %}
|
|
Chunks em bins rápidos não são definidos como disponíveis, portanto, 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.
|
|
{% 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)
|
|
```
|
|
<details>
|
|
|
|
<summary>Adicionar um exemplo de chunk fastbin</summary>
|
|
```c
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
int main(void)
|
|
{
|
|
char *chunks[8];
|
|
int i;
|
|
|
|
// Loop to allocate memory 8 times
|
|
for (i = 0; i < 8; i++) {
|
|
chunks[i] = malloc(24);
|
|
if (chunks[i] == NULL) { // Check if malloc failed
|
|
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
|
|
return 1;
|
|
}
|
|
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
|
|
}
|
|
|
|
// Loop to free the allocated memory
|
|
for (i = 0; i < 8; i++) {
|
|
free(chunks[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
Note como alocamos e liberamos 8 chunks do mesmo tamanho para que eles preencham o tcache e o oitavo é armazenado no fast chunk.
|
|
|
|
Compile-o e depure-o 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 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
|
|
```
|
|
</details>
|
|
|
|
### Unsorted bin
|
|
|
|
O unsorted bin é 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 chunk, e se esse chunk não pode ser alocado em um tcache ou fast bin e não está colidindo com o top chunk, 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 chunks livres vizinhos** para criar um bloco maior de memória livre. Em seguida, coloca esse novo chunk em um bin geral chamado "unsorted bin."
|
|
|
|
Quando um programa **pede memória**, o gerenciador de heap **verifica o unsorted bin** para ver se há um chunk de tamanho suficiente. Se encontrar um, ele o utiliza imediatamente. Se não encontrar um chunk adequado no unsorted bin, ele move todos os chunks dessa lista para seus bins correspondentes, seja pequeno ou grande, com base em seu tamanho.
|
|
|
|
Note que se um chunk maior for dividido em 2 metades e o restante for maior que MINSIZE, ele será colocado de volta no unsorted bin.
|
|
|
|
Assim, o unsorted bin é 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.
|
|
|
|
{% hint style="danger" %}
|
|
Note que mesmo que os chunks sejam de categorias diferentes, se um chunk disponível estiver colidindo com outro chunk disponível (mesmo que originalmente pertençam a bins diferentes), eles serão mesclados.
|
|
{% endhint %}
|
|
|
|
<details>
|
|
|
|
<summary>Adicionar um exemplo de chunk não ordenado</summary>
|
|
```c
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
int main(void)
|
|
{
|
|
char *chunks[9];
|
|
int i;
|
|
|
|
// Loop to allocate memory 8 times
|
|
for (i = 0; i < 9; i++) {
|
|
chunks[i] = malloc(0x100);
|
|
if (chunks[i] == NULL) { // Check if malloc failed
|
|
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
|
|
return 1;
|
|
}
|
|
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
|
|
}
|
|
|
|
// Loop to free the allocated memory
|
|
for (i = 0; i < 8; i++) {
|
|
free(chunks[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
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**.
|
|
|
|
Compile-o e depure-o 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 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.
|
|
```
|
|
</details>
|
|
|
|
### 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\*índice (por exemplo, índice 5 -> 40)
|
|
* Tamanho maior: 2\*8\*índice (por exemplo, índice 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)
|
|
```
|
|
Função para escolher entre bins pequenos e grandes:
|
|
```c
|
|
#define bin_index(sz) \
|
|
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))
|
|
```
|
|
<details>
|
|
|
|
<summary>Adicionar um pequeno exemplo de chunk</summary>
|
|
```c
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
int main(void)
|
|
{
|
|
char *chunks[10];
|
|
int i;
|
|
|
|
// Loop to allocate memory 8 times
|
|
for (i = 0; i < 9; i++) {
|
|
chunks[i] = malloc(0x100);
|
|
if (chunks[i] == NULL) { // Check if malloc failed
|
|
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
|
|
return 1;
|
|
}
|
|
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
|
|
}
|
|
|
|
// Loop to free the allocated memory
|
|
for (i = 0; i < 8; i++) {
|
|
free(chunks[i]);
|
|
}
|
|
|
|
chunks[9] = malloc(0x110);
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
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 **o chunk no bin não ordenado ir 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.
|
|
```
|
|
</details>
|
|
|
|
### Grandes bins
|
|
|
|
Ao contrário dos pequenos bins, que gerenciam pedaços de tamanhos fixos, cada **grande bin lida com uma faixa de tamanhos de pedaços**. Isso é mais flexível, permitindo que o sistema acomode **vários tamanhos** sem precisar de um bin separado para cada tamanho.
|
|
|
|
Em um alocador de memória, os grandes bins começam onde os pequenos bins terminam. As faixas para grandes bins crescem progressivamente, o que significa que o primeiro bin pode cobrir pedaços de 512 a 576 bytes, enquanto o próximo cobre de 576 a 640 bytes. Esse padrão continua, com o maior bin contendo todos os pedaços acima de 1MB.
|
|
|
|
Os grandes bins são mais lentos para operar em comparação com os pequenos bins porque eles 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 um grande bin, ele precisa ser classificado, e quando a memória é alocada, o sistema deve encontrar o pedaço certo. Esse trabalho extra os torna **mais lentos**, mas como as alocações grandes são menos comuns do que as pequenas, é uma troca aceitável.
|
|
|
|
Existem:
|
|
|
|
* 32 bins de faixa de 64B (colidem com pequenos bins)
|
|
* 16 bins de faixa de 512B (colidem com pequenos bins)
|
|
* 8 bins de faixa de 4096B (parte colide com pequenos bins)
|
|
* 4 bins de faixa de 32768B
|
|
* 2 bins de faixa de 262144B
|
|
* 1 bin para tamanhos restantes
|
|
|
|
<details>
|
|
|
|
<summary>Código de tamanhos de grandes bins</summary>
|
|
```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:
|
|
```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.
|
|
```
|
|
</details>
|
|
|
|
### Top Chunk
|
|
```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))
|
|
```
|
|
Basicamente, este é um chunk que contém toda a 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.
|
|
|
|
<details>
|
|
|
|
<summary>Observe o exemplo do Top Chunk</summary>
|
|
```c
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
int main(void)
|
|
{
|
|
char *chunk;
|
|
chunk = malloc(24);
|
|
printf("Address of the chunk: %p\n", (void *)chunk);
|
|
gets(chunk);
|
|
return 0;
|
|
}
|
|
```
|
|
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 top chunk 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 Top chunk em seu cabeçalho de chunk:
|
|
```bash
|
|
gef➤ x/8wx 0xaaaaaaac1ae0 - 16
|
|
0xaaaaaaac1ad0: 0x00000000 0x00000000 0x00020531 0x00000000
|
|
0xaaaaaaac1ae0: 0x00000000 0x00000000 0x00000000 0x00000000
|
|
```
|
|
</details>
|
|
|
|
### Ú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:
|
|
|
|
{% content-ref url="heap-memory-functions/malloc-and-sysmalloc.md" %}
|
|
[malloc-and-sysmalloc.md](heap-memory-functions/malloc-and-sysmalloc.md)
|
|
{% endcontent-ref %}
|
|
|
|
## Fluxo de Liberação
|
|
|
|
Confira:
|
|
|
|
{% content-ref url="heap-memory-functions/free.md" %}
|
|
[free.md](heap-memory-functions/free.md)
|
|
{% endcontent-ref %}
|
|
|
|
## 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:
|
|
|
|
{% 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 %}
|
|
|
|
## Referências
|
|
|
|
* [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" %}
|
|
Aprenda e pratique Hacking AWS:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
|
Aprenda e pratique Hacking GCP: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
|
|
|
<details>
|
|
|
|
<summary>Support HackTricks</summary>
|
|
|
|
* Confira os [**planos de assinatura**](https://github.com/sponsors/carlospolop)!
|
|
* **Junte-se ao** 💬 [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga**-nos no **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|
* **Compartilhe truques de hacking enviando PRs para o** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositórios do github.
|
|
|
|
</details>
|
|
{% endhint %}
|