100 lines
7.3 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** oznacza **Relocation Read-Only** i jest to środek zaradczy wdrożony przez linker (`ld`), który zmienia podzbiór segmentów danych ELF na **tylko do odczytu po zastosowaniu wszystkich relokacji**. Celem jest uniemożliwienie atakującemu nadpisania wpisów w **GOT (Global Offset Table)** lub innych tabelach związanych z relokacją, które są dereferencjonowane podczas wykonywania programu (np. `__fini_array`).
Nowoczesne linkery implementują RELRO poprzez **przestawienie** **GOT** (i kilku innych sekcji) tak, aby znajdowały się **przed** **.bss** i co najważniejsze poprzez utworzenie dedykowanego segmentu `PT_GNU_RELRO`, który jest mapowany jako `RX` zaraz po tym, jak dynamiczny loader zakończy stosowanie relokacji. W konsekwencji typowe przepełnienia bufora w **.bss** nie mogą już dotrzeć do GOT, a prymitywy do nadpisywania nie mogą być używane do nadpisywania wskaźników funkcji, które znajdują się w chronionej stronie RELRO.
Istnieją **dwa poziomy** ochrony, które linker może wygenerować:
### Partial RELRO
* Wytwarzany z flagą `-Wl,-z,relro` (lub po prostu `-z relro`, gdy wywołuje się `ld` bezpośrednio).
* Tylko **nie-PLT** część **GOT** (część używana do relokacji danych) jest umieszczana w segmencie tylko do odczytu. Sekcje, które muszą być modyfikowane w czasie wykonywania co najważniejsze **.got.plt**, która wspiera **lazy binding** pozostają zapisywalne.
* Z tego powodu prymityw **arbitrary write** może nadal przekierować przepływ wykonania, nadpisując wpis PLT (lub wykonując **ret2dlresolve**).
* Wpływ na wydajność jest znikomy i dlatego **prawie każda dystrybucja od lat dostarcza pakiety z przynajmniej Partial RELRO (jest to domyślne ustawienie GCC/Binutils od 2016 roku)**.
### Full RELRO
* Wytwarzany z **obu** flag `-Wl,-z,relro,-z,now` (znany również jako `-z relro -z now`). `-z now` zmusza dynamiczny loader do rozwiązania **wszystkich** symboli z góry (eager binding), aby **.got.plt** nigdy nie musiał być ponownie zapisywany i mógł być bezpiecznie mapowany jako tylko do odczytu.
* Cały **GOT**, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** oraz kilka dodatkowych wewnętrznych tabel glibc trafia do segmentu `PT_GNU_RELRO` tylko do odczytu.
* Dodaje mierzalny narzut przy uruchamianiu (wszystkie dynamiczne relokacje są przetwarzane przy uruchomieniu), ale **nie ma narzutu w czasie wykonywania**.
Od 2023 roku kilka głównych dystrybucji przeszło na kompilowanie **system tool-chain** (i większości pakietów) z **Full RELRO domyślnie** np. **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** i **Fedora 35+**. Jako pentester powinieneś zatem spodziewać się napotkania binarnych plików, w których **każdy wpis GOT jest tylko do odczytu**.
---
## Jak sprawdzić status RELRO binarnego pliku
```bash
$ checksec --file ./vuln
[*] '/tmp/vuln'
Arch: amd64-64-little
RELRO: Full
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
```
`checksec` (część [pwntools](https://github.com/pwncollege/pwntools) i wielu dystrybucji) analizuje nagłówki `ELF` i wyświetla poziom ochrony. Jeśli nie możesz użyć `checksec`, polegaj na `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
```
Jeśli binarny program jest uruchomiony (np. pomocnik z ustawionym uid root), nadal możesz sprawdzić plik wykonywalny **via `/proc/$PID/exe`**:
```bash
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
```
---
## Włączanie RELRO podczas kompilacji własnego kodu
```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` działa zarówno dla **GCC/clang** (przekazane po `-Wl,`), jak i bezpośrednio dla **ld**. Używając **CMake 3.18+** możesz zażądać pełnego RELRO za pomocą wbudowanego preset:
```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")
```
---
## Techniki omijania
| Poziom RELRO | Typowy prymityw | Możliwe techniki eksploatacji |
|--------------|------------------|-------------------------------|
| Brak / Częściowy | Dowolny zapis | 1. Nadpisanie wpisu **.got.plt** i zmiana wykonania.<br>2. **ret2dlresolve** stworzenie fałszywego `Elf64_Rela` i `Elf64_Sym` w zapisywalnym segmencie i wywołanie `_dl_runtime_resolve`.<br>3. Nadpisanie wskaźników funkcji w **.fini_array** / liście **atexit()**. |
| Pełny | GOT jest tylko do odczytu | 1. Szukaj **innych zapisywalnych wskaźników kodu** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, wywołania zwrotne w niestandardowych sekcjach `.data`, strony JIT).<br>2. Wykorzystaj prymitywy *relatywnego odczytu* do wycieku libc i wykonania **SROP/ROP w libc**.<br>3. Wstrzyknij złośliwy obiekt współdzielony za pomocą **DT_RPATH**/`LD_PRELOAD` (jeśli środowisko jest kontrolowane przez atakującego) lub **`ld_audit`**.<br>4. Wykorzystaj **format-string** lub częściowe nadpisanie wskaźnika, aby zmienić przepływ kontroli bez dotykania GOT. |
> 💡 Nawet przy Pełnym RELRO **GOT załadowanych bibliotek współdzielonych (np. sama libc)** jest **tylko Częściowy RELRO**, ponieważ te obiekty są już mapowane, gdy loader stosuje relokacje. Jeśli uzyskasz prymityw **dowolnego zapisu**, który może celować w strony innego obiektu współdzielonego, nadal możesz zmienić wykonanie, nadpisując wpisy GOT libc lub stos `__rtld_global`, technikę regularnie wykorzystywaną w nowoczesnych wyzwaniach CTF.
### Przykład omijania w rzeczywistości (2024 CTF *pwn.college “enlightened”*)
Wyzwanie dostarczono z Pełnym RELRO. Eksploatacja wykorzystała **off-by-one** do uszkodzenia rozmiaru fragmentu sterty, wyciekła libc za pomocą `tcache poisoning`, a na koniec nadpisała `__free_hook` (poza segmentem RELRO) za pomocą jednego gadżetu, aby uzyskać wykonanie kodu. Nie było wymagane żadne zapisanie do GOT.
---
## Ostatnie badania i luki (2022-2025)
* **glibc 2.40 deprecjonuje `__malloc_hook` / `__free_hook` (2025)** Większość nowoczesnych eksploatacji sterty, które wykorzystywały te symbole, musi teraz przejść do alternatywnych wektorów, takich jak **`rtld_global._dl_load_jump`** lub tabele wyjątków C++. Ponieważ haki znajdują się **poza** RELRO, ich usunięcie zwiększa trudność omijania Pełnego RELRO.
* **Poprawka “max-page-size” w Binutils 2.41 (2024)** Błąd pozwalał ostatnim kilku bajtom segmentu RELRO dzielić stronę z zapisywalnymi danymi w niektórych kompilacjach ARM64, pozostawiając mały **RELRO gap**, który mógł być zapisany po `mprotect`. Teraz upstream wyrównuje `PT_GNU_RELRO` do granic stron, eliminując ten przypadek brzegowy.
---
## Odniesienia
* Dokumentacja Binutils *`-z relro`, `-z now` i `PT_GNU_RELRO`*
* *“RELRO Pełny, Częściowy i Techniki Omijania”* post na blogu @ wolfslittlered 2023
{{#include ../../banners/hacktricks-training.md}}