| name | arbitrary-write-to-rce |
| description | Arbitrary write to RCE playbook. Use when you have an arbitrary write primitive (from heap exploitation, format string, or OOB write) and need to convert it into code execution by targeting GOT, hooks, _IO_FILE vtable, exit_funcs, TLS_dtor_list, modprobe_path, .fini_array, or C++ vtables. |
SKILL: Arbitrary Write to Code Execution — Expert Attack Playbook
AI LOAD INSTRUCTION: Expert techniques for converting an arbitrary write primitive into code execution. Covers every major overwrite target organized by glibc version compatibility: GOT, __malloc_hook, __free_hook, _IO_FILE vtable, __exit_funcs, TLS_dtor_list, _dl_fini, modprobe_path, .fini_array, C++ vtable, and setcontext gadget. This is the "last mile" skill. Base models often target hooks that no longer exist (post-glibc 2.34) or miss pointer mangling requirements.
0. RELATED ROUTING
1. TARGET SELECTION BY GLIBC VERSION
| Target | glibc < 2.24 | 2.24–2.33 | ≥ 2.34 | Required Knowledge |
|---|
| GOT overwrite | OK (Partial RELRO) | OK (Partial RELRO) | OK (Partial RELRO) | Binary base |
__malloc_hook | OK | OK | Removed | libc base |
__free_hook | OK | OK | Removed | libc base |
__realloc_hook | OK | OK | Removed | libc base |
_IO_FILE vtable (direct) | OK | Vtable range check | Vtable range check | libc base + heap |
_IO_FILE via _IO_str_jumps | N/A | OK (2.24–2.27) | Patched | libc base + heap |
_IO_FILE via _IO_wfile_jumps | N/A | OK (≥ 2.28) | OK | libc base + heap |
__exit_funcs | OK | OK | OK | libc base + pointer guard |
TLS_dtor_list | N/A | N/A | OK | TLS addr + pointer guard |
_dl_fini / link_map | OK | OK | OK | ld.so base |
modprobe_path (kernel) | OK | OK | OK | Kernel base |
.fini_array | OK | OK | OK | Binary base (if writable) |
| C++ vtable | OK | OK | OK | Object address + heap |
setcontext gadget | OK | OK (changed in 2.29) | OK | libc base |
| Stack return address | Always | Always | Always | Stack address |
2. GOT OVERWRITE
Replace a function pointer in the Global Offset Table.
Requirements
- Partial RELRO (
.got.plt writable) — Full RELRO blocks this entirely
Common Targets
| Overwrite From | Overwrite To | Trigger |
|---|
printf@GOT | system | Next printf(user_input) with input = /bin/sh |
free@GOT | system | Next free(ptr) where ptr points to "/bin/sh" |
strlen@GOT | system | Next strlen(user_input) |
atoi@GOT | system | Next atoi(user_input) with input = "sh" |
puts@GOT | system | Next puts(user_input) |
exit@GOT | main or gadget | Create loop for multi-shot exploit |
__stack_chk_fail@GOT | ret gadget | Neutralize canary check |
from pwn import fmtstr_payload
payload = fmtstr_payload(offset, {elf.got['printf']: libc.sym['system']})
3. __malloc_hook / __free_hook (glibc < 2.34)
__malloc_hook
write(libc.sym['__malloc_hook'], one_gadget_addr)
io.sendline('%100000c')
__free_hook
write(libc.sym['__free_hook'], libc.sym['system'])
chunk_data = b'/bin/sh\x00'
Realloc Trick for one_gadget Constraints
write(libc.sym['__realloc_hook'], one_gadget)
write(libc.sym['__malloc_hook'], libc.sym['realloc'] + 2)
4. _IO_FILE VTABLE
See IO_FILE_EXPLOITATION.md for full details.
Quick Summary by Version
| glibc | Method | Vtable Target |
|---|
| < 2.24 | Direct vtable overwrite | Point vtable to fake table with system at __overflow offset |
| 2.24–2.27 | _IO_str_jumps | Within valid range; _IO_str_finish calls _s._free_buffer |
| ≥ 2.28 | _IO_wfile_jumps | Wide-char path: _wide_data->_wide_vtable not range-checked |
| ≥ 2.35 | House of Cat | _IO_wfile_seekoff → _IO_switch_to_wget_mode → fake wide vtable call |
FSOP Trigger
5. __exit_funcs / __atexit
Exploitation
import struct
def mangle(ptr, guard):
return ((ptr ^ guard) << 0x11 | (ptr ^ guard) >> (64-0x11)) & 0xffffffffffffffff
Without Pointer Guard Knowledge
If you can overwrite both the function pointer AND the pointer guard (in TLS at fs:[0x30]):
- Set pointer guard to 0
- Set function pointer to
ROL(target, 0x11)
- Demangling:
ROR(stored, 0x11) ^ 0 = ROR(ROL(target, 0x11), 0x11) = target
6. TLS_dtor_list (glibc ≥ 2.34)
Thread-local destructor list — the primary post-2.34 target.
Location
TLS area (pointed by fs register on x86-64)
tls_dtor_list is a thread-local variable in libc
Typically at fs:[offset] — offset found via libc symbol or brute-force
Exploitation
entry = p64(mangled_func_ptr)
entry += p64(arg_value)
entry += p64(0)
7. _dl_fini / LINK_MAP CORRUPTION
Attack Vector
During exit(), _dl_fini iterates the link_map list and calls DT_FINI_ARRAY entries.
for each loaded library (link_map entry):
if l_info[DT_FINI_ARRAY]:
array = l_addr + l_info[DT_FINI_ARRAY]->d_un.d_ptr
for each entry in array:
entry()
Exploitation
- Corrupt a
link_map entry's l_addr (relocation base) to shift the FINI_ARRAY pointer
- Or corrupt
l_info[DT_FINI_ARRAY] to point to fake array
- Fake array contains target function pointer (system, one_gadget)
- Trigger:
exit() → _dl_fini → calls fake destructor
Advantage: No pointer mangling (function pointers in FINI_ARRAY are not mangled).
8. modprobe_path (KERNEL)
Overwrite the kernel's modprobe_path to execute arbitrary commands as root.
kernel_write(modprobe_path_addr, b'/tmp/x\x00')
See kernel-exploitation for kernel write primitives.
9. .fini_array
Overwrite destructor function pointers called during normal program exit.
Limitation: .fini_array may be read-only in Full RELRO binaries.
10. C++ VTABLE OVERWRITE
fake_vtable = p64(0)
fake_vtable += p64(0)
fake_vtable += p64(target_func)
fake_vtable += p64(target_func)
11. setcontext GADGET
setcontext in libc loads registers from a ucontext_t structure — useful as a pivot gadget.
glibc < 2.29
glibc ≥ 2.29
12. DECISION TREE
You have an arbitrary write primitive. What to target?
├── What's the RELRO level?
│ ├── None / Partial → GOT overwrite (simplest, most reliable)
│ │ └── printf→system, free→system, atoi→system
│ └── Full RELRO → GOT read-only, choose alternative:
│
├── What glibc version?
│ ├── < 2.34 (hooks available)
│ │ ├── __free_hook = system → free("/bin/sh") [easiest]
│ │ ├── __malloc_hook = one_gadget → trigger malloc [if constraints met]
│ │ └── __realloc_hook + __malloc_hook realloc trick [adjust stack alignment]
│ │
│ ├── ≥ 2.34 (no hooks)
│ │ ├── Know pointer guard (fs:[0x30])?
│ │ │ ├── YES → __exit_funcs or TLS_dtor_list
│ │ │ └── NO → overwrite pointer guard to 0 first, then exit_funcs
│ │ ├── _IO_FILE + _IO_wfile_jumps (House of Apple 2 / Cat)
│ │ │ └── Need: libc base + heap address + controllable FILE structure
│ │ ├── _dl_fini link_map corruption
│ │ │ └── Need: ld.so base address
│ │ └── .fini_array (if writable)
│ │ └── Need: binary base (no PIE, or PIE base leaked)
│ │
│ └── Any version
│ ├── Stack return address (if stack address known)
│ └── C++ vtable (if targeting C++ object with virtual functions)
│
├── Kernel write primitive?
│ ├── modprobe_path (simplest kernel→root)
│ ├── core_pattern (/proc/sys/kernel/core_pattern)
│ └── Direct cred structure overwrite
│
└── Need to chain read → write → execute?
└── setcontext gadget: arbitrary write → pivot RSP → ROP chain
├── glibc < 2.29: setcontext+53 (uses RDI)
└── glibc ≥ 2.29: setcontext+61 (uses RDX, need mov rdx, [rdi] gadget)