Relro

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

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 depois que todas as relocations foram aplicadas. O objetivo é impedir que um atacante sobrescreva entradas na GOT (Global Offset Table) ou outras tabelas relacionadas a relocations que são desreferenciadas durante a execução do programa (ex.: __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 loader dinâmico terminar de aplicar as relocations. Consequentemente, estouros de buffer típicos na .bss não conseguem mais alcançar a GOT e primitivos de escrita arbitrária não podem ser usados para sobrescrever ponteiros de função que residem 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 quando invocando ld diretamente).
  • Apenas a parte non-PLT da GOT (a parte usada para relocations 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, um primitivo de arbitrary write ainda pode redirecionar o fluxo de execução sobrescrevendo uma entrada da PLT (ou realizando ret2dlresolve).
  • O impacto em performance é negligível e, portanto, quase todas as distribuições vêm empacotando pacotes com pelo menos Partial RELRO há anos (é o default do GCC/Binutils desde 2016).

Full RELRO

  • Produzido com ambas as flags -Wl,-z,relro,-z,now (a.k.a. -z relro -z now). -z now força o loader dinâmico a resolver todos os símbolos antecipadamente (eager binding) de modo que .got.plt nunca precise ser escrita novamente e possa ser mapeada seguramente como somente leitura.
  • A GOT inteira, .got.plt, .fini_array, .init_array, .preinit_array e algumas tabelas internas adicionais do glibc terminam dentro de um segmento PT_GNU_RELRO somente leitura.
  • Adiciona overhead mensurável no start-up (todas as relocations dinâmicas são processadas no lançamento) mas sem overhead em tempo de execução.

Desde 2023 várias distribuições mainstream mudaram para compilar a system tool-chain (e a maioria dos pacotes) com Full RELRO por padrão – ex.: Debian 12 “bookworm” (dpkg-buildflags 13.0.0) e Fedora 35+. Como pentester, você deve portanto esperar encontrar binários onde toda entrada da GOT é somente leitura.


How to Check the RELRO status of a binary

$ 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 e muitas distribuições) analisa os cabeçalhos ELF e imprime o nível de proteção. Se você não puder usar checksec, use readelf:

# 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
# 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 set-uid root helper), você ainda pode inspecionar o executável via /proc/$PID/exe:

readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO

Habilitando RELRO ao compilar seu próprio code

# 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 tanto para GCC/clang (passado após -Wl,) quanto para ld diretamente. Ao usar CMake 3.18+ você pode solicitar Full RELRO com o preset embutido:

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

RELRO levelPrimitiva típicaTécnicas de exploração possíveis
Nenhum / ParcialArbitrary write1. Sobrescrever .got.plt entry e pivotar execução.
2. ret2dlresolve – craft fake Elf64_Rela & Elf64_Sym in a writable segment and call _dl_runtime_resolve.
3. Sobrescrever function pointers em .fini_array / atexit() list.
FullGOT é somente leitura1. Procure por outros ponteiros de código graváveis (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, callbacks em seções .data customizadas, páginas JIT).
2. Abuse relative read primitives para leak libc e perform SROP/ROP into libc.
3. Injete um shared object malicioso via DT_RPATH/LD_PRELOAD (se o ambiente for controlado pelo atacante) ou ld_audit.
4. Explore format-string ou overwrite parcial de ponteiro para desviar o control-flow sem tocar no GOT.

💡 Mesmo com Full RELRO a GOT of loaded shared libraries (e.g. libc itself) é only Partial RELRO porque esses objetos já estão mapeados quando o loader aplica as relocations. Se você ganhar uma primitiva de arbitrary write que possa mirar as páginas de outro shared object você ainda pode pivotar a execução sobrescrevendo entradas da GOT do libc ou a stack __rtld_global, uma técnica regularmente explorada em CTFs modernos.

Exemplo de bypass no mundo real (2024 CTF – pwn.college “enlightened”)

O desafio veio com Full RELRO. O exploit usou um off-by-one para corromper o tamanho de um chunk do heap, leaked 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 hook removal (2.34 → present) – os malloc/free hooks foram extraídos do libc principal para o opcional libc_malloc_debug.so, eliminando uma primitiva comum de bypass de Full‑RELRO; exploits modernos devem mirar outros ponteiros graváveis.
  • GNU ld RELRO page‑alignment fix (binutils 2.39+/2.41) – o bug do linker 30612 fazia com que os últimos bytes de PT_GNU_RELRO compartilhassem uma página gravável em sistemas com páginas de 64 KiB; as binutils atuais alinham RELRO ao max-page-size, fechando essa “RELRO gap”.

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks