mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/libc-heap/use-after-free/first-fit.
This commit is contained in:
parent
3dbf612ee1
commit
bca3198e9a
@ -8,14 +8,14 @@ Quando você libera memória em um programa usando glibc, diferentes "bins" são
|
|||||||
|
|
||||||
### Bins Não Ordenados
|
### Bins Não Ordenados
|
||||||
|
|
||||||
Quando você libera um bloco de memória que não é um bloco rápido, ele vai para o bin não ordenado. Este bin atua como uma lista onde novos blocos liberados são adicionados à frente (o "cabeça"). Quando você solicita um novo bloco de memória, o alocador olha para o bin não ordenado a partir de trás (o "cauda") para encontrar um bloco que seja grande o suficiente. Se um bloco do bin não ordenado for maior do que o que você precisa, ele é dividido, com a parte da frente sendo retornada e a parte restante permanecendo no bin.
|
Quando você libera um bloco de memória que não é um bloco rápido, ele vai para o bin não ordenado. Este bin atua como uma lista onde novos blocos liberados são adicionados à frente (a "cabeça"). Quando você solicita um novo bloco de memória, o alocador olha para o bin não ordenado de trás para frente (a "cauda") para encontrar um bloco que seja grande o suficiente. Se um bloco do bin não ordenado for maior do que o que você precisa, ele é dividido, com a parte da frente sendo retornada e a parte restante permanecendo no bin.
|
||||||
|
|
||||||
Exemplo:
|
Exemplo:
|
||||||
|
|
||||||
- Você aloca 300 bytes (`a`), depois 250 bytes (`b`), libera `a` e solicita novamente 250 bytes (`c`).
|
- Você aloca 300 bytes (`a`), depois 250 bytes (`b`), então libera `a` e solicita novamente 250 bytes (`c`).
|
||||||
- Quando você libera `a`, ele vai para o bin não ordenado.
|
- Quando você libera `a`, ele vai para o bin não ordenado.
|
||||||
- Se você então solicitar 250 bytes novamente, o alocador encontra `a` na cauda e o divide, retornando a parte que atende ao seu pedido e mantendo o resto no bin.
|
- Se você então solicitar 250 bytes novamente, o alocador encontra `a` na cauda e o divide, retornando a parte que atende ao seu pedido e mantendo o restante no bin.
|
||||||
- `c` apontará para o anterior `a` e será preenchido com os `a's`.
|
- `c` apontará para o anterior `a` e será preenchido com o conteúdo de `a`.
|
||||||
```c
|
```c
|
||||||
char *a = malloc(300);
|
char *a = malloc(300);
|
||||||
char *b = malloc(250);
|
char *b = malloc(250);
|
||||||
@ -27,10 +27,6 @@ char *c = malloc(250);
|
|||||||
Fastbins são usados para pequenos pedaços de memória. Ao contrário dos bins não ordenados, fastbins adicionam novos pedaços ao início, criando um comportamento de último a entrar, primeiro a sair (LIFO). Se você solicitar um pequeno pedaço de memória, o alocador irá retirar do início do fastbin.
|
Fastbins são usados para pequenos pedaços de memória. Ao contrário dos bins não ordenados, fastbins adicionam novos pedaços ao início, criando um comportamento de último a entrar, primeiro a sair (LIFO). Se você solicitar um pequeno pedaço de memória, o alocador irá retirar do início do fastbin.
|
||||||
|
|
||||||
Exemplo:
|
Exemplo:
|
||||||
|
|
||||||
- Você aloca quatro pedaços de 20 bytes cada (`a`, `b`, `c`, `d`).
|
|
||||||
- Quando você os libera em qualquer ordem, os pedaços liberados são adicionados ao início do fastbin.
|
|
||||||
- Se você então solicitar um pedaço de 20 bytes, o alocador retornará o pedaço mais recentemente liberado do início do fastbin.
|
|
||||||
```c
|
```c
|
||||||
char *a = malloc(20);
|
char *a = malloc(20);
|
||||||
char *b = malloc(20);
|
char *b = malloc(20);
|
||||||
@ -45,18 +41,89 @@ b = malloc(20); // c
|
|||||||
c = malloc(20); // b
|
c = malloc(20); // b
|
||||||
d = malloc(20); // a
|
d = malloc(20); // a
|
||||||
```
|
```
|
||||||
|
---
|
||||||
|
### 🔥 Considerações modernas do glibc (tcache ≥ 2.26)
|
||||||
|
|
||||||
|
Desde o glibc 2.26, cada thread mantém seu próprio **tcache** que é consultado *antes* do bin não ordenado. Portanto, um cenário de first-fit **só será alcançado se**:
|
||||||
|
|
||||||
|
1. O tamanho solicitado é **maior que `tcache_max`** (0x420 em 64 bits por padrão), *ou*
|
||||||
|
2. O bin correspondente do tcache está **já cheio ou esvaziado manualmente** (alocando 7 elementos e mantendo-os em uso).
|
||||||
|
|
||||||
|
Em exploits reais, você geralmente adicionará uma rotina auxiliar como:
|
||||||
|
```c
|
||||||
|
// Drain the tcache for a given size
|
||||||
|
for(int i = 0; i < 7; i++) pool[i] = malloc(0x100);
|
||||||
|
for(int i = 0; i < 7; i++) free(pool[i]);
|
||||||
|
```
|
||||||
|
Uma vez que o tcache está esgotado, liberações subsequentes vão para o bin não ordenado e o comportamento clássico de first-fit (busca na cauda, inserção na cabeça) pode ser acionado novamente.
|
||||||
|
|
||||||
|
---
|
||||||
|
### 🚩 Criando um UAF de chunk sobreposto com first-fit
|
||||||
|
|
||||||
|
O fragmento abaixo (testado no glibc 2.38) mostra como o divisor no bin não ordenado pode ser abusado para criar 2 **ponteiros sobrepostos** – um primitivo poderoso que converte uma única liberação em uma escrita após a liberação.
|
||||||
|
```c
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
setbuf(stdout, NULL);
|
||||||
|
|
||||||
|
/* 1. prepare 2 adjacent chunks and free the first one */
|
||||||
|
char *A = malloc(0x420); // big enough to bypass tcache
|
||||||
|
char *B = malloc(0x420);
|
||||||
|
strcpy(A, "AAAA\n");
|
||||||
|
free(A); // A → unsorted
|
||||||
|
|
||||||
|
/* 2. request a *smaller* size to force a split of A */
|
||||||
|
char *C = malloc(0x400); // returns lower half of former A
|
||||||
|
|
||||||
|
/* 3. The remainder of A is still in the unsorted bin.
|
||||||
|
Another 0x400-byte malloc will now return the *same*
|
||||||
|
region pointed to by B – creating a UAF/overlap. */
|
||||||
|
char *C2 = malloc(0x400);
|
||||||
|
|
||||||
|
printf("B = %p\nC2 = %p (overlaps B)\n", B, C2);
|
||||||
|
|
||||||
|
// Arbitrary write in B is immediately visible via C2
|
||||||
|
memset(B, 'X', 0x10);
|
||||||
|
fwrite(C2, 1, 0x10, stdout); // prints Xs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Receita de exploração (comum em CTFs recentes):
|
||||||
|
|
||||||
|
1. **Drenar** o tcache para o tamanho alvo.
|
||||||
|
2. **Liberar** um chunk para que ele caia no bin não ordenado.
|
||||||
|
3. **Alocar** um tamanho ligeiramente menor – o alocador divide o chunk não ordenado.
|
||||||
|
4. **Alocar** novamente – a parte restante se sobrepõe a um chunk existente em uso → UAF.
|
||||||
|
5. Sobrescrever campos sensíveis (ponteiros de função, vtable de FILE, etc.)
|
||||||
|
|
||||||
|
Uma aplicação prática pode ser encontrada no desafio *Setjmp* das Quals HITCON 2024, onde este exato primitivo é usado para pivotar de um UAF para controle total de `__free_hook`.{{#ref}}
|
||||||
|
../../../../references/2024_setjmp_firstfit.md
|
||||||
|
{{#endref}}
|
||||||
|
|
||||||
|
---
|
||||||
|
### 🛡️ Mitigações & Fortalecimento
|
||||||
|
|
||||||
|
* **Safe-linking (glibc ≥ 2.32)** protege apenas as listas *tcache*/**fastbin** encadeadas. Os bins não ordenados/pequenos/grandes ainda armazenam ponteiros brutos, então sobreposições baseadas em first-fit permanecem viáveis se você conseguir obter um leak de heap.
|
||||||
|
* **Criptografia de ponteiro de heap & MTE** (ARM64) ainda não afetam glibc x86-64, mas flags de endurecimento de distribuições como `GLIBC_TUNABLES=glibc.malloc.check=3` abortarão em metadados inconsistentes e podem quebrar PoCs ingênuas.
|
||||||
|
* **Preenchendo tcache ao liberar** (proposto em 2024 para glibc 2.41) reduziria ainda mais o uso não ordenado; monitore lançamentos futuros ao desenvolver exploits genéricos.
|
||||||
|
|
||||||
|
---
|
||||||
## Outras Referências & Exemplos
|
## Outras Referências & Exemplos
|
||||||
|
|
||||||
- [**https://heap-exploitation.dhavalkapil.com/attacks/first_fit**](https://heap-exploitation.dhavalkapil.com/attacks/first_fit)
|
- [**https://heap-exploitation.dhavalkapil.com/attacks/first_fit**](https://heap-exploitation.dhavalkapil.com/attacks/first_fit)
|
||||||
- [**https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/**](https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/)
|
- [**https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/**](https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/)
|
||||||
- ARM64. Use after free: Gere um objeto de usuário, libere-o, gere um objeto que obtenha o pedaço liberado e permita escrever nele, **sobrescrevendo a posição de user->password** do anterior. Reutilize o usuário para **contornar a verificação de senha**
|
- ARM64. Use after free: Gere um objeto de usuário, libere-o, gere um objeto que obtenha o chunk liberado e permita escrever nele, **sobrescrevendo a posição de user->password** do anterior. Reutilize o usuário para **burlar a verificação de senha**
|
||||||
- [**https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example)
|
- [**https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example)
|
||||||
- O programa permite criar notas. Uma nota terá as informações da nota em um malloc(8) (com um ponteiro para uma função que pode ser chamada) e um ponteiro para outro malloc(\<size>) com o conteúdo da nota.
|
- O programa permite criar notas. Uma nota terá as informações da nota em um malloc(8) (com um ponteiro para uma função que pode ser chamada) e um ponteiro para outro malloc(<size>) com o conteúdo da nota.
|
||||||
- O ataque seria criar 2 notas (note0 e note1) com conteúdos malloc maiores do que o tamanho das informações da nota e, em seguida, liberá-las para que entrem no fast bin (ou tcache).
|
- O ataque seria criar 2 notas (note0 e note1) com conteúdos malloc maiores do que o tamanho das informações da nota e, em seguida, liberá-las para que entrem no fast bin (ou tcache).
|
||||||
- Em seguida, crie outra nota (note2) com tamanho de conteúdo 8. O conteúdo vai estar na note1, pois o pedaço será reutilizado, onde poderíamos modificar o ponteiro da função para apontar para a função win e então Use-After-Free a note1 para chamar o novo ponteiro da função.
|
- Em seguida, crie outra nota (note2) com tamanho de conteúdo 8. O conteúdo vai estar na note1, pois o chunk será reutilizado, onde poderíamos modificar o ponteiro de função para apontar para a função win e então Use-After-Free a note1 para chamar o novo ponteiro de função.
|
||||||
- [**https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html**](https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html)
|
- [**https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html**](https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html)
|
||||||
- É possível alocar alguma memória, escrever o valor desejado, liberá-la, realocá-la e, como os dados anteriores ainda estão lá, serão tratados de acordo com a nova estrutura esperada no pedaço, tornando possível definir o valor ou obter a flag.
|
- É possível alocar alguma memória, escrever o valor desejado, liberá-la, realocá-la e, como os dados anteriores ainda estão lá, serão tratados de acordo com a nova estrutura esperada no chunk, tornando possível definir o valor para obter a flag.
|
||||||
- [**https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html**](https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html)
|
- [**https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html**](https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html)
|
||||||
- Neste caso, é necessário escrever 4 dentro de um pedaço específico que é o primeiro a ser alocado (mesmo após forçar a liberação de todos eles). Em cada novo pedaço alocado, seu número no índice do array é armazenado. Então, aloque 4 pedaços (+ o inicialmente alocado), o último terá 4 dentro dele, libere-os e force a realocação do primeiro, que usará o último pedaço liberado, que é o que contém 4 dentro dele.
|
- Neste caso, é necessário escrever 4 dentro de um chunk específico que é o primeiro a ser alocado (mesmo após forçar a liberação de todos eles). Em cada novo chunk alocado, seu número no índice do array é armazenado. Então, aloque 4 chunks (+ o inicialmente alocado), o último terá 4 dentro dele, libere-os e force a realocação do primeiro, que usará o último chunk liberado, que é o que tem 4 dentro dele.
|
||||||
|
- 2024 HITCON Quals Setjmp write-up (Quarkslab) – ataque prático de sobreposição first-fit / unsorted-split: <https://ctftime.org/writeup/39355>
|
||||||
|
- Angstrom CTF 2024 *heapify* write-up – abusando da divisão de bin não ordenado para vazar libc e ganhar sobreposição: <https://hackmd.io/@aneii11/H1S2snV40>
|
||||||
|
|
||||||
{{#include ../../../banners/hacktricks-training.md}}
|
{{#include ../../../banners/hacktricks-training.md}}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user