100 lines
11 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** означає **Relocation Read-Only** і є заходом, реалізованим компоновником (`ld`), який перетворює підмножину сегментів даних ELF на **тільки для читання після застосування всіх перенесень**. Мета полягає в тому, щоб зупинити зловмисника від перезапису записів у **GOT (Global Offset Table)** або інших таблицях, пов'язаних з перенесеннями, які розіменовуються під час виконання програми (наприклад, `__fini_array`).
Сучасні компоновники реалізують RELRO, **переставляючи** **GOT** (і кілька інших секцій) так, щоб вони знаходилися **перед** **.bss** і що найважливіше створюючи спеціальний сегмент `PT_GNU_RELRO`, який перенаправляється `RX` відразу після того, як динамічний завантажувач закінчує застосування перенесень. Внаслідок цього типові переповнення буфера в **.bss** більше не можуть досягти GOT, і примітиви довільного запису не можуть бути використані для перезапису вказівників функцій, які знаходяться всередині захищеної сторінки RELRO.
Існує **два рівні** захисту, які може випустити компоновник:
### Partial RELRO
* Виробляється з прапором `-Wl,-z,relro` (або просто `-z relro`, коли викликається `ld` безпосередньо).
* Тільки **не-PLT** частина **GOT** (частина, що використовується для перенесень даних) поміщається в сегмент тільки для читання. Секції, які потрібно змінювати під час виконання найважливіше **.got.plt**, що підтримує **lazy binding** залишаються записуваними.
* Через це примітив **довільного запису** все ще може перенаправити потік виконання, перезаписуючи запис PLT (або виконуючи **ret2dlresolve**).
* Вплив на продуктивність незначний, тому **майже кожен дистрибутив протягом багатьох років постачає пакунки з принаймні Partial RELRO (це стандарт GCC/Binutils з 2016 року)**.
### Full RELRO
* Виробляється з **обома** прапорами `-Wl,-z,relro,-z,now` (також відомий як `-z relro -z now`). `-z now` змушує динамічний завантажувач вирішувати **всі** символи заздалегідь (eager binding), щоб **.got.plt** більше не потрібно було записувати і його можна було безпечно відобразити як тільки для читання.
* Весь **GOT**, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** та кілька додаткових внутрішніх таблиць glibc потрапляють у сегмент тільки для читання `PT_GNU_RELRO`.
* Додає вимірюване навантаження при запуску (всі динамічні перенесення обробляються під час запуску), але **немає накладних витрат під час виконання**.
З 2023 року кілька основних дистрибутивів перейшли на компіляцію **системного інструментального набору** (і більшості пакунків) з **Full RELRO за замовчуванням** наприклад, **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** та **Fedora 35+**. Як пентестер, ви повинні очікувати зустріти бінарні файли, де **кожен запис GOT є тільки для читання**.
---
## Як перевірити статус RELRO бінарного файлу
```bash
$ checksec --file ./vuln
[*] '/tmp/vuln'
Arch: amd64-64-little
RELRO: Full
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
```
`checksec` (частина [pwntools](https://github.com/pwncollege/pwntools) та багатьох дистрибутивів) аналізує заголовки `ELF` і виводить рівень захисту. Якщо ви не можете використовувати `checksec`, покладайтеся на `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
```
Якщо двійковий файл працює (наприклад, допоміжна програма з set-uid root), ви все ще можете перевірити виконуваний файл **через `/proc/$PID/exe`**:
```bash
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
```
---
## Увімкнення RELRO під час компіляції власного коду
```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` працює для обох **GCC/clang** (передано після `-Wl,`) і **ld** безпосередньо. Коли ви використовуєте **CMake 3.18+**, ви можете запитати повний RELRO за допомогою вбудованого пресету:
```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")
```
---
## Техніки обходу
| Рівень RELRO | Типовий примітив | Можливі техніки експлуатації |
|--------------|------------------|------------------------------|
| Немає / Частковий | Довільний запис | 1. Перезаписати запис **.got.plt** і змінити виконання.<br>2. **ret2dlresolve** створити підроблені `Elf64_Rela` та `Elf64_Sym` у записуваному сегменті та викликати `_dl_runtime_resolve`.<br>3. Перезаписати вказівники функцій у списку **.fini_array** / **atexit()**. |
| Повний | GOT є тільки для читання | 1. Шукати **інші записувані вказівники коду** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, зворотні виклики в кастомних секціях `.data`, JIT-сторінки).<br>2. Зловживати *відносними читаннями* для витоку libc та виконання **SROP/ROP у libc**.<br>3. Впровадити зловмисний спільний об'єкт через **DT_RPATH**/`LD_PRELOAD` (якщо середовище контролюється атакуючим) або **`ld_audit`**.<br>4. Використати **форматний рядок** або частковий перезапис вказівника для зміни потоку управління без торкання GOT. |
> 💡 Навіть з Повним RELRO **GOT завантажених спільних бібліотек (наприклад, сама libc)** є **тільки Частковим RELRO**, оскільки ці об'єкти вже відображені, коли завантажувач застосовує перенесення. Якщо ви отримали **довільний запис** примітив, який може націлюватися на сторінки іншого спільного об'єкта, ви все ще можете змінити виконання, перезаписавши записи GOT libc або стек `__rtld_global`, техніка, яка регулярно використовується в сучасних CTF-завданнях.
### Приклад обходу в реальному світі (2024 CTF *pwn.college “enlightened”*)
Завдання постачалося з Повним RELRO. Експлуатація використовувала **off-by-one** для корупції розміру частини купи, витекла libc з `tcache poisoning`, і нарешті перезаписала `__free_hook` (поза сегментом RELRO) з одним гаджетом для отримання виконання коду. Запис у GOT не був потрібен.
---
## Останні дослідження та вразливості (2022-2025)
* **glibc 2.40 знецінює `__malloc_hook` / `__free_hook` (2025)** Більшість сучасних експлуатацій купи, які зловживали цими символами, тепер повинні переходити на альтернативні вектори, такі як **`rtld_global._dl_load_jump`** або таблиці виключень C++. Оскільки гачки живуть **поза** RELRO, їх видалення ускладнює обходи Повного RELRO.
* **Виправлення “max-page-size” Binutils 2.41 (2024)** Помилка дозволила останнім кільком байтам сегмента RELRO ділити сторінку з записуваними даними на деяких збірках ARM64, залишаючи маленький **RELRO-проміжок**, який можна було записати після `mprotect`. Тепер upstream вирівнює `PT_GNU_RELRO` до меж сторінок, усуваючи цей крайній випадок.
---
## Посилання
* Документація Binutils *`-z relro`, `-z now` та `PT_GNU_RELRO`*
* *“RELRO Повний, Частковий та Техніки обходу”* допис у блозі @ wolfslittlered 2023
{{#include ../../banners/hacktricks-training.md}}