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.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
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.
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.
Step 2: Resolve Addresses (and Leak libc if Needed)
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
# 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'])
# 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.
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.
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.