Relro

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术: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 read-only after all relocations have been applied. The goal is to stop an attacker from overwriting entries in the GOT (Global Offset Table) or other relocation-related tables that are dereferenced during program execution (e.g. __fini_array).

现代链接器通过重新排列 GOT(以及其他一些节),使其位于 .bss 之前,并——最重要的——创建一个专门的 PT_GNU_RELRO 段,该段在动态加载器完成重定位后立即被映射为 R–X。因此,位于 .bss 的典型 buffer overflows 无法再到达 GOT,arbitrary‐write primitives 也不能用于覆盖位于 RELRO 保护页内的函数指针。

There are two levels of protection that the linker can emit:

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 data relocations) 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.


How to Check the RELRO status of a 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

# 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 nowGCC/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 level典型原语可能的利用技术
None / PartialArbitrary write1. 覆盖 .got.plt 条目并 pivot execution。
2. ret2dlresolve – 在可写段中伪造 Elf64_Rela & Elf64_Sym 并调用 _dl_runtime_resolve
3. 覆盖 .fini_array / atexit() 列表中的函数指针。
FullGOT 是只读的1. 寻找 其他可写的代码指针 (C++ vtables, __malloc_hook < glibc 2.34, __free_hook, callbacks in custom .data sections, JIT pages)。
2. 滥用 relative read 原语来 leak libc 并执行 SROP/ROP into libc
3. 通过 DT_RPATH/LD_PRELOAD(如果环境受攻击者控制)或 ld_audit 注入恶意共享对象。
4. 利用 format-string 或 partial pointer overwrite 改变控制流而无需触碰 GOT。

💡 即便在 Full RELRO 下,**已加载共享库的 GOT(例如 libc 本身)**仍然是 only Partial RELRO,因为这些对象在 loader 应用重定位时已经被映射。如果你获得了能写入其他共享对象页面的 arbitrary write 原语,你仍然可以通过覆盖 libc 的 GOT 条目或 __rtld_global 栈来 pivot execution,这是一种在现代 CTF 挑战中经常被利用的技术。

真实世界绕过示例 (2024 CTF – pwn.college “enlightened”)

该题目带有 Full RELRO。利用链使用了一个 off-by-one 破坏了堆 chunk 的大小,通过 tcache poisoning leak 了 libc,最后用 one-gadget 覆盖了位于 RELRO 段之外的 __free_hook 以获得代码执行。无需写入 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) – linker bug 30612 导致 PT_GNU_RELRO 的末尾字节在 64 KiB 页面系统上与一个可写页面共享;当前的 binutils 将 RELRO 对齐到 max-page-size,修补了该 “RELRO gap”。

References

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks