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 stands for Relocation Read-Only and it is a mitigation implemented by the linker (ld) that turns a subset of the ELF’s data segments тільки для читання після застосування всіх релокацій. Метою є перешкодити атакуючому перезаписувати записи в GOT (Global Offset Table) або інших таблицях, пов’язаних з релокаціями, до яких звертаються під час виконання програми (наприклад, __fini_array).

Сучасні лінкери реалізують RELRO шляхом перепорядкування GOT (та кількох інших секцій), щоб вони розташовувалися перед .bss і — що найважливіше — шляхом створення виділеного сегмента PT_GNU_RELRO, який після того, як динамічний лоадер застосує релокації, відмаплюється як R–X. Внаслідок цього типові переповнення буфера в .bss більше не можуть дістатися до GOT і примітиви довільного запису не можуть бути використані для перезапису вказівників на функції, що містяться на сторінці, захищеній RELRO.

Існує дві рівні захисту, які може згенерувати лінкер:

Partial RELRO

  • Produced with the flag -Wl,-z,relro (or just -z relro when invoking ld directly).
  • Only the non-PLT part of the GOT (the part used for релокацій даних) is put into the read-only segment. Sections that need to be modified at run-time – most importantly .got.plt which supports lazy binding – remain writable.
  • Because of that, an arbitrary write primitive can still redirect execution flow by overwriting a PLT entry (or by performing ret2dlresolve).
  • The performance impact is negligible and therefore almost every distribution has been shipping packages with at least Partial RELRO for years (it is the GCC/Binutils default as of 2016).

Full RELRO

  • Produced with both flags -Wl,-z,relro,-z,now (a.k.a. -z relro -z now). -z now forces the dynamic loader to resolve all symbols up-front (eager binding) so that .got.plt never needs to be written again and can safely be mapped read-only.
  • The entire GOT, .got.plt, .fini_array, .init_array, .preinit_array and a few additional internal glibc tables end up inside a read-only PT_GNU_RELRO segment.
  • Adds measurable start-up overhead (all dynamic relocations are processed at launch) but no run-time overhead.

Since 2023 several mainstream distributions have switched to compiling the system tool-chain (and most packages) with Full RELRO by default – e.g. Debian 12 “bookworm” (dpkg-buildflags 13.0.0) and Fedora 35+. As a pentester you should therefore expect to encounter binaries where every GOT entry is read-only.


Як перевірити статус RELRO у binary

$ 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:

# 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
# 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 helper), ви все ще можете переглянути виконуваний файл через /proc/$PID/exe:

readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO

Увімкнення RELRO під час компіляції власного code

# 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+ ви можете увімкнути Full RELRO за допомогою вбудованого пресету:

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 levelTypical primitivePossible exploitation techniques
Немає / ЧастковийArbitrary write1. Перезаписати .got.plt entry і перенаправити виконання.
2. ret2dlresolve – змайструвати фейкові Elf64_Rela & Elf64_Sym в записуваному сегменті і викликати _dl_runtime_resolve.
3. Перезаписати вказівники функцій у .fini_array / списку atexit().
FullGOT is read-only1. Шукати інші записувані вказівники коду (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, callbacks у кастомних .data секціях, JIT сторінки).
2. Зловживати relative read примітивами щоб leaked libc і виконати SROP/ROP into libc.
3. Інжектити зловмисний shared object через DT_RPATH/LD_PRELOAD (якщо середовище контролюється нападником) або ld_audit.
4. Використати format-string або частковий перезапис вказівника, щоб відхилити контроль виконання без зміни GOT.

💡 Навіть з Full RELRO GOT завантажених shared libraries (наприклад libc) є лише Partial RELRO, бо ці об’єкти вже відображені, коли завантажувач застосовує релокації. Якщо ви отримуєте примітив arbitrary write, який може цілитися в сторінки іншого shared object, ви все одно можете перенаправити виконання, перезаписавши записи GOT libc або стек __rtld_global — техніка, яку регулярно експлуатують у сучасних CTF-змаганнях.

Приклад обходу в реальному світі (2024 CTF – pwn.college “enlightened”)

Завдання було з Full RELRO. Експлойт використав off-by-one для пошкодження розміру heap chunk, leaked libc за допомогою tcache poisoning, і врешті перезаписав __free_hook (поза сегментом RELRO) одним one-gadget для отримання виконання коду. Запис у GOT не знадобився.


Останні дослідження та вразливості (2022-2025)

  • glibc hook removal (2.34 → present) – malloc/free hooks були винесені з основного libc в опційний libc_malloc_debug.so, що усунуло поширений примітив обходу Full‑RELRO; сучасні експлойти мають цілитись в інші записувані вказівники.
  • GNU ld RELRO page‑alignment fix (binutils 2.39+/2.41) – помилка лінкера 30612 спричиняла, що останні байти PT_GNU_RELRO ділили записувану сторінку на системах з 64 KiB сторінками; поточні binutils вирівнюють RELRO до max-page-size, закриваючи цю “RELRO gap”.

References

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