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.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
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.