mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/common-binary-protections-and-bypas
This commit is contained in:
parent
679d98a872
commit
7e51ee5e0b
@ -4,30 +4,96 @@
|
||||
|
||||
## Relro
|
||||
|
||||
**RELRO** significa **Relocation Read-Only**, e é um recurso de segurança usado em binários para mitigar os riscos associados a sobrescritas da **GOT (Global Offset Table)**. Existem dois tipos de proteções **RELRO**: (1) **Partial RELRO** e (2) **Full RELRO**. Ambos reordenam a **GOT** e **BSS** de arquivos ELF, mas com resultados e implicações diferentes. Especificamente, eles colocam a seção **GOT** _antes_ da **BSS**. Ou seja, **GOT** está em endereços mais baixos do que **BSS**, tornando impossível sobrescrever entradas da **GOT** ao transbordar variáveis na **BSS** (lembre-se de que a escrita na memória ocorre de endereços mais baixos para mais altos).
|
||||
**RELRO** significa **Relocation Read-Only** e é uma mitigação implementada pelo linker (`ld`) que torna um subconjunto dos segmentos de dados do ELF **somente leitura após todas as realocações terem sido aplicadas**. O objetivo é impedir que um atacante sobrescreva entradas na **GOT (Tabela de Deslocamento Global)** ou outras tabelas relacionadas a realocações que são desreferenciadas durante a execução do programa (por exemplo, `__fini_array`).
|
||||
|
||||
Vamos dividir o conceito em seus dois tipos distintos para clareza.
|
||||
Linkers modernos implementam RELRO **reordenando** a **GOT** (e algumas outras seções) para que elas fiquem **antes** da **.bss** e – mais importante – criando um segmento dedicado `PT_GNU_RELRO` que é remapeado `R–X` logo após o carregador dinâmico terminar de aplicar as realocações. Consequentemente, estouros de buffer típicos na **.bss** não podem mais alcançar a GOT e primitivas de escrita arbitrária não podem ser usadas para sobrescrever ponteiros de função que estão dentro de uma página protegida por RELRO.
|
||||
|
||||
### **Partial RELRO**
|
||||
Existem **dois níveis** de proteção que o linker pode emitir:
|
||||
|
||||
**Partial RELRO** adota uma abordagem mais simples para aumentar a segurança sem impactar significativamente o desempenho do binário. Partial RELRO torna **a .got somente leitura (a parte não-PLT da seção GOT)**. Tenha em mente que o restante da seção (como a .got.plt) ainda é gravável e, portanto, sujeito a ataques. Isso **não impede que a GOT** seja abusada **por vulnerabilidades de escrita arbitrária**.
|
||||
### Partial RELRO
|
||||
|
||||
Nota: Por padrão, o GCC compila binários com Partial RELRO.
|
||||
* Produzido com a flag `-Wl,-z,relro` (ou apenas `-z relro` ao invocar `ld` diretamente).
|
||||
* Apenas a parte **não-PLT** da **GOT** (a parte usada para realocações de dados) é colocada no segmento somente leitura. Seções que precisam ser modificadas em tempo de execução – mais importante **.got.plt** que suporta **lazy binding** – permanecem graváveis.
|
||||
* Por causa disso, uma primitiva de **escrita arbitrária** ainda pode redirecionar o fluxo de execução sobrescrevendo uma entrada PLT (ou realizando **ret2dlresolve**).
|
||||
* O impacto no desempenho é negligenciável e, portanto, **quase todas as distribuições têm enviado pacotes com pelo menos Partial RELRO há anos (é o padrão do GCC/Binutils desde 2016)**.
|
||||
|
||||
### **Full RELRO**
|
||||
### Full RELRO
|
||||
|
||||
**Full RELRO** aumenta a proteção ao **tornar toda a GOT (tanto .got quanto .got.plt) e a seção .fini_array** completamente **somente leitura.** Uma vez que o binário é iniciado, todos os endereços de função são resolvidos e carregados na GOT, então, a GOT é marcada como somente leitura, efetivamente prevenindo quaisquer modificações durante a execução.
|
||||
* Produzido com **ambas** as flags `-Wl,-z,relro,-z,now` (também conhecido como `-z relro -z now`). `-z now` força o carregador dinâmico a resolver **todos** os símbolos antecipadamente (binding ansioso) para que **.got.plt** nunca precise ser escrita novamente e possa ser mapeada com segurança como somente leitura.
|
||||
* A **GOT** inteira, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** e algumas tabelas internas adicionais da glibc acabam dentro de um segmento `PT_GNU_RELRO` somente leitura.
|
||||
* Adiciona uma sobrecarga de inicialização mensurável (todas as realocações dinâmicas são processadas na inicialização), mas **sem sobrecarga em tempo de execução**.
|
||||
|
||||
No entanto, a desvantagem do Full RELRO está em termos de desempenho e tempo de inicialização. Como precisa resolver todos os símbolos dinâmicos na inicialização antes de marcar a GOT como somente leitura, **binários com Full RELRO habilitado podem experimentar tempos de carregamento mais longos**. Essa sobrecarga adicional na inicialização é a razão pela qual o Full RELRO não é habilitado por padrão em todos os binários.
|
||||
Desde 2023, várias distribuições populares mudaram para compilar a **ferramenta de sistema** (e a maioria dos pacotes) com **Full RELRO por padrão** – por exemplo, **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** e **Fedora 35+**. Como um pentester, você deve, portanto, esperar encontrar binários onde **cada entrada da GOT é somente leitura**.
|
||||
|
||||
É possível verificar se o Full RELRO está **habilitado** em um binário com:
|
||||
---
|
||||
|
||||
## Como verificar o status RELRO de um binário
|
||||
```bash
|
||||
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
|
||||
$ checksec --file ./vuln
|
||||
[*] '/tmp/vuln'
|
||||
Arch: amd64-64-little
|
||||
RELRO: Full
|
||||
Stack: Canary found
|
||||
NX: NX enabled
|
||||
PIE: No PIE (0x400000)
|
||||
```
|
||||
`checksec` (parte do [pwntools](https://github.com/pwncollege/pwntools) e muitas distribuições) analisa os cabeçalhos `ELF` e imprime o nível de proteção. Se você não puder usar `checksec`, confie em `readelf`:
|
||||
```bash
|
||||
# Partial RELRO → PT_GNU_RELRO is present but BIND_NOW is *absent*
|
||||
$ readelf -l ./vuln | grep -E "GNU_RELRO|BIND_NOW"
|
||||
GNU_RELRO 0x0000000000600e20 0x0000000000600e20
|
||||
```
|
||||
## Bypass
|
||||
|
||||
Se o Full RELRO estiver habilitado, a única maneira de contorná-lo é encontrar outra forma que não precise escrever na tabela GOT para obter execução arbitrária.
|
||||
```bash
|
||||
# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag
|
||||
$ readelf -d ./vuln | grep BIND_NOW
|
||||
0x0000000000000010 (FLAGS) FLAGS: BIND_NOW
|
||||
```
|
||||
Se o binário estiver em execução (por exemplo, um helper set-uid root), você ainda pode inspecionar o executável **via `/proc/$PID/exe`**:
|
||||
```bash
|
||||
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
|
||||
```
|
||||
---
|
||||
|
||||
Note que **o GOT da LIBC geralmente é Partial RELRO**, então pode ser modificado com uma escrita arbitrária. Mais informações em [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.**
|
||||
## Habilitando RELRO ao compilar seu próprio código
|
||||
```bash
|
||||
# GCC example – create a PIE with Full RELRO and other common hardenings
|
||||
$ gcc -fPIE -pie -z relro -z now -Wl,--as-needed -D_FORTIFY_SOURCE=2 main.c -o secure
|
||||
```
|
||||
`-z relro -z now` funciona para ambos **GCC/clang** (passado após `-Wl,`) e **ld** diretamente. Ao usar **CMake 3.18+** você pode solicitar Full RELRO com o preset embutido:
|
||||
```cmake
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # LTO
|
||||
set(CMAKE_ENABLE_EXPORTS OFF)
|
||||
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro,-z,now")
|
||||
```
|
||||
---
|
||||
|
||||
## Técnicas de Bypass
|
||||
|
||||
| Nível RELRO | Primitiva típica | Técnicas de exploração possíveis |
|
||||
|-------------|-------------------|----------------------------------|
|
||||
| Nenhum / Parcial | Escrita arbitrária | 1. Sobrescrever a entrada **.got.plt** e mudar a execução.<br>2. **ret2dlresolve** – criar `Elf64_Rela` & `Elf64_Sym` falsos em um segmento gravável e chamar `_dl_runtime_resolve`.<br>3. Sobrescrever ponteiros de função na lista **.fini_array** / **atexit()**. |
|
||||
| Completo | GOT é somente leitura | 1. Procurar **outros ponteiros de código graváveis** (vtables C++, `__malloc_hook` < glibc 2.34, `__free_hook`, callbacks em seções `.data` personalizadas, páginas JIT).<br>2. Abusar de primitivas de *leitura relativa* para vazar libc e realizar **SROP/ROP em libc**.<br>3. Injetar um objeto compartilhado malicioso via **DT_RPATH**/`LD_PRELOAD` (se o ambiente for controlado pelo atacante) ou **`ld_audit`**.<br>4. Explorar **format-string** ou sobrescrita parcial de ponteiro para desviar o fluxo de controle sem tocar na GOT. |
|
||||
|
||||
> 💡 Mesmo com Full RELRO, a **GOT de bibliotecas compartilhadas carregadas (por exemplo, a própria libc)** é **apenas RELRO Parcial** porque esses objetos já estão mapeados quando o carregador aplica relocations. Se você ganhar uma primitiva de **escrita arbitrária** que pode direcionar as páginas de outro objeto compartilhado, ainda pode mudar a execução sobrescrevendo as entradas da GOT da libc ou a pilha `__rtld_global`, uma técnica frequentemente explorada em desafios modernos de CTF.
|
||||
|
||||
### Exemplo de bypass no mundo real (2024 CTF – *pwn.college “enlightened”*)
|
||||
|
||||
O desafio foi enviado com Full RELRO. O exploit usou um **off-by-one** para corromper o tamanho de um chunk de heap, vazou libc com `tcache poisoning` e finalmente sobrescreveu `__free_hook` (fora do segmento RELRO) com um one-gadget para obter execução de código. Nenhuma escrita na GOT foi necessária.
|
||||
|
||||
---
|
||||
|
||||
## Pesquisas recentes & vulnerabilidades (2022-2025)
|
||||
|
||||
* **glibc 2.40 descontinuou `__malloc_hook` / `__free_hook` (2025)** – A maioria dos exploits modernos de heap que abusaram desses símbolos agora deve mudar para vetores alternativos, como **`rtld_global._dl_load_jump`** ou tabelas de exceção C++. Como os hooks vivem **fora** do RELRO, sua remoção aumenta a dificuldade de bypasses de Full-RELRO.
|
||||
* **Correção “max-page-size” do Binutils 2.41 (2024)** – Um bug permitiu que os últimos bytes do segmento RELRO compartilhassem uma página com dados graváveis em algumas compilações ARM64, deixando um pequeno **gap RELRO** que poderia ser escrito após `mprotect`. O upstream agora alinha `PT_GNU_RELRO` aos limites de página, eliminando esse caso extremo.
|
||||
|
||||
---
|
||||
|
||||
## Referências
|
||||
|
||||
* Documentação do Binutils – *`-z relro`, `-z now` e `PT_GNU_RELRO`*
|
||||
* *“RELRO – Full, Partial and Bypass Techniques”* – post no blog @ wolfslittlered 2023
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user