Relro

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

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 та багатьох дистрибутивів) аналізує заголовки 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

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks