# 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** і змінити виконання.
2. **ret2dlresolve** – створити підроблені `Elf64_Rela` та `Elf64_Sym` у записуваному сегменті та викликати `_dl_runtime_resolve`.
3. Перезаписати вказівники функцій у списку **.fini_array** / **atexit()**. | | Повний | GOT є тільки для читання | 1. Шукати **інші записувані вказівники коду** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, зворотні виклики в кастомних секціях `.data`, JIT-сторінки).
2. Зловживати *відносними читаннями* для витоку libc та виконання **SROP/ROP у libc**.
3. Впровадити зловмисний спільний об'єкт через **DT_RPATH**/`LD_PRELOAD` (якщо середовище контролюється атакуючим) або **`ld_audit`**.
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}}