Skip to content

Commit f34f2a0

Browse files
authored
Merge pull request #2471 from HackTricks-wiki/research_update_src_binary-exploitation_arbitrary-write-2-exec_www2exec-atexit_20260706_040435
Research Update Enhanced src/binary-exploitation/arbitrary-w...
2 parents 17974b9 + ef93a2e commit f34f2a0

1 file changed

Lines changed: 63 additions & 3 deletions

File tree

src/binary-exploitation/arbitrary-write-2-exec/www2exec-atexit.md

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
1010
**`atexit()`** is a function to which **other functions are passed as parameters.** These **functions** will be **executed** when executing an **`exit()`** or the **return** of the **main**.\
1111
If you can **modify** the **address** of any of these **functions** to point to a shellcode for example, you will **gain control** of the **process**, but this is currently more complicated.\
12-
Currently the **addresses to the functions** to be executed are **hidden** behind several structures and finally the address to which it points are not the addresses of the functions, but are **encrypted with XOR** and displacements with a **random key**. So currently this attack vector is **not very useful at least on x86** and **x64_86**.\
13-
The **encryption function** is **`PTR_MANGLE`**. **Other architectures** such as m68k, mips32, mips64, aarch64, arm, hppa... **do not implement the encryption** function because it **returns the same** as it received as input. So these architectures would be attackable by this vector.
12+
Currently the **addresses to the functions** to be executed are **hidden** behind several structures and finally the address to which it points are not the addresses of the functions, but are **encrypted with XOR** and displacements with a **random key**. So currently this attack vector is **not very useful at least on x86** and **x86_64**.\
13+
The **encryption function** is **`PTR_MANGLE`**. However, the exact implementation is **architecture- and glibc-version-dependent**: the generic glibc header is a no-op, but modern arch-specific implementations exist for important targets such as **x86_64** and **aarch64**. Therefore, **do not assume** that a non-x86 target automatically has **unmangled** exit pointers; check the target build's `pointer_guard.h` / `PTR_MANGLE` macros first.
1414

1515
You can find an in depth explanation on how this works in [https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html](https://m101.github.io/binholic/2017/05/20/notes-on-abusing-exit-handlers.html)
1616

@@ -124,7 +124,7 @@ __call_tls_dtors (void)
124124
For each registered function in **`tls_dtor_list`**, it'll demangle the pointer from **`cur->func`** and call it with the argument **`cur->obj`**.
125125

126126
Using the **`tls`** function from this [**fork of GEF**](https://github.com/bata24/gef), it's possible to see that actually the **`dtor_list`** is very **close** to the **stack canary** and **PTR_MANGLE cookie**. So, with an overflow on it's it would be possible to **overwrite** the **cookie** and the **stack canary**.\
127-
Overwriting the PTR_MANGLE cookie, it would be possible to **bypass the `PTR_DEMANLE` function** by setting it to 0x00, will mean that the **`xor`** used to get the real address is just the address configured. Then, by writing on the **`dtor_list`** it's possible **chain several functions** with the function **address** and it's **argument.**
127+
Overwriting the PTR_MANGLE cookie, it would be possible to **bypass the `PTR_DEMANGLE` function** by setting it to 0x00, will mean that the **`xor`** used to get the real address is just the address configured. Then, by writing on the **`dtor_list`** it's possible **chain several functions** with the function **address** and it's **argument.**
128128

129129
Finally notice that the stored pointer is not only going to be xored with the cookie but also rotated 17 bits:
130130

@@ -235,6 +235,66 @@ It's possible to check the **`initial` structure** in a debugging session with G
235235
To abuse this you need either to **leak or erase the `PTR_MANGLE`cookie** and then overwrite a `cxa` entry in initial with `system('/bin/sh')`.\
236236
You can find an example of this in the [**original blog post about the technique**](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#6---code-execution-via-other-mangled-pointers-in-initial-structure).
237237
238+
## Practical `__exit_funcs` / `initial` forgery on modern glibc
239+
240+
Since classic hooks such as `__free_hook` disappeared from normal modern-glibc targets, a very common follow-up to an arbitrary write is to **forge a fake `struct exit_function_list`** and then repoint **`__exit_funcs`** to it. This is basically the most practical version of abusing `__run_exit_handlers()` on recent glibc releases.
241+
242+
The important points are:
243+
244+
- `exit()` is just a wrapper around **`__run_exit_handlers(status, &__exit_funcs, true, true)`**, so corrupting `__exit_funcs` is often enough to win when the program returns from `main` or calls `exit()`.
245+
- On a lot of targets the statically allocated `initial` list already contains one `ef_cxa` entry for **`_dl_fini`**. That gives you a **known plaintext** pointer inside the exit list.
246+
- If you can leak the **mangled** `_dl_fini` pointer and also know the **real** `_dl_fini` address, you can recover the pointer-guard cookie and then mangle any function you want. For a refresher on pointer guard, check [**this page**](../common-binary-protections-and-bypasses/libc-protections.md#pointer-guard).
247+
248+
On **x86_64**, the formulas are usually:
249+
250+
```python
251+
rol = lambda x, n: ((x << n) | (x >> (64 - n))) & ((1 << 64) - 1)
252+
ror = lambda x, n: ((x >> n) | (x << (64 - n))) & ((1 << 64) - 1)
253+
ptr_guard = ror(enc_dl_fini, 0x11) ^ real_dl_fini
254+
enc_target = rol(real_target ^ ptr_guard, 0x11)
255+
```
256+
257+
With that, a forged exit list usually looks like:
258+
259+
```c
260+
struct exit_function_list {
261+
struct exit_function_list *next;
262+
size_t idx;
263+
struct exit_function fns[32];
264+
};
265+
266+
// Minimal fake list
267+
next = NULL;
268+
idx = 1;
269+
fns[0].flavor = ef_cxa;
270+
fns[0].func.cxa.fn = mangled(system);
271+
fns[0].func.cxa.arg = binsh_ptr;
272+
```
273+
274+
Then, overwrite `__exit_funcs` so it points to the fake list and make the program terminate via `exit()` or `return` from `main`. Remember that **`idx` is consumed backwards**, so if you forge **several** entries they will run in **reverse order**.
275+
276+
### Practical notes
277+
278+
- If you only have a **write** primitive but no read, another option is to **overwrite the pointer-guard cookie in TLS** with `0x0` and store `rol(target, 0x11)` as the encrypted pointer. This works because demangling becomes just `ror(ptr, 0x11)`.
279+
- If you already have a **libc leak**, the encrypted `_dl_fini` entry is often enough to recover the cookie because **`ld.so` is commonly adjacent to libc** in memory and `_dl_fini` sits at a fixed offset inside `ld.so` for the target build.
280+
- This technique is useless if the target terminates via **`_exit()`** or crashes before returning to the normal exit path. In those cases prefer other post-write targets such as [**`.fini_array` / `.dtors`**](www2exec-.dtors-and-.fini_array.md) or non-exit-time pivots.
281+
282+
### Quick debugging checklist
283+
284+
```gdb
285+
b __run_exit_handlers
286+
b __call_tls_dtors
287+
x/10gx __exit_funcs
288+
x/gx $fs_base+0x30
289+
```
290+
291+
If `__exit_funcs` is already corrupted, inspect the first entry and verify: `flavor`, `idx`, the mangled function pointer, and whether the program will really reach `exit()` instead of `_exit()`.
292+
293+
## References
294+
295+
- [Code execution part 1: from exit to system](https://blog.rop.la/en/exploiting/2024/06/11/code-exec-part1-from-exit-to-system.html)
296+
- [Dead or Alive - forging custom exit handlers on modern glibc](https://draksec.cz/blog/htb-uni-ctf-24/dead-or-alive/)
297+
238298
{{#include ../../banners/hacktricks-training.md}}
239299

240300

0 commit comments

Comments
 (0)