binary-protection-bypass について
バイナリ分析や脆弱性調査向けに、各種防御機構の識別方法と回避手法、必要なプリミティブをまとめたセキュリティスキルです。一般的なコンパイル時の防御からハードウェア機能まで扱います。
主な機能
- 01ASLR、PIE、NX、RELRO、Canary の識別
- 02防御機構と回避手法の対応付け
- 03手法に必要な条件とプリミティブの提示
- 04FORTIFY_SOURCE、CET、ARM MTE の解説
活用シーン
- 01checksec の防御設定を確認
- 02有効な防御に沿って攻撃手順を検討
- 03回避手法と必要条件を比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill binary-protection-bypassインストール後、Claude Code で/binary-protection-bypassを入力して読み込まれたか確認します。
同梱ファイルPROTECTION_BYPASS_MATRIX.md
詳細
スキル:バイナリ保護の回避 — エキスパート向け攻撃プレイブック
AI LOAD INSTRUCTION:バイナリ保護の特定と回避に関するエキスパート向け手法です。ASLR、PIE、NX、RELRO、canary、FORTIFY_SOURCE、stack clash、CET shadow stack、ARM MTE を扱います。各保護について、回避方法と必要なプリミティブを組み合わせて説明します。ctf-wiki の緩和策に関するセクションと、実環境でのエクスプロイトをもとに要点をまとめています。ベースモデルは、どの保護がどの攻撃を阻止するのかを混同しやすく、複数の保護が組み合わさった場合の効果を見落としがちです。
0. 関連するルーティング
- stack-overflow-and-rop — NX を回避する ROP チェーン、ASLR を回避する ret2libc
- format-string-exploitation — canary、PIE、libc アドレスのリークに使う主な手法
- heap-exploitation — RELRO を回避するヒープ攻撃(GOT が読み取り専用の場合)
- arbitrary-write-to-rce — RELRO によって GOT が保護されている場合に上書きする対象
高度なリファレンス
保護 × 回避方法 × プリミティブの包括的なマトリックスについては、PROTECTION_BYPASS_MATRIX.md を読み込んでください。
1. 保護の特定
$ checksec ./binary
[*] '/path/to/binary'
Arch: amd64-64-little
RELRO: Full RELRO ← GOT read-only
Stack: Canary found ← stack canary enabled
NX: NX enabled ← stack not executable
PIE: PIE enabled ← position-independent code
FORTIFY: Enabled ← fortified libc functions
簡易特定表
| 保護 | 確認コマンド | バイナリの指標 |
|---|---|---|
| ASLR | cat /proc/sys/kernel/randomize_va_space | OS レベル(0=無効、1=部分的、2=完全) |
| PIE | checksec または readelf -h(Type: DYN) | -pie を指定してコンパイルされたバイナリ |
| NX | checksec または readelf -l(RWE セグメントなし) | gcc -z noexecstack(デフォルトで有効) |
| Canary | checksec または __stack_chk_fail@plt を確認 | gcc -fstack-protector-all |
| Partial RELRO | readelf -l(GNU_RELRO セグメント、.got.plt は書き込み可能) | gcc -Wl,-z,relro |
| Full RELRO | readelf -l と .got セクションが読み取り専用であることを確認 | gcc -Wl,-z,relro,-z,now |
| FORTIFY | __printf_chk、__memcpy_chk などが存在すること | gcc -D_FORTIFY_SOURCE=2 |
2. ASLR の回避
ASLR は実行のたびに、スタック、ヒープ、libc、mmap 領域のベースアドレスをランダム化します。
| 回避方法 | 必要なプリミティブ | 備考 |
|---|---|---|
| 情報リーク | 任意の読み取りプリミティブ(フォーマット文字列、範囲外読み取り、UAF) | libc/スタック/ヒープのアドレスをリーク → ベースアドレスを計算 |
| 部分上書き | 書き込みプリミティブ(長さに制限あり) | 末尾の 1~2 バイトを上書き(ページオフセットは固定) |
| ブルートフォース(32 ビット) | 再接続/再試行できること | 約 256~4096 回の試行(エントロピー 8~12 ビット) |
| Return-to-PLT | スタックオーバーフロー | PLT アドレスはバイナリのベースアドレスから固定オフセットにある(PIE なしの場合) |
| ret2dlresolve | スタックオーバーフロー+書き込みプリミティブ | libc のベースアドレスを知らずに任意の関数を解決 |
| フォーマット文字列によるリーク | フォーマット文字列の脆弱性 | %N$p でスタック/libc/ヒープのアドレスを取得 |
| スタック読み取り | 1 バイトずつ(fork サーバー) | クラッシュオラクルを使ってスタックを 1 バイトずつ読み取る |
ASLR のエントロピー(x86-64 Linux)
| 領域 | エントロピー(ビット) | 位置数 |
|---|---|---|
| スタック | 22 | 約 4M |
| mmap/libc | 28 | 約 256M |
| ヒープ(brk) | 13 | 約 8K |
| PIE バイナリ | 28 | 約 256M |
3. PIE の回避
PIE(位置独立実行ファイル)は、バイナリ自体のコード/データのベースアドレスをランダム化します。
| 回避方法 | 必要なプリミティブ | 備考 |
|---|---|---|
| 情報リーク | スタックからリターンアドレスを読み取る | PIE のベースアドレス = リークしたアドレス - 既知のオフセット |
| 部分上書き | 1 バイトまたは 2 バイトの書き込み | ページオフセットの下位 12 ビットは固定 |
| フォーマット文字列によるリーク | フォーマット文字列の脆弱性 | N が .text のリターンアドレスを指す場合の %N$p |
| 相対アドレス指定 | バイナリのレイアウトに関する知識 | 相対オフセットが分かっていれば、リークは 1 つだけでよい |
部分上書きの詳細
PIE binary loaded at: 0x555555554000 (example)
Function at offset 0x1234: 0x555555555234
Overwrite return address last 2 bytes: 0x?234 → 0x?XXX
Unknown: bits 12-15 (one nibble = 4 bits = 16 possibilities)
Success rate: 1/16 per attempt
4. NX/DEP のバイパス
NX(No-eXecute)/DEP(Data Execution Prevention)は、スタック/ヒープ上のコード実行を防止します。
| バイパス方法 | 詳細 |
|---|---|
| ROP(Return-Oriented Programming) | ret で終わる既存のコードガジェットを連結します |
| ret2libc | libc 関数(system、execve)を直接呼び出します |
| ret2csu | __libc_csu_init ガジェットを使って、制御された関数呼び出しを行います |
| ret2dlresolve | 動的リンカー構造を偽造し、任意の関数を解決させます |
| SROP | sigreturn を使い、偽のシグナルフレームからすべてのレジスターを設定します |
| mprotect ROP | mprotect(addr, size, PROT_RWX) を連結 → ページを実行可能にする → シェルコードにジャンプします |
| JIT スプレー | JIT 環境(V8 など)で、JIT コンパイラーを通じて実行可能コードを作成します |
mprotect チェーン
# Make stack executable, then jump to shellcode
rop = b'A' * offset
rop += p64(pop_rdi) + p64(stack_page) # page-aligned address
rop += p64(pop_rsi) + p64(0x1000) # size
rop += p64(pop_rdx) + p64(7) # PROT_READ|PROT_WRITE|PROT_EXEC
rop += p64(mprotect_addr)
rop += p64(shellcode_addr) # jump to shellcode on now-executable stack
5. RELRO のバイパス
| RELRO レベル | GOT の状態 | バイパス方法 |
|---|---|---|
| No RELRO | GOT は完全に書き込み可能 | GOT を直接上書きします |
| Partial RELRO | .got.plt は書き込み可能(遅延バインディング) | GOT の上書きが引き続き可能です |
| Full RELRO | すべての GOT エントリーがロード時に解決され、GOT は読み取り専用 | GOT には書き込めないため、他の構造を標的にします |
Full RELRO の代替ターゲット
| ターゲット | 条件 | 方法 |
|---|---|---|
__malloc_hook | glibc < 2.34 | one_gadget で上書きします |
__free_hook | glibc < 2.34 | system で上書きし、free("/bin/sh") を実行します |
_IO_FILE vtable | すべての glibc | FSOP/vtable ハイジャック |
__exit_funcs | すべての glibc | 終了ハンドラーリストを上書きします |
TLS_dtor_list | glibc ≥ 2.34 | スレッドローカルデストラクターリスト(ポインターガードが必要) |
.fini_array | 書き込み可能な場合 | デストラクター関数ポインターを上書きします |
| スタックのリターンアドレス | スタックへの直接書き込み | リターンアドレスを上書きして ROP を実行します |
包括的なターゲット一覧については、arbitrary-write-to-rce を参照してください。
6. CANARY のバイパス
| 方法 | 条件 | 詳細 |
|---|---|---|
| フォーマット文字列リーク | printf(user_input) | %N$p を使ってスタックから canary を読み取ります |
| ブルートフォース | fork() サーバー(canary は子プロセスでも維持されます) | 1 バイトずつ試行します:256 × (canary_size-1) 回の試行 |
| スタック読み取り | 部分上書き/情報リーク | canary のヌルバイトを上書きし、出力からリークさせます |
| スレッド canary の上書き | オーバーフローが TLS に到達する | canary は fs:[0x28] にあります。バッファーを越えて TLS までオーバーフローさせ、canary を既知の値で上書きします |
| canary 相対上書き | canary の後、リターンアドレスの前でオーバーフローする | canary を避け、リターンアドレスのみを上書きします(まれなレイアウト) |
| ヒープベース | 脆弱性がスタックではなくヒープ上にある | canary が保護するのはスタックのみです |
| __stack_chk_fail GOT の上書き | Partial RELRO | __stack_chk_fail@GOT を無害な関数に向けて上書きし、canary チェックを通過させます |
Canary の形式
x86: 0x00XXXXXX (4 bytes, leading null byte)
x86-64: 0x00XXXXXXXXXXXXXX (8 bytes, leading null byte)
先頭の \x00 によって、文字列操作で誤って canary が読み取られるのを防ぎます。
7. FORTIFY_SOURCE のバイパス
_FORTIFY_SOURCE=2 はバッファーサイズチェックを追加し、フォーマット文字列操作を制限します。
| 強化された関数 | 制限 | バイパス方法 |
|---|---|---|
__printf_chk | 位置指定引数を伴う %n(%N$n)は禁止されています | 位置指定なしの %n または %hn チェーンを使います |
__memcpy_chk | 宛先バッファーのサイズがチェックされます | スタックの代わりにヒープオーバーフローを使います |
__strcpy_chk | 同上 | |
__read_chk | 読み取りサイズがバッファーに対してチェックされます |
FORTIFY_SOURCE を使ったフォーマット文字列
# %1$n is blocked by __printf_chk
# But sequential (non-positional) %n may still work:
# Print exact byte count, then %hn — must be very precise
# Or: find unfortified printf in binary/libc via ROP
8. CET(Control-flow Enforcement Technology)
Intel CET は、2 つのメカニズムを追加します。
シャドウスタック
- ハードウェアで管理されるリターンアドレスのコピー
retの実行時に、CPU はシャドウスタックが実際のスタックと一致するかをチェックします- 不一致の場合 →
#CPフォールト(制御保護例外)
| 影響 | 詳細 |
|---|---|
| ROP はブロックされる | ret の実行時にリターンアドレスの上書きが検出されます |
| JOP は可能 | jmp [reg] はシャドウスタックでチェックされません |
| COP は可能 | call [reg] はシャドウスタックにプッシュされますが、ターゲットは IBT で検証されます |
間接分岐トラッキング(IBT)
- 間接
jmp/callはENDBR64命令に着地する必要があります ENDBR以外の場所に着地すると、#CPフォールトが発生します
バイパス:
- データのみを狙う攻撃(制御フローは変更しません)
- 有用な操作につながる、有効な ENDBR ガジェットを見つけます
- ENDBR で始まるガジェットを使った JOP
- CFI の対象範囲外にある構造を標的にします(modprobe_path、関数ポインター配列)
9. MTE(メモリータグ付け拡張、ARM)
ARM MTE は、メモリーポインターとメモリー割り当てに 4 ビットのタグを割り当てます。タグが一致しない場合、フォールトが発生します。
| 項目 | 詳細 |
|---|---|
| タグのビット数 | ポインター内の 4 ビット(ビット 56~59)=16 個のタグが使用可能 |
| グラニュール | 16 バイト(各 16 バイトのグラニュールに 1 つのタグがあります) |
| チェック | ロード/ストア時に、ポインターのタグがメモリータグと一致する必要があります |
| 確率 | タグはランダムであり、攻撃者が正しく推測できる確率は 1/16 です |
バイパス手法
| 手法 | 成功率 |
|---|---|
| ブルートフォース | 試行ごとに 1/16(6.25%) |
| タグオラクル | サイドチャネルを使ってタグを判定します(タイミング、エラーメッセージ) |
| 範囲内でのエクスプロイト | 同じタグ付き領域内にとどまります(相対オフセットを使用します) |
| タグバイパスガジェット | アクセス可能な場合は LDGM/STGM 命令を使用します |
| 投機的実行 | Spectre 型のタグチェックバイパス |
10. 意思決定ツリー
Binary analysis: checksec output
├── NX disabled?
│ └── Shellcode on stack/heap (simplest path)
│
├── NX enabled (standard modern binary)?
│ ├── Need code execution → ROP/ret2libc
│ │
│ ├── Canary enabled?
│ │ ├── fork server? → byte-by-byte brute-force
│ │ ├── Format string? → leak canary via %p
│ │ ├── Heap vuln? → canary doesn't protect heap
│ │ └── Partial RELRO? → overwrite __stack_chk_fail@GOT
│ │
│ ├── PIE enabled?
│ │ ├── Format string? → leak .text address → PIE base
│ │ ├── Partial overwrite → last 12 bits fixed (1/16 brute-force)
│ │ └── OOB read? → leak code pointer
│ │
│ ├── ASLR enabled?
│ │ ├── Info leak available → leak libc base
│ │ ├── No leak → ret2dlresolve or SROP
│ │ ├── 32-bit? → brute-force feasible (~4096 attempts)
│ │ └── Return-to-PLT (no libc base needed for PLT calls)
│ │
│ ├── RELRO level?
│ │ ├── None/Partial → GOT overwrite
│ │ └── Full → alternative targets:
│ │ ├── glibc < 2.34 → __malloc_hook / __free_hook
│ │ ├── glibc ≥ 2.34 → _IO_FILE / exit_funcs / TLS_dtor_list
│ │ ├── .fini_array (if writable)
│ │ └── Stack return address
│ │
│ └── FORTIFY_SOURCE?
│ ├── Blocks positional %n → use sequential %n or heap exploit
│ └── Blocks buffer overflows in fortified functions → use unfortified paths
│
├── CET (shadow stack)?
│ ├── ROP blocked → data-only attack or JOP
│ └── ENDBR-gadget chaining
│
└── MTE (ARM)?
├── 1/16 brute-force
└── Stay in-bounds for relative corruption