mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	Add content from: Research Update: Enhanced src/binary-exploitation/common-bin...
This commit is contained in:
		
							parent
							
								
									13eb306dcf
								
							
						
					
					
						commit
						3cd04fb6e2
					
				@ -4,35 +4,106 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Relro
 | 
					## Relro
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**RELRO** stands for **Relocation Read-Only**, and it's a security feature used in binaries to mitigate the risks associated with **GOT (Global Offset Table)** overwrites. There are two types of **RELRO** protections: (1) **Partial RELRO** and (2) **Full RELRO**. Both of them reorder the **GOT** and **BSS** from ELF files, but with different results and implications. Speciifically, they place the **GOT** section _before_ the **BSS**. That is, **GOT** is at lower addresses than **BSS**, hence making it impossible to overwrite **GOT** entries by overflowing variables in the **BSS** (rembember writing into memory happens from lower toward higher addresses).
 | 
					**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`).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Let's break down the concept into its two distinct types for clarity.
 | 
					Modern linkers implement RELRO by **re–ordering** the **GOT** (and a few other sections) so they live **before** the **.bss** and – most importantly – by creating a dedicated `PT_GNU_RELRO` segment that is remapped `R–X` right after the dynamic loader finishes applying relocations.  Consequently, typical buffer overflows in the **.bss** can no longer reach the GOT and arbitrary‐write primitives cannot be used to overwrite function pointers that sit inside a RELRO-protected page.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### **Partial RELRO**
 | 
					There are **two levels** of protection that the linker can emit:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Partial RELRO** takes a simpler approach to enhance security without significantly impacting the binary's performance. Partial RELRO makes **the .got read only (the non-PLT part of the GOT section)**. Bear in mind that the rest of the section (like the .got.plt) is still writeable and, therefore, subject to attacks. This **doesn't prevent the GOT** to be abused **from arbitrary write** vulnerabilities.
 | 
					### Partial RELRO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note: By default, GCC compiles binaries with 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**
 | 
					### Full RELRO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Full RELRO** steps up the protection by **making the entire GOT (both .got and .got.plt) and .fini_array** section completely **read-only.** Once the binary starts all the function addresses are resolved and loaded in the GOT, then, GOT is marked as read-only, effectively preventing any modifications to it during runtime.
 | 
					* 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**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
However, the trade-off with Full RELRO is in terms of performance and startup time. Because it needs to resolve all dynamic symbols at startup before marking the GOT as read-only, **binaries with Full RELRO enabled may experience longer load times**. This additional startup overhead is why Full RELRO is not enabled by default in all binaries.
 | 
					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**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
It's possible to see if Full RELRO is **enabled** in a binary with:
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## How to Check the RELRO status of a binary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
 | 
					$ checksec --file ./vuln
 | 
				
			||||||
 | 
					[*] '/tmp/vuln'
 | 
				
			||||||
 | 
					    Arch:     amd64-64-little
 | 
				
			||||||
 | 
					    RELRO:    Full
 | 
				
			||||||
 | 
					    Stack:    Canary found
 | 
				
			||||||
 | 
					    NX:       NX enabled
 | 
				
			||||||
 | 
					    PIE:      No PIE (0x400000)
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Bypass
 | 
					`checksec` (part of [pwntools](https://github.com/pwncollege/pwntools) and many distributions) parses `ELF` headers and prints the protection level.  If you cannot use `checksec`, rely on `readelf`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If Full RELRO is enabled, the only way to bypass it is to find another way that doesn't need to write in the GOT table to get arbitrary execution.
 | 
					```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
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note that **LIBC's GOT is usually Partial RELRO**, so it can be modified with an arbitrary write. More information in [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.**
 | 
					```bash
 | 
				
			||||||
 | 
					# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag
 | 
				
			||||||
 | 
					$ readelf -d ./vuln | grep BIND_NOW
 | 
				
			||||||
 | 
					 0x0000000000000010 (FLAGS)              FLAGS: BIND_NOW
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If the binary is running (e.g. a set-uid root helper), you can still inspect the executable **via `/proc/$PID/exe`**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Enabling RELRO when compiling your own code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```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` works for both **GCC/clang** (passed after `-Wl,`) and **ld** directly.  When using **CMake 3.18+** you can request Full RELRO with the built-in preset:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```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")
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Bypass Techniques
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					| RELRO level | Typical primitive | Possible exploitation techniques |
 | 
				
			||||||
 | 
					|-------------|-------------------|----------------------------------|
 | 
				
			||||||
 | 
					| None / Partial | Arbitrary write | 1. Overwrite **.got.plt** entry and pivot execution.<br>2. **ret2dlresolve** – craft fake `Elf64_Rela` & `Elf64_Sym` in a writable segment and call `_dl_runtime_resolve`.<br>3. Overwrite function pointers in **.fini_array** / **atexit()** list. |
 | 
				
			||||||
 | 
					| Full | GOT is read-only | 1. Look for **other writable code pointers** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, callbacks in custom `.data` sections, JIT pages).<br>2. Abuse *relative read* primitives to leak libc and perform **SROP/ROP into libc**.<br>3. Inject a rogue shared object via **DT_RPATH**/`LD_PRELOAD` (if environment is attacker-controlled) or **`ld_audit`**.<br>4. Exploit **format-string** or partial pointer overwrite to divert control-flow without touching the GOT. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> 💡 Even with Full RELRO the **GOT of loaded shared libraries (e.g. libc itself)** is **only Partial RELRO** because those objects are already mapped when the loader applies relocations.  If you gain an **arbitrary write** primitive that can target another shared object’s pages you can still pivot execution by overwriting libc’s GOT entries or the `__rtld_global` stack, a technique regularly exploited in modern CTF challenges.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Real-world bypass example (2024 CTF – *pwn.college “enlightened”*)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The challenge shipped with Full RELRO.  The exploit used an **off-by-one** to corrupt the size of a heap chunk, leaked libc with `tcache poisoning`, and finally overwrote `__free_hook` (outside of the RELRO segment) with a one-gadget to get code execution.  No GOT write was required.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Recent research & vulnerabilities (2022-2025)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **glibc 2.40 de-precates `__malloc_hook` / `__free_hook` (2025)** – Most modern heap exploits that abused these symbols must now pivot to alternative vectors such as **`rtld_global._dl_load_jump`** or C++ exception tables.  Because hooks live **outside** of RELRO their removal increases the difficulty of Full-RELRO bypasses.
 | 
				
			||||||
 | 
					* **Binutils 2.41 “max-page-size” fix (2024)** – A bug allowed the last few bytes of the RELRO segment to share a page with writable data on some ARM64 builds, leaving a tiny **RELRO gap** that could be written after `mprotect`.  Upstream now aligns `PT_GNU_RELRO` to page boundaries, eliminating that edge-case.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## References
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Binutils documentation – *`-z relro`, `-z now` and `PT_GNU_RELRO`*  
 | 
				
			||||||
 | 
					* *“RELRO – Full, Partial and Bypass Techniques”* – blog post @ wolfslittlered 2023
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{#include ../../banners/hacktricks-training.md}}
 | 
					{{#include ../../banners/hacktricks-training.md}}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user