mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/common-binary-protections-and-bypas
This commit is contained in:
parent
937ec9e3b3
commit
b9757fe3b3
@ -4,30 +4,96 @@
|
||||
|
||||
## Relro
|
||||
|
||||
**RELRO** oznacza **Relocation Read-Only** i jest funkcją zabezpieczeń stosowaną w plikach binarnych w celu złagodzenia ryzyka związanego z nadpisywaniem **GOT (Global Offset Table)**. Istnieją dwa typy ochrony **RELRO**: (1) **Partial RELRO** i (2) **Full RELRO**. Oba z nich reorganizują **GOT** i **BSS** z plików ELF, ale z różnymi wynikami i implikacjami. Konkretnie, umieszczają sekcję **GOT** _przed_ **BSS**. To znaczy, że **GOT** znajduje się pod niższymi adresami niż **BSS**, co uniemożliwia nadpisywanie wpisów **GOT** przez przepełnianie zmiennych w **BSS** (pamiętaj, że zapis do pamięci odbywa się od niższych do wyższych adresów).
|
||||
**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`).
|
||||
|
||||
Rozłóżmy koncepcję na dwa wyraźne typy dla jasności.
|
||||
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 `R–X` 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.
|
||||
|
||||
### **Partial RELRO**
|
||||
Istnieją **dwa poziomy** ochrony, które linker może wygenerować:
|
||||
|
||||
**Partial RELRO** przyjmuje prostsze podejście do zwiększenia bezpieczeństwa bez znaczącego wpływu na wydajność binariów. Partial RELRO sprawia, że **.got jest tylko do odczytu (część nie-PLT sekcji GOT)**. Pamiętaj, że reszta sekcji (jak .got.plt) jest nadal zapisywalna i, w związku z tym, podatna na ataki. To **nie zapobiega nadużywaniu GOT** z **wrażliwości na dowolne zapisy**.
|
||||
### Partial RELRO
|
||||
|
||||
Uwaga: Domyślnie GCC kompiluje pliki binarne z 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**
|
||||
### Full RELRO
|
||||
|
||||
**Full RELRO** zwiększa ochronę, **czyniąc całą sekcję GOT (zarówno .got, jak i .got.plt) oraz .fini_array** całkowicie **tylko do odczytu.** Gdy plik binarny się uruchamia, wszystkie adresy funkcji są rozwiązywane i ładowane w GOT, a następnie GOT jest oznaczany jako tylko do odczytu, skutecznie zapobiegając jakimkolwiek modyfikacjom w czasie wykonywania.
|
||||
* 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**.
|
||||
|
||||
Jednak kompromis związany z Full RELRO dotyczy wydajności i czasu uruchamiania. Ponieważ musi rozwiązać wszystkie dynamiczne symbole podczas uruchamiania przed oznaczeniem GOT jako tylko do odczytu, **pliki binarne z włączonym Full RELRO mogą doświadczać dłuższych czasów ładowania**. Ten dodatkowy narzut uruchamiania to powód, dla którego Full RELRO nie jest domyślnie włączony we wszystkich plikach binarnych.
|
||||
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**.
|
||||
|
||||
Można sprawdzić, czy Full RELRO jest **włączony** w pliku binarnym za pomocą:
|
||||
---
|
||||
|
||||
## Jak sprawdzić status RELRO binarnego pliku
|
||||
```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` (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
|
||||
```
|
||||
## Bypass
|
||||
|
||||
Jeśli Full RELRO jest włączony, jedynym sposobem na obejście go jest znalezienie innej metody, która nie wymaga zapisu w tabeli GOT, aby uzyskać dowolne wykonanie.
|
||||
```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
|
||||
```
|
||||
---
|
||||
|
||||
Zauważ, że **GOT LIBC jest zazwyczaj Partial RELRO**, więc może być modyfikowany za pomocą dowolnego zapisu. Więcej informacji w [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.**
|
||||
## 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}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user