mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
100 lines
11 KiB
Markdown
100 lines
11 KiB
Markdown
# 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`, який перенаправляється `R–X` відразу після того, як динамічний завантажувач закінчує застосування перенесень. Внаслідок цього типові переповнення буфера в **.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}}
|