# Relro
{{#include ../../banners/hacktricks-training.md}}
## Relro
**RELRO** signifie **Relocation Read-Only** et c'est une atténuation mise en œuvre par le linker (`ld`) qui rend un sous-ensemble des segments de données ELF **lecture seule après que toutes les relocations ont été appliquées**. L'objectif est d'empêcher un attaquant de remplacer des entrées dans la **GOT (Global Offset Table)** ou d'autres tables liées aux relocations qui sont déréférencées pendant l'exécution du programme (par exemple, `__fini_array`).
Les linkers modernes mettent en œuvre RELRO en **réorganisant** la **GOT** (et quelques autres sections) afin qu'elles se trouvent **avant** le **.bss** et – surtout – en créant un segment `PT_GNU_RELRO` dédié qui est remappé `R–X` juste après que le chargeur dynamique ait terminé d'appliquer les relocations. Par conséquent, les débordements de tampon typiques dans le **.bss** ne peuvent plus atteindre la GOT et les primitives d'écriture arbitraire ne peuvent pas être utilisées pour écraser des pointeurs de fonction qui se trouvent à l'intérieur d'une page protégée par RELRO.
Il existe **deux niveaux** de protection que le linker peut émettre :
### Partial RELRO
* Produit avec le drapeau `-Wl,-z,relro` (ou juste `-z relro` lors de l'invocation directe de `ld`).
* Seule la partie **non-PLT** de la **GOT** (la partie utilisée pour les relocations de données) est placée dans le segment en lecture seule. Les sections qui doivent être modifiées à l'exécution – surtout **.got.plt** qui prend en charge **lazy binding** – restent modifiables.
* À cause de cela, une primitive **d'écriture arbitraire** peut toujours rediriger le flux d'exécution en écrasant une entrée PLT (ou en effectuant **ret2dlresolve**).
* L'impact sur les performances est négligeable et donc **presque toutes les distributions expédient des paquets avec au moins Partial RELRO depuis des années (c'est le défaut de GCC/Binutils depuis 2016)**.
### Full RELRO
* Produit avec **les deux** drapeaux `-Wl,-z,relro,-z,now` (a.k.a. `-z relro -z now`). `-z now` force le chargeur dynamique à résoudre **tous** les symboles à l'avance (liaison impatiente) afin que **.got.plt** n'ait jamais besoin d'être écrit à nouveau et puisse être mappé en toute sécurité en lecture seule.
* L'ensemble de la **GOT**, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** et quelques tables internes supplémentaires de glibc se retrouvent à l'intérieur d'un segment `PT_GNU_RELRO` en lecture seule.
* Ajoute un surcoût de démarrage mesurable (toutes les relocations dynamiques sont traitées au lancement) mais **aucun surcoût à l'exécution**.
Depuis 2023, plusieurs distributions grand public ont commencé à compiler la **tool-chain système** (et la plupart des paquets) avec **Full RELRO par défaut** – par exemple **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** et **Fedora 35+**. En tant que pentester, vous devez donc vous attendre à rencontrer des binaires où **chaque entrée GOT est en lecture seule**.
---
## Comment vérifier le statut RELRO d'un binaire
```bash
$ checksec --file ./vuln
[*] '/tmp/vuln'
Arch: amd64-64-little
RELRO: Full
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
```
`checksec` (fait partie de [pwntools](https://github.com/pwncollege/pwntools) et de nombreuses distributions) analyse les en-têtes `ELF` et affiche le niveau de protection. Si vous ne pouvez pas utiliser `checksec`, comptez sur `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
```
Si le binaire est en cours d'exécution (par exemple, un helper set-uid root), vous pouvez toujours inspecter l'exécutable **via `/proc/$PID/exe`** :
```bash
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
```
---
## Activer RELRO lors de la compilation de votre propre code
```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` fonctionne pour **GCC/clang** (passé après `-Wl,`) et **ld** directement. Lorsque vous utilisez **CMake 3.18+**, vous pouvez demander Full RELRO avec le préréglage intégré :
```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")
```
---
## Techniques de contournement
| Niveau RELRO | Primitive typique | Techniques d'exploitation possibles |
|--------------|-------------------|------------------------------------|
| Aucun / Partiel | Écriture arbitraire | 1. Écraser l'entrée **.got.plt** et pivoter l'exécution.
2. **ret2dlresolve** – créer un faux `Elf64_Rela` & `Elf64_Sym` dans un segment écrivable et appeler `_dl_runtime_resolve`.
3. Écraser les pointeurs de fonction dans **.fini_array** / liste **atexit()**. |
| Complet | GOT en lecture seule | 1. Rechercher **d'autres pointeurs de code écrivable** (vtables C++, `__malloc_hook` < glibc 2.34, `__free_hook`, rappels dans des sections `.data` personnalisées, pages JIT).
2. Abuser des primitives de *lecture relative* pour divulguer libc et effectuer **SROP/ROP dans libc**.
3. Injecter un objet partagé malveillant via **DT_RPATH**/`LD_PRELOAD` (si l'environnement est contrôlé par l'attaquant) ou **`ld_audit`**.
4. Exploiter **format-string** ou écriture partielle de pointeur pour détourner le flux de contrôle sans toucher le GOT. |
> 💡 Même avec un RELRO Complet, le **GOT des bibliothèques partagées chargées (par exemple, libc elle-même)** est **seulement Partiel RELRO** car ces objets sont déjà mappés lorsque le chargeur applique les relocations. Si vous obtenez une primitive **d'écriture arbitraire** qui peut cibler les pages d'un autre objet partagé, vous pouvez toujours pivoter l'exécution en écrasant les entrées GOT de libc ou la pile `__rtld_global`, une technique régulièrement exploitée dans les défis CTF modernes.
### Exemple de contournement dans le monde réel (CTF 2024 – *pwn.college “enlightened”*)
Le défi était livré avec un RELRO Complet. L'exploitation a utilisé un **off-by-one** pour corrompre la taille d'un morceau de tas, a divulgué libc avec `tcache poisoning`, et enfin a écrasé `__free_hook` (en dehors du segment RELRO) avec un one-gadget pour obtenir l'exécution de code. Aucune écriture dans le GOT n'était requise.
---
## Recherches récentes & vulnérabilités (2022-2025)
* **glibc 2.40 déprécie `__malloc_hook` / `__free_hook` (2025)** – La plupart des exploits de tas modernes qui abusaient de ces symboles doivent maintenant pivoter vers des vecteurs alternatifs tels que **`rtld_global._dl_load_jump`** ou les tables d'exceptions C++. Étant donné que les hooks vivent **en dehors** du RELRO, leur suppression augmente la difficulté des contournements de RELRO Complet.
* **Correction “max-page-size” de Binutils 2.41 (2024)** – Un bug permettait aux derniers octets du segment RELRO de partager une page avec des données écrivables sur certaines constructions ARM64, laissant un petit **écart RELRO** qui pouvait être écrit après `mprotect`. En amont, `PT_GNU_RELRO` est maintenant aligné sur les limites de page, éliminant ce cas particulier.
---
## Références
* Documentation de Binutils – *`-z relro`, `-z now` et `PT_GNU_RELRO`*
* *“RELRO – Complet, Partiel et Techniques de Contournement”* – article de blog @ wolfslittlered 2023
{{#include ../../banners/hacktricks-training.md}}