# 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 `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. 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.
2. **ret2dlresolve** – criar `Elf64_Rela` & `Elf64_Sym` falsos em um segmento gravável e chamar `_dl_runtime_resolve`.
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).
2. Abusar de primitivas de *leitura relativa* para vazar libc e realizar **SROP/ROP em libc**.
3. Injetar um objeto compartilhado malicioso via **DT_RPATH**/`LD_PRELOAD` (se o ambiente for controlado pelo atacante) ou **`ld_audit`**.
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}}