hacktricks/binary-exploitation/heap/bins-and-memory-allocations.md

211 lines
21 KiB
Markdown
Raw Normal View History

# Bins & Alocações de Memória
<details>
<summary><strong>Aprenda hacking AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Outras maneiras de apoiar o HackTricks:
* Se você deseja ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF**, confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
* Adquira o [**swag oficial PEASS & HackTricks**](https://peass.creator-spring.com)
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
* **Junte-se ao** 💬 [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-nos** no **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Compartilhe seus truques de hacking enviando PRs para os** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositórios do github.
</details>
## Informações Básicas
Para melhorar a eficiência na forma 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 existem 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) small bins, 63 large bins, 1 unsorted bin, 10 fast bins e 64 tcache bins por thread.
O endereço inicial para cada bin não ordenado, small e large está dentro do mesmo array. O índice 0 não é usado, 1 é o bin não ordenado, os bins 2-64 são os small bins e os bins 65-127 são os large bins.
### Small Bins
Os small bins são mais rápidos do que os large bins, mas mais lentos do que os 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 para encontrar o bin onde um espaço deve ser alocado e inserir e remover entradas nessas listas.
### Large Bins
Ao contrário dos small bins, que gerenciam chunks de tamanhos fixos, cada **large bin lida com uma faixa de tamanhos de chunk**. 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 large bins começam onde os small bins terminam. As faixas para os large bins crescem progressivamente, o que significa que o primeiro bin pode cobrir chunks 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 chunks acima de 1MB.
Os large bins são mais lentos de operar em comparação com os small bins porque eles precisam **ordenar e pesquisar em uma lista de tamanhos de chunk variados para encontrar o melhor encaixe** para uma alocação. Quando um chunk é inserido em um large bin, ele precisa ser ordenado, e quando a memória é alocada, o sistema deve encontrar o chunk certo. Esse trabalho extra os torna **mais lentos**, mas como alocações grandes são menos comuns do que as pequenas, é uma troca aceitável.
Existem:
* 32 bins de faixa de 64B
* 16 bins de faixa de 512B
* 8 bins de faixa de 4096B
* 4 bins de faixa de 32768B
* 2 bins de faixa de 262144B
* 1 bin para tamanhos restantes
### Unsorted Bin
O unsorted bin é um **cache rápido** usado pelo gerenciador de heap para tornar a alocação de memória mais rápida. Veja como funciona: Quando um programa libera memória, o gerenciador de heap não a coloca imediatamente em um bin específico. Em vez disso, primeiro tenta **fundir 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 **solicita memória**, o gerenciador de heap **verifica o unsorted bin** para ver se há um chunk de tamanho suficiente. Se encontrar um, ele o usa imediatamente. Se não encontrar um chunk adequado, move os chunks liberados para seus bins correspondentes, seja small ou large, com base em seus tamanhos.
Portanto, o unsorted bin é uma maneira de acelerar a alocação de memória reutilizando rapidamente a memória liberada recentemente e reduzindo a necessidade de pesquisas e fusões demoradas.
{% hint style="danger" %}
Observe que mesmo que os chunks sejam de categorias diferentes, se um chunk disponível estiver colidindo com outro chunk disponível (mesmo que sejam de categorias diferentes), eles serão mesclados.
{% endhint %}
### Fast Bins
Os fast bins são projetados para **acelerar a alocação de memória para pequenos chunks** mantendo chunks liberados recentemente em uma estrutura de acesso rápido. Esses bins usam uma abordagem Last-In, First-Out (LIFO), o que significa que o **chunk liberado mais recentemente é o primeiro** a ser reutilizado quando há uma nova solicitação 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, **os fast bins usam listas encadeadas simples**, não duplamente encadeadas, o que melhora ainda mais a velocidade. Como os chunks nos fast bins 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 liberado mais recentemente desse tamanho. Portanto:
* 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 para o chunk disponível atual e o endereço para esse chunk recém-liberado será colocado no cabeçalho.
{% hint style="danger" %}
Chunks nos fast bins não são definidos automaticamente como disponíveis, então eles permanecem como chunks de fast bin por algum tempo em vez de poderem ser mesclados com outros chunks.
{% endhint %}
### Tcache (Cache por Thread) Bins
Mesmo que as threads tentem ter sua própria heap (veja [Arenas](bins-and-memory-allocations.md#arenas) e [Subheaps](bins-and-memory-allocations.md#subheaps)), há a possibilidade de que um processo com muitas threads (como um servidor web) **acabe compartilhando a heap com outras threads**. Nesse caso, a solução principal é o uso de **lockers**, que podem **desacelerar significativamente as threads**.
Portanto, um tcache é semelhante a um fast bin por thread no sentido de que é uma **lista encadeada simples** que não mescla chunks. Cada thread tem **64 tcache bins encadeados simples**. 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 de 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 do tcache **não estiver cheio** (já com 7 chunks), **ele será alocado lá**. Se não puder ir para o tcache, precisará esperar o bloqueio da 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 será usado**, caso contrário, precisará esperar o bloqueio da heap para poder encontrar um nos bins globais ou criar um novo.\
Há também uma otimização, nesse caso, enquanto tiver o bloqueio da heap, a thread **preencherá seu Tcache com chunks da heap (7) do tamanho solicitado**, para que, caso precise de mais, os encontre no Tcache.
## Fluxo de Alocação
{% hint style="success" %}
(Esta explicação atual é de [https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions). TODO: Verificar última versão e atualizá-la)
{% endhint %}
As alocações são finalmente realizadas com a função: `void * _int_malloc (mstate av, size_t bytes)` e seguem esta ordem:
1. Atualiza `bytes` para cuidar dos **alinhamentos**, etc.
2. Verifica se `av` é **NULL** ou não.
3. No caso de ausência de **arena utilizável** (quando `av` é NULL), chama `sysmalloc` para obter um chunk usando mmap. Se bem-sucedido, chama `alloc_perturb`. Retorna o ponteiro.
4. Dependendo do tamanho:
* \[Adição ao original] Usa tcache antes de verificar o próximo fastbin.
* \[Adição ao original] Se não houver tcache, mas um bin diferente for usado (ver etapa posterior), tenta preencher a tcache a partir desse bin.
* Se o tamanho estiver na faixa do **fastbin**:
1. Obtém o índice na matriz fastbin para acessar um bin apropriado de acordo com o tamanho solicitado.
2. Remove o primeiro chunk nesse bin e faz com que `victim` aponte para ele.
3. Se `victim` for NULL, passa para o próximo caso (smallbin).
4. Se `victim` não for NULL, verifica o tamanho do chunk para garantir que pertença a esse bin específico. Caso contrário, é lançado um erro ("malloc(): corrupção de memória (fast)").
5. Chama `alloc_perturb` e então retorna o ponteiro.
* Se o tamanho estiver na faixa do **smallbin**:
1. Obtém o índice na matriz smallbin para acessar um bin apropriado de acordo com o tamanho solicitado.
2. Se não houver chunks neste bin, passa para o próximo caso. Isso é verificado comparando os ponteiros `bin` e `bin->bk`.
3. `victim` é igual a `bin->bk` (o último chunk no bin). Se for NULL (ocorre durante a `inicialização`), chama `malloc_consolidate` e pula esta etapa completa de verificação em bins diferentes.
4. Caso contrário, quando `victim` não for NULL, verifica se `victim->bk->fd` e `victim` são iguais ou não. Se não forem iguais, é lançado um erro (`malloc(): lista duplamente vinculada smallbin corrompida`).
5. Define o bit PREV\_INSUSE para o próximo chunk (na memória, não na lista duplamente vinculada) para `victim`.
6. Remove este chunk da lista do bin.
7. Define o bit de arena apropriado para este chunk dependendo de `av`.
8. Chama `alloc_perturb` e então retorna o ponteiro.
* Se o tamanho não estiver na faixa do smallbin:
1. Obtém o índice na matriz largebin para acessar um bin apropriado de acordo com o tamanho solicitado.
2. Verifica se `av` possui fastchunks ou não. Isso é feito verificando o `FASTCHUNKS_BIT` em `av->flags`. Se sim, chama `malloc_consolidate` em `av`.
5. Se nenhum ponteiro foi retornado ainda, isso significa um ou mais dos seguintes casos:
1. O tamanho está na faixa do 'fastbin' mas nenhum fastchunk está disponível.
2. O tamanho está na faixa do 'smallbin' mas nenhum smallchunk está disponível (chama `malloc_consolidate` durante a inicialização).
3. O tamanho está na faixa do 'largebin'.
6. Em seguida, os **chunks não ordenados** são verificados e os chunks percorridos são colocados em bins. Este é o único lugar onde os chunks são colocados em bins. Itera o bin não ordenado a partir do 'TAIL'.
1. `victim` aponta para o chunk atual em consideração.
2. Verifica se o tamanho do chunk de `victim` está dentro da faixa mínima (`2*SIZE_SZ`) e máxima (`av->system_mem`). Lança um erro (`malloc(): corrupção de memória`) caso contrário.
3. Se (o tamanho do chunk solicitado está na faixa do smallbin) e (`victim` é o último chunk restante) e (é o único chunk no bin não ordenado) e (o tamanho dos chunks >= o solicitado): **Divide o chunk em 2 chunks**:
* O primeiro chunk corresponde ao tamanho solicitado e é retornado.
* O chunk restante se torna o novo último chunk restante. Ele é reinserido no bin não ordenado.
1. Define os campos `chunk_size` e `chunk_prev_size` apropriadamente para ambos os chunks.
2. O primeiro chunk é retornado após chamar `alloc_perturb`.
3. Se a condição acima for falsa, o controle chega aqui. Remove `victim` do bin não ordenado. Se o tamanho de `victim` corresponder exatamente ao tamanho solicitado, retorne este chunk após chamar `alloc_perturb`.
4. Se o tamanho de `victim` estiver na faixa do smallbin, adicione o chunk no smallbin apropriado no `HEAD`.
5. Caso contrário, insira no largebin apropriado mantendo a ordem classificada:
6. Primeiro verifica o último chunk (menor). Se `victim` for menor que o último chunk, insere-o no final.
7. Caso contrário, faça um loop para encontrar um chunk com tamanho >= tamanho de `victim`. Se o tamanho for exatamente o mesmo, sempre insira na segunda posição.
8. Repita toda essa etapa um máximo de `MAX_ITERS` (10000) vezes ou até que todos os chunks no bin não ordenado se esgotem.
7. Após verificar os chunks não ordenados, verifique se o tamanho solicitado não está na faixa do smallbin, se sim, verifique os **largebins**.
1. Obtém o índice na matriz largebin para acessar um bin apropriado de acordo com o tamanho solicitado.
2. Se o tamanho do maior chunk (o primeiro chunk no bin) for maior que o tamanho solicitado:
1. Itera a partir do 'TAIL' para encontrar um chunk (`victim`) com o menor tamanho >= o tamanho solicitado.
2. Chama `unlink` para remover o chunk `victim` do bin.
3. Calcula `remainder_size` para o chunk de `victim` (este será o tamanho do chunk de `victim` - tamanho solicitado).
4. Se este `remainder_size` >= `MINSIZE` (o tamanho mínimo do chunk incluindo os cabeçalhos), divide o chunk em dois chunks. Caso contrário, o chunk inteiro de `victim` será retornado. Insira o chunk restante no bin não ordenado (no final do 'TAIL'). É feita uma verificação no bin não ordenado se `unsorted_chunks(av)->fd->bk == unsorted_chunks(av)`. Um erro é lançado caso contrário ("malloc(): chunks não ordenados corrompidos").
5. Retorna o chunk `victim` após chamar `alloc_perturb`.
8. Até agora, verificamos o bin não ordenado e também o respectivo fast, small ou large bin. Observe que um único bin (fast ou small) foi verificado usando o tamanho **exato** do chunk solicitado. Repita as seguintes etapas até que todos os bins se esgotem:
1. O índice na matriz bin é incrementado para verificar o próximo bin.
2. Usa o mapa `av->binmap` para pular os bins vazios.
3. `victim` aponta para o 'TAIL' do bin atual.
4. Usando o binmap garante que se um bin for pulado (no segundo passo acima), ele está definitivamente vazio. No entanto, não garante que todos os bins vazios serão pulados. Verifique se o victim está vazio ou não. Se estiver vazio, pule novamente o bin e repita o processo acima (ou 'continue' este loop) até chegarmos a um bin não vazio.
5. Divida o chunk (`victim` aponta para o último chunk de um bin não vazio) em dois chunks. Insira o chunk restante no bin não ordenado (no final do 'TAIL'). É feita uma verificação no bin não ordenado se `unsorted_chunks(av)->fd->bk == unsorted_chunks(av)`. Um erro é lançado caso contrário ("malloc(): chunks não ordenados corrompidos 2").
6. Retorna o chunk `victim` após chamar `alloc_perturb`.
9. Se ainda nenhum bin vazio for encontrado, o chunk 'top' será usado para atender à solicitação:
1. `victim` aponta para `av->top`.
2. Se o tamanho do chunk 'top' >= 'tamanho solicitado' + `MINSIZE`, divida-o em dois chunks. Neste caso, o chunk restante se torna o novo chunk 'top' e o outro chunk é retornado ao usuário após chamar `alloc_perturb`.
3. Verifica se `av` possui fastchunks ou não. Isso é feito verificando o `FASTCHUNKS_BIT` em `av->flags`. Se sim, chama `malloc_consolidate` em `av`. Retorna para a etapa 6 (onde verificamos o bin não ordenado).
4. Se `av` não tiver fastchunks, chama `sysmalloc` e retorna o ponteiro obtido após chamar `alloc_perturb`.
## Fluxo Livre
{% hint style="success" %}
(Esta explicação atual é de [https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions](https://heap-exploitation.dhavalkapil.com/diving\_into\_glibc\_heap/core\_functions). TODO: Verificar última versão e atualizá-la)
{% endhint %}
A função final que libera pedaços de memória é `_int_free (mstate av, mchunkptr p, int have_lock)`:
1. Verificar se `p` está antes de `p + chunksize(p)` na memória (para evitar embrulhar). Um erro (`free(): ponteiro inválido`) é lançado caso contrário.
2. Verificar se o pedaço tem pelo menos o tamanho `MINSIZE` ou é um múltiplo de `MALLOC_ALIGNMENT`. Um erro (`free(): tamanho inválido`) é lançado caso contrário.
3. Se o tamanho do pedaço estiver na lista fastbin:
1. Verificar se o tamanho do próximo pedaço está entre o tamanho mínimo e máximo (`av->system_mem`), lançar um erro (`free(): tamanho próximo inválido (rápido)`) caso contrário.
2. Chamar `free_perturb` no pedaço.
3. Definir `FASTCHUNKS_BIT` para `av`.
4. Obter índice na matriz fastbin de acordo com o tamanho do pedaço.
5. Verificar se o topo do bin não é o pedaço que estamos adicionando. Caso contrário, lançar um erro (`dupla liberação ou corrupção (topo rápido)`).
6. Verificar se o tamanho do pedaço fastbin no topo é o mesmo que o pedaço que estamos adicionando. Caso contrário, lançar um erro (`entrada fastbin inválida (liberação)`).
7. Inserir o pedaço no topo da lista fastbin e retornar.
4. Se o pedaço não estiver mapeado:
1. Verificar se o pedaço é o pedaço superior ou não. Se sim, um erro (`dupla liberação ou corrupção (topo)`) é lançado.
2. Verificar se o próximo pedaço (por memória) está dentro dos limites da arena. Se não estiver, um erro (`dupla liberação ou corrupção (fora)`) é lançado.
3. Verificar se o bit anterior em uso do próximo pedaço (por memória) está marcado ou não. Se não estiver, um erro (`dupla liberação ou corrupção (!prev)`) é lançado.
4. Verificar se o tamanho do próximo pedaço está entre o tamanho mínimo e máximo (`av->system_mem`). Se não estiver, um erro (`free(): tamanho próximo inválido (normal)`) é lançado.
5. Chamar `free_perturb` no pedaço.
6. Se o pedaço anterior (por memória) não estiver em uso, chamar `unlink` no pedaço anterior.
7. Se o próximo pedaço (por memória) não for o pedaço superior:
1. Se o próximo pedaço não estiver em uso, chamar `unlink` no próximo pedaço.
2. Mesclar o pedaço com o anterior, próximo (por memória), se algum estiver livre e adicioná-lo ao início do bin não ordenado. Antes de inserir, verificar se `unsorted_chunks(av)->fd->bk == unsorted_chunks(av)` ou não. Se não, um erro ("free(): pedaços não ordenados corrompidos") é lançado.
8. Se o próximo pedaço (por memória) era um pedaço superior, mesclar os pedaços apropriadamente em um único pedaço superior.
5. Se o pedaço estava mapeado, chamar `munmap_chunk`.
## Verificações de Segurança das Funções de Heap
Verifique as verificações de segurança realizadas por funções amplamente utilizadas no heap em:
{% content-ref url="heap-functions-security-checks.md" %}
[heap-functions-security-checks.md](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)
<details>
<summary><strong>Aprenda hacking AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Outras maneiras de apoiar o HackTricks:
* Se você quiser ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF** Verifique os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
* Obtenha o [**swag oficial PEASS & HackTricks**](https://peass.creator-spring.com)
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
* **Junte-se ao** 💬 [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-nos** no **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Compartilhe seus truques de hacking enviando PRs para o** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
</details>