100 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Relro
{{#include ../../banners/hacktricks-training.md}}
## Relro
**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`).
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 `RX` 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.
Existem **dois níveis** de proteção que o linker pode emitir:
### 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
* 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**.
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**.
---
## Como verificar o status RELRO de um binário
```bash
$ 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
```
```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
```
---
## 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}}