mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-24 12:03:37 +00:00
502 lines
23 KiB
Markdown
502 lines
23 KiB
Markdown
# Libc Heap
|
|
|
|
## Heap Basics
|
|
|
|
O heap é basicamente o lugar onde um programa poderá armazenar dados quando solicita dados chamando funções como **`malloc`**, `calloc`... Além disso, quando essa memória não é mais necessária, ela é disponibilizada chamando a função **`free`**.
|
|
|
|
Como mostrado, é logo após onde o binário está sendo carregado na memória (ver a seção `[heap]`):
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1241).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
### Basic Chunk Allocation
|
|
|
|
Quando alguns dados são solicitados para serem armazenados no heap, um espaço do heap é alocado para isso. Esse espaço pertencerá a um bin e apenas os dados solicitados + o espaço dos cabeçalhos do bin + o deslocamento do tamanho mínimo do bin serão reservados para o chunk. O objetivo é reservar a menor quantidade de memória possível sem complicar a localização de onde cada chunk está. Para isso, as informações de chunk de metadados são usadas para saber onde cada chunk usado/livre está.
|
|
|
|
Existem diferentes maneiras de reservar o espaço, dependendo principalmente do bin utilizado, mas uma metodologia geral é a seguinte:
|
|
|
|
* O programa começa solicitando uma certa quantidade de memória.
|
|
* Se na lista de chunks houver alguém disponível grande o suficiente para atender à solicitação, ele será usado.
|
|
* Isso pode até significar que parte do chunk disponível será usada para essa solicitação e o restante será adicionado à lista de chunks.
|
|
* Se não houver nenhum chunk disponível na lista, mas ainda houver espaço na memória heap alocada, o gerenciador de heap cria um novo chunk.
|
|
* Se não houver espaço de heap suficiente para alocar o novo chunk, o gerenciador de heap solicita ao kernel que expanda a memória alocada para o heap e, em seguida, usa essa memória para gerar o novo chunk.
|
|
* Se tudo falhar, `malloc` retorna nulo.
|
|
|
|
Observe que se a **memória solicitada ultrapassar um limite**, **`mmap`** será usado para mapear a memória solicitada.
|
|
|
|
## Arenas
|
|
|
|
Em aplicações **multithreaded**, o gerenciador de heap deve prevenir **condições de corrida** que poderiam levar a falhas. Inicialmente, isso era feito usando um **mutex global** para garantir que apenas um thread pudesse acessar o heap por vez, mas isso causou **problemas de desempenho** devido ao gargalo induzido pelo mutex.
|
|
|
|
Para resolver isso, o alocador de heap ptmalloc2 introduziu "arenas", onde **cada arena** atua como um **heap separado** com suas **próprias** estruturas de **dados** e **mutex**, permitindo que múltiplos threads realizem operações de heap sem interferir uns nos outros, desde que usem arenas diferentes.
|
|
|
|
A arena "principal" padrão lida com operações de heap para aplicações de thread único. Quando **novos threads** são adicionados, o gerenciador de heap os atribui **arenas secundárias** para reduzir a contenção. Ele tenta primeiro anexar cada novo thread a uma arena não utilizada, criando novas se necessário, até um limite de 2 vezes o número de núcleos de CPU para sistemas de 32 bits e 8 vezes para sistemas de 64 bits. Uma vez que o limite é alcançado, **threads devem compartilhar arenas**, levando a uma potencial contenção.
|
|
|
|
Diferente da arena principal, que se expande usando a chamada de sistema `brk`, as arenas secundárias criam "subheaps" usando `mmap` e `mprotect` para simular o comportamento do heap, permitindo flexibilidade na gestão de memória para operações multithreaded.
|
|
|
|
### Subheaps
|
|
|
|
Subheaps servem como reservas de memória para arenas secundárias em aplicações multithreaded, permitindo que elas cresçam e gerenciem suas próprias regiões de heap separadamente do heap principal. Aqui está como os subheaps diferem do heap inicial e como operam:
|
|
|
|
1. **Heap Inicial vs. Subheaps**:
|
|
* O heap inicial está localizado diretamente após o binário do programa na memória, e se expande usando a chamada de sistema `sbrk`.
|
|
* Subheaps, usados por arenas secundárias, são criados através de `mmap`, uma chamada de sistema que mapeia uma região de memória especificada.
|
|
2. **Reserva de Memória com `mmap`**:
|
|
* Quando o gerenciador de heap cria um subheap, ele reserva um grande bloco de memória através de `mmap`. Essa reserva não aloca memória imediatamente; simplesmente designa uma região que outros processos ou alocações do sistema não devem usar.
|
|
* Por padrão, o tamanho reservado para um subheap é de 1 MB para processos de 32 bits e 64 MB para processos de 64 bits.
|
|
3. **Expansão Gradual com `mprotect`**:
|
|
* A região de memória reservada é inicialmente marcada como `PROT_NONE`, indicando que o kernel não precisa alocar memória física para esse espaço ainda.
|
|
* Para "crescer" o subheap, o gerenciador de heap usa `mprotect` para mudar as permissões de página de `PROT_NONE` para `PROT_READ | PROT_WRITE`, solicitando ao kernel que aloque memória física para os endereços previamente reservados. Essa abordagem passo a passo permite que o subheap se expanda conforme necessário.
|
|
* Uma vez que todo o subheap é esgotado, o gerenciador de heap cria um novo subheap para continuar a alocação.
|
|
|
|
### heap\_info <a href="#heap_info" id="heap_info"></a>
|
|
|
|
Esta struct aloca informações relevantes do heap. Além disso, a memória do heap pode não ser contínua após mais alocações, esta struct também armazenará essa informação.
|
|
```c
|
|
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/arena.c#L837
|
|
|
|
typedef struct _heap_info
|
|
{
|
|
mstate ar_ptr; /* Arena for this heap. */
|
|
struct _heap_info *prev; /* Previous heap. */
|
|
size_t size; /* Current size in bytes. */
|
|
size_t mprotect_size; /* Size in bytes that has been mprotected
|
|
PROT_READ|PROT_WRITE. */
|
|
size_t pagesize; /* Page size used when allocating the arena. */
|
|
/* Make sure the following data is properly aligned, particularly
|
|
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
|
|
MALLOC_ALIGNMENT. */
|
|
char pad[-3 * SIZE_SZ & MALLOC_ALIGN_MASK];
|
|
} heap_info;
|
|
```
|
|
### malloc\_state
|
|
|
|
**Cada heap** (arena principal ou outras arenas de threads) tem uma **estrutura `malloc_state`.**\
|
|
É importante notar que a **estrutura `malloc_state` da arena principal** é uma **variável global na libc** (portanto localizada no espaço de memória da libc).\
|
|
No caso das **estruturas `malloc_state`** dos heaps das threads, elas estão localizadas **dentro do "heap" da própria thread**.
|
|
|
|
Há algumas coisas interessantes a notar a partir desta estrutura (veja o código C abaixo):
|
|
|
|
* `__libc_lock_define (, mutex);` Está lá para garantir que esta estrutura do heap seja acessada por 1 thread de cada vez
|
|
* Flags:
|
|
* ```c
|
|
#define NONCONTIGUOUS_BIT (2U)
|
|
|
|
#define contiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) == 0)
|
|
#define noncontiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) != 0)
|
|
#define set_noncontiguous(M) ((M)->flags |= NONCONTIGUOUS_BIT)
|
|
#define set_contiguous(M) ((M)->flags &= ~NONCONTIGUOUS_BIT)
|
|
```
|
|
* O `mchunkptr bins[NBINS * 2 - 2];` contém **ponteiros** para os **primeiros e últimos chunks** dos **bins** pequenos, grandes e não ordenados (o -2 é porque o índice 0 não é usado)
|
|
* Portanto, o **primeiro chunk** desses bins terá um **ponteiro reverso para esta estrutura** e o **último chunk** desses bins terá um **ponteiro para frente** para esta estrutura. O que basicamente significa que se você puder **vazar esses endereços na arena principal**, você terá um ponteiro para a estrutura na **libc**.
|
|
* As structs `struct malloc_state *next;` e `struct malloc_state *next_free;` são listas encadeadas de arenas
|
|
* O chunk `top` é o último "chunk", que é basicamente **todo o espaço restante do heap**. Uma vez que o chunk top está "vazio", o heap está completamente utilizado e precisa solicitar mais espaço.
|
|
* O chunk `last reminder` vem de casos onde um chunk de tamanho exato não está disponível e, portanto, um chunk maior é dividido, uma parte do ponteiro restante é colocada aqui.
|
|
```c
|
|
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1812
|
|
|
|
struct malloc_state
|
|
{
|
|
/* Serialize access. */
|
|
__libc_lock_define (, mutex);
|
|
|
|
/* Flags (formerly in max_fast). */
|
|
int flags;
|
|
|
|
/* Set if the fastbin chunks contain recently inserted free blocks. */
|
|
/* Note this is a bool but not all targets support atomics on booleans. */
|
|
int have_fastchunks;
|
|
|
|
/* Fastbins */
|
|
mfastbinptr fastbinsY[NFASTBINS];
|
|
|
|
/* Base of the topmost chunk -- not otherwise kept in a bin */
|
|
mchunkptr top;
|
|
|
|
/* The remainder from the most recent split of a small request */
|
|
mchunkptr last_remainder;
|
|
|
|
/* Normal bins packed as described above */
|
|
mchunkptr bins[NBINS * 2 - 2];
|
|
|
|
/* Bitmap of bins */
|
|
unsigned int binmap[BINMAPSIZE];
|
|
|
|
/* Linked list */
|
|
struct malloc_state *next;
|
|
|
|
/* Linked list for free arenas. Access to this field is serialized
|
|
by free_list_lock in arena.c. */
|
|
struct malloc_state *next_free;
|
|
|
|
/* Number of threads attached to this arena. 0 if the arena is on
|
|
the free list. Access to this field is serialized by
|
|
free_list_lock in arena.c. */
|
|
INTERNAL_SIZE_T attached_threads;
|
|
|
|
/* Memory allocated from the system in this arena. */
|
|
INTERNAL_SIZE_T system_mem;
|
|
INTERNAL_SIZE_T max_system_mem;
|
|
};
|
|
```
|
|
### malloc\_chunk
|
|
|
|
Esta estrutura representa um determinado bloco de memória. Os vários campos têm significados diferentes para blocos alocados e não alocados.
|
|
```c
|
|
// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
|
struct malloc_chunk {
|
|
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk, if it is free. */
|
|
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
|
|
struct malloc_chunk* fd; /* double links -- used only if this chunk is free. */
|
|
struct malloc_chunk* bk;
|
|
/* Only used for large blocks: pointer to next larger size. */
|
|
struct malloc_chunk* fd_nextsize; /* double links -- used only if this chunk is free. */
|
|
struct malloc_chunk* bk_nextsize;
|
|
};
|
|
|
|
typedef struct malloc_chunk* mchunkptr;
|
|
```
|
|
Como comentado anteriormente, esses chunks também possuem alguns metadados, muito bem representados nesta imagem:
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1242).png" alt=""><figcaption><p><a href="https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png">https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png</a></p></figcaption></figure>
|
|
|
|
Os metadados geralmente são 0x08B, indicando o tamanho atual do chunk, usando os últimos 3 bits para indicar:
|
|
|
|
* `A`: Se 1, vem de um subheap; se 0, está na arena principal
|
|
* `M`: Se 1, este chunk é parte de um espaço alocado com mmap e não parte de um heap
|
|
* `P`: Se 1, o chunk anterior está em uso
|
|
|
|
Então, o espaço para os dados do usuário, e finalmente 0x08B para indicar o tamanho do chunk anterior quando o chunk está disponível (ou para armazenar dados do usuário quando está alocado).
|
|
|
|
Além disso, quando disponível, os dados do usuário também são usados para conter alguns dados:
|
|
|
|
* **`fd`**: Ponteiro para o próximo chunk
|
|
* **`bk`**: Ponteiro para o chunk anterior
|
|
* **`fd_nextsize`**: Ponteiro para o primeiro chunk na lista que é menor que ele mesmo
|
|
* **`bk_nextsize`:** Ponteiro para o primeiro chunk na lista que é maior que ele mesmo
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1243).png" alt=""><figcaption><p><a href="https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png">https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png</a></p></figcaption></figure>
|
|
|
|
{% hint style="info" %}
|
|
Note como vincular a lista dessa forma evita a necessidade de ter um array onde cada chunk é registrado.
|
|
{% endhint %}
|
|
|
|
### Ponteiros de Chunk
|
|
|
|
Quando malloc é usado, um ponteiro para o conteúdo que pode ser escrito é retornado (logo após os cabeçalhos), no entanto, ao gerenciar chunks, é necessário um ponteiro para o início dos cabeçalhos (metadados).\
|
|
Para essas conversões, essas funções são usadas:
|
|
```c
|
|
// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
|
|
|
/* Convert a chunk address to a user mem pointer without correcting the tag. */
|
|
#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))
|
|
|
|
/* Convert a user mem pointer to a chunk address and extract the right tag. */
|
|
#define mem2chunk(mem) ((mchunkptr)tag_at (((char*)(mem) - CHUNK_HDR_SZ)))
|
|
|
|
/* The smallest possible chunk */
|
|
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))
|
|
|
|
/* The smallest size we can malloc is an aligned minimal chunk */
|
|
|
|
#define MINSIZE \
|
|
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
|
|
```
|
|
### Alinhamento e tamanho mínimo
|
|
|
|
O ponteiro para o chunk e `0x0f` devem ser 0.
|
|
```c
|
|
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/sysdeps/generic/malloc-size.h#L61
|
|
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
|
|
|
|
// https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/sysdeps/i386/malloc-alignment.h
|
|
#define MALLOC_ALIGNMENT 16
|
|
|
|
|
|
// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
|
/* Check if m has acceptable alignment */
|
|
#define aligned_OK(m) (((unsigned long)(m) & MALLOC_ALIGN_MASK) == 0)
|
|
|
|
#define misaligned_chunk(p) \
|
|
((uintptr_t)(MALLOC_ALIGNMENT == CHUNK_HDR_SZ ? (p) : chunk2mem (p)) \
|
|
& MALLOC_ALIGN_MASK)
|
|
|
|
|
|
/* pad request bytes into a usable size -- internal version */
|
|
/* Note: This must be a macro that evaluates to a compile time constant
|
|
if passed a literal constant. */
|
|
#define request2size(req) \
|
|
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
|
|
MINSIZE : \
|
|
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
|
|
|
|
/* Check if REQ overflows when padded and aligned and if the resulting
|
|
value is less than PTRDIFF_T. Returns the requested size or
|
|
MINSIZE in case the value is less than MINSIZE, or 0 if any of the
|
|
previous checks fail. */
|
|
static inline size_t
|
|
checked_request2size (size_t req) __nonnull (1)
|
|
{
|
|
if (__glibc_unlikely (req > PTRDIFF_MAX))
|
|
return 0;
|
|
|
|
/* When using tagged memory, we cannot share the end of the user
|
|
block with the header for the next chunk, so ensure that we
|
|
allocate blocks that are rounded up to the granule size. Take
|
|
care not to overflow from close to MAX_SIZE_T to a small
|
|
number. Ideally, this would be part of request2size(), but that
|
|
must be a macro that produces a compile time constant if passed
|
|
a constant literal. */
|
|
if (__glibc_unlikely (mtag_enabled))
|
|
{
|
|
/* Ensure this is not evaluated if !mtag_enabled, see gcc PR 99551. */
|
|
asm ("");
|
|
|
|
req = (req + (__MTAG_GRANULE_SIZE - 1)) &
|
|
~(size_t)(__MTAG_GRANULE_SIZE - 1);
|
|
}
|
|
|
|
return request2size (req);
|
|
}
|
|
```
|
|
Note que, para calcular o espaço total necessário, `SIZE_SZ` é adicionado apenas 1 vez porque o campo `prev_size` pode ser usado para armazenar dados, portanto, apenas o cabeçalho inicial é necessário.
|
|
|
|
### Obter dados do Chunk e alterar metadados
|
|
|
|
Essas funções funcionam recebendo um ponteiro para um chunk e são úteis para verificar/definir metadados:
|
|
|
|
* Verificar flags do chunk
|
|
```c
|
|
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
|
|
|
|
|
/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
|
|
#define PREV_INUSE 0x1
|
|
|
|
/* extract inuse bit of previous chunk */
|
|
#define prev_inuse(p) ((p)->mchunk_size & PREV_INUSE)
|
|
|
|
|
|
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
|
|
#define IS_MMAPPED 0x2
|
|
|
|
/* check for mmap()'ed chunk */
|
|
#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)
|
|
|
|
|
|
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
|
|
from a non-main arena. This is only set immediately before handing
|
|
the chunk to the user, if necessary. */
|
|
#define NON_MAIN_ARENA 0x4
|
|
|
|
/* Check for chunk from main arena. */
|
|
#define chunk_main_arena(p) (((p)->mchunk_size & NON_MAIN_ARENA) == 0)
|
|
|
|
/* Mark a chunk as not being on the main arena. */
|
|
#define set_non_main_arena(p) ((p)->mchunk_size |= NON_MAIN_ARENA)
|
|
```
|
|
* Tamanhos e ponteiros para outros pedaços
|
|
```c
|
|
/*
|
|
Bits to mask off when extracting size
|
|
|
|
Note: IS_MMAPPED is intentionally not masked off from size field in
|
|
macros for which mmapped chunks should never be seen. This should
|
|
cause helpful core dumps to occur if it is tried by accident by
|
|
people extending or adapting this malloc.
|
|
*/
|
|
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
|
|
|
|
/* Get size, ignoring use bits */
|
|
#define chunksize(p) (chunksize_nomask (p) & ~(SIZE_BITS))
|
|
|
|
/* Like chunksize, but do not mask SIZE_BITS. */
|
|
#define chunksize_nomask(p) ((p)->mchunk_size)
|
|
|
|
/* Ptr to next physical malloc_chunk. */
|
|
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))
|
|
|
|
/* Size of the chunk below P. Only valid if !prev_inuse (P). */
|
|
#define prev_size(p) ((p)->mchunk_prev_size)
|
|
|
|
/* Set the size of the chunk below P. Only valid if !prev_inuse (P). */
|
|
#define set_prev_size(p, sz) ((p)->mchunk_prev_size = (sz))
|
|
|
|
/* Ptr to previous physical malloc_chunk. Only valid if !prev_inuse (P). */
|
|
#define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))
|
|
|
|
/* Treat space at ptr + offset as a chunk */
|
|
#define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))
|
|
```
|
|
* Insue bit
|
|
```c
|
|
/* extract p's inuse bit */
|
|
#define inuse(p) \
|
|
((((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size) & PREV_INUSE)
|
|
|
|
/* set/clear chunk as being inuse without otherwise disturbing */
|
|
#define set_inuse(p) \
|
|
((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size |= PREV_INUSE
|
|
|
|
#define clear_inuse(p) \
|
|
((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size &= ~(PREV_INUSE)
|
|
|
|
|
|
/* check/set/clear inuse bits in known places */
|
|
#define inuse_bit_at_offset(p, s) \
|
|
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)
|
|
|
|
#define set_inuse_bit_at_offset(p, s) \
|
|
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)
|
|
|
|
#define clear_inuse_bit_at_offset(p, s) \
|
|
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE))
|
|
```
|
|
* Defina o cabeçalho e o rodapé (quando os números de chunk estão em uso)
|
|
```c
|
|
/* Set size at head, without disturbing its use bit */
|
|
#define set_head_size(p, s) ((p)->mchunk_size = (((p)->mchunk_size & SIZE_BITS) | (s)))
|
|
|
|
/* Set size/use field */
|
|
#define set_head(p, s) ((p)->mchunk_size = (s))
|
|
|
|
/* Set size at footer (only when chunk is not in use) */
|
|
#define set_foot(p, s) (((mchunkptr) ((char *) (p) + (s)))->mchunk_prev_size = (s))
|
|
```
|
|
* Obtenha o tamanho dos dados utilizáveis reais dentro do chunk
|
|
```c
|
|
#pragma GCC poison mchunk_size
|
|
#pragma GCC poison mchunk_prev_size
|
|
|
|
/* This is the size of the real usable data in the chunk. Not valid for
|
|
dumped heap chunks. */
|
|
#define memsize(p) \
|
|
(__MTAG_GRANULE_SIZE > SIZE_SZ && __glibc_unlikely (mtag_enabled) ? \
|
|
chunksize (p) - CHUNK_HDR_SZ : \
|
|
chunksize (p) - CHUNK_HDR_SZ + (chunk_is_mmapped (p) ? 0 : SIZE_SZ))
|
|
|
|
/* If memory tagging is enabled the layout changes to accommodate the granule
|
|
size, this is wasteful for small allocations so not done by default.
|
|
Both the chunk header and user data has to be granule aligned. */
|
|
_Static_assert (__MTAG_GRANULE_SIZE <= CHUNK_HDR_SZ,
|
|
"memory tagging is not supported with large granule.");
|
|
|
|
static __always_inline void *
|
|
tag_new_usable (void *ptr)
|
|
{
|
|
if (__glibc_unlikely (mtag_enabled) && ptr)
|
|
{
|
|
mchunkptr cp = mem2chunk(ptr);
|
|
ptr = __libc_mtag_tag_region (__libc_mtag_new_tag (ptr), memsize (cp));
|
|
}
|
|
return ptr;
|
|
}
|
|
```
|
|
## Exemplos
|
|
|
|
### Exemplo Rápido de Heap
|
|
|
|
Exemplo rápido de heap de [https://guyinatuxedo.github.io/25-heap/index.html](https://guyinatuxedo.github.io/25-heap/index.html) mas em arm64:
|
|
```c
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
void main(void)
|
|
{
|
|
char *ptr;
|
|
ptr = malloc(0x10);
|
|
strcpy(ptr, "panda");
|
|
}
|
|
```
|
|
Defina um ponto de interrupção no final da função principal e vamos descobrir onde a informação foi armazenada:
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1239).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
É possível ver que a string panda foi armazenada em `0xaaaaaaac12a0` (que foi o endereço dado como resposta pelo malloc dentro de `x0`). Verificando 0x10 bytes antes, é possível ver que o `0x0` representa que o **chunk anterior não está em uso** (comprimento 0) e que o comprimento deste chunk é `0x21`.
|
|
|
|
Os espaços extras reservados (0x21-0x10=0x11) vêm dos **cabeçalhos adicionados** (0x10) e 0x1 não significa que foi reservado 0x21B, mas os últimos 3 bits do comprimento do cabeçalho atual têm alguns significados especiais. Como o comprimento é sempre alinhado a 16 bytes (em máquinas de 64 bits), esses bits na verdade nunca serão usados pelo número de comprimento.
|
|
```
|
|
0x1: Previous in Use - Specifies that the chunk before it in memory is in use
|
|
0x2: Is MMAPPED - Specifies that the chunk was obtained with mmap()
|
|
0x4: Non Main Arena - Specifies that the chunk was obtained from outside of the main arena
|
|
```
|
|
### Exemplo de Multithreading
|
|
|
|
<details>
|
|
|
|
<summary>Multithread</summary>
|
|
```c
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
|
|
|
|
void* threadFuncMalloc(void* arg) {
|
|
printf("Hello from thread 1\n");
|
|
char* addr = (char*) malloc(1000);
|
|
printf("After malloc and before free in thread 1\n");
|
|
free(addr);
|
|
printf("After free in thread 1\n");
|
|
}
|
|
|
|
void* threadFuncNoMalloc(void* arg) {
|
|
printf("Hello from thread 2\n");
|
|
}
|
|
|
|
|
|
int main() {
|
|
pthread_t t1;
|
|
void* s;
|
|
int ret;
|
|
char* addr;
|
|
|
|
printf("Before creating thread 1\n");
|
|
getchar();
|
|
ret = pthread_create(&t1, NULL, threadFuncMalloc, NULL);
|
|
getchar();
|
|
|
|
printf("Before creating thread 2\n");
|
|
ret = pthread_create(&t1, NULL, threadFuncNoMalloc, NULL);
|
|
|
|
printf("Before exit\n");
|
|
getchar();
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
</details>
|
|
|
|
Depurando o exemplo anterior, é possível ver como no início há apenas 1 arena:
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
Então, após chamar o primeiro thread, aquele que chama malloc, uma nova arena é criada:
|
|
|
|
<figure><img src="../../.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
e dentro dela alguns chunks podem ser encontrados:
|
|
|
|
<figure><img src="../../.gitbook/assets/image (2) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
|
|
|
## Bins & Alocações/Liberções de Memória
|
|
|
|
Verifique quais são os bins e como estão organizados e como a memória é alocada e liberada em:
|
|
|
|
{% content-ref url="bins-and-memory-allocations.md" %}
|
|
[bins-and-memory-allocations.md](bins-and-memory-allocations.md)
|
|
{% endcontent-ref %}
|
|
|
|
## Verificações de Segurança das Funções do Heap
|
|
|
|
Funções envolvidas no heap realizarão certas verificações antes de executar suas ações para tentar garantir que o heap não foi corrompido:
|
|
|
|
{% 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/)
|