Translated ['src/binary-exploitation/common-binary-protections-and-bypas

This commit is contained in:
Translator 2025-07-17 00:13:13 +00:00
parent 946c093dca
commit ddcc50e985

View File

@ -4,30 +4,96 @@
## Relro
**RELRO** signifie **Relocation Read-Only**, et c'est une fonctionnalité de sécurité utilisée dans les binaires pour atténuer les risques associés aux écrasements de **GOT (Global Offset Table)**. Il existe deux types de protections **RELRO** : (1) **Partial RELRO** et (2) **Full RELRO**. Tous deux réorganisent la **GOT** et la **BSS** des fichiers ELF, mais avec des résultats et des implications différents. Plus précisément, ils placent la section **GOT** _avant_ la **BSS**. C'est-à-dire que **GOT** se trouve à des adresses inférieures à celles de **BSS**, rendant ainsi impossible l'écrasement des entrées **GOT** en débordant des variables dans la **BSS** (rappelez-vous que l'écriture en mémoire se fait des adresses inférieures vers les adresses supérieures).
**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`).
Décomposons le concept en ses deux types distincts pour plus de clarté.
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é `RX` 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.
### **Partial RELRO**
Il existe **deux niveaux** de protection que le linker peut émettre :
**Partial RELRO** adopte une approche plus simple pour améliorer la sécurité sans impacter significativement les performances du binaire. Partial RELRO rend **la .got en lecture seule (la partie non-PLT de la section GOT)**. Gardez à l'esprit que le reste de la section (comme la .got.plt) est toujours modifiable et, par conséquent, sujet à des attaques. Cela **ne prévient pas l'abus de la GOT** à partir de vulnérabilités d'**écriture arbitraire**.
### Partial RELRO
Note : Par défaut, GCC compile les binaires avec 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**
### Full RELRO
**Full RELRO** renforce la protection en **rendant l'ensemble de la GOT (à la fois .got et .got.plt) et la section .fini_array** complètement **en lecture seule.** Une fois que le binaire démarre, toutes les adresses de fonction sont résolues et chargées dans la GOT, puis, la GOT est marquée comme en lecture seule, empêchant effectivement toute modification pendant l'exécution.
* 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**.
Cependant, le compromis avec Full RELRO se situe en termes de performances et de temps de démarrage. Parce qu'il doit résoudre tous les symboles dynamiques au démarrage avant de marquer la GOT comme en lecture seule, **les binaires avec Full RELRO activé peuvent connaître des temps de chargement plus longs**. Ce surcoût supplémentaire au démarrage est la raison pour laquelle Full RELRO n'est pas activé par défaut dans tous les binaires.
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**.
Il est possible de voir si Full RELRO est **activé** dans un binaire avec :
---
## Comment vérifier le statut RELRO d'un binaire
```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` (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
```
## Bypass
Si Full RELRO est activé, la seule façon de le contourner est de trouver un autre moyen qui ne nécessite pas d'écrire dans la table GOT pour obtenir une exécution arbitraire.
```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
```
---
Notez que **la GOT de LIBC est généralement en Partial RELRO**, donc elle peut être modifiée avec une écriture arbitraire. Plus d'informations dans [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.**
## 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.<br>2. **ret2dlresolve** créer un faux `Elf64_Rela` & `Elf64_Sym` dans un segment écrivable et appeler `_dl_runtime_resolve`.<br>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).<br>2. Abuser des primitives de *lecture relative* pour divulguer libc et effectuer **SROP/ROP dans libc**.<br>3. Injecter un objet partagé malveillant via **DT_RPATH**/`LD_PRELOAD` (si l'environnement est contrôlé par l'attaquant) ou **`ld_audit`**.<br>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}}