From 36548e430cb72e1f227042fd7867ced753c7ae4a Mon Sep 17 00:00:00 2001 From: Translator Date: Thu, 17 Jul 2025 00:13:23 +0000 Subject: [PATCH] Translated ['src/binary-exploitation/common-binary-protections-and-bypas --- .../relro.md | 92 ++++++++++++++++--- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md b/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md index 4b0f1fe51..5269cb337 100644 --- a/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md +++ b/src/binary-exploitation/common-binary-protections-and-bypasses/relro.md @@ -4,30 +4,96 @@ ## Relro -**RELRO**は**Relocation Read-Only**の略で、**GOT (Global Offset Table)**の上書きに関連するリスクを軽減するためにバイナリで使用されるセキュリティ機能です。**RELRO**保護には2種類あります:(1) **Partial RELRO**と(2) **Full RELRO**です。どちらもELFファイルから**GOT**と**BSS**の順序を変更しますが、結果と影響は異なります。具体的には、**GOT**セクションを**BSS**の_前_に配置します。つまり、**GOT**は**BSS**よりも低いアドレスにあり、したがって**BSS**内の変数をオーバーフローさせることで**GOT**エントリを上書きすることは不可能です(メモリへの書き込みは低いアドレスから高いアドレスに向かって行われることを思い出してください)。 +**RELRO**は**Relocation Read-Only**の略で、リンカー(`ld`)によって実装された緩和策であり、ELFのデータセグメントのサブセットを**すべての再配置が適用された後に読み取り専用にする**ものです。目的は、攻撃者がプログラム実行中に参照される**GOT(Global Offset Table)**やその他の再配置関連テーブルのエントリを上書きするのを防ぐことです(例:`__fini_array`)。 -この概念を明確にするために、2つの異なるタイプに分解しましょう。 +現代のリンカーは、**GOT**(およびいくつかの他のセクション)を**.bss**の**前に**配置し、最も重要なことに、動的ローダーが再配置を適用した後に`R–X`として再マッピングされる専用の`PT_GNU_RELRO`セグメントを作成することによってRELROを実装します。その結果、**.bss**内の典型的なバッファオーバーフローはGOTに到達できなくなり、任意の書き込みプリミティブを使用してRELRO保護ページ内の関数ポインタを上書きすることはできません。 -### **Partial RELRO** +リンカーが出力できる保護の**2つのレベル**があります: -**Partial RELRO**は、バイナリのパフォーマンスに大きな影響を与えずにセキュリティを強化するためのよりシンプルなアプローチを取ります。Partial RELROは**.gotを読み取り専用にします(GOTセクションの非PLT部分)**。セクションの残りの部分(.got.pltなど)はまだ書き込み可能であり、したがって攻撃の対象となることに注意してください。これは**任意の書き込み**の脆弱性から**GOT**が悪用されるのを防ぐものではありません。 +### Partial RELRO -注:デフォルトでは、GCCはPartial RELROでバイナリをコンパイルします。 +* フラグ`-Wl,-z,relro`(または`ld`を直接呼び出すときは単に`-z relro`)で生成されます。 +* **GOT**の**非PLT**部分(データ再配置に使用される部分)のみが読み取り専用セグメントに配置されます。実行時に変更が必要なセクション、特に**遅延バインディング**をサポートする**.got.plt**は書き込み可能なままです。 +* そのため、**任意の書き込み**プリミティブはPLTエントリを上書きすることによって実行フローをリダイレクトすることができます(または**ret2dlresolve**を実行することによって)。 +* パフォーマンスへの影響は無視できるため、**ほぼすべてのディストリビューションは、少なくともPartial RELROを持つパッケージを何年も出荷しています(2016年以降、GCC/Binutilsのデフォルトです)**。 -### **Full RELRO** +### Full RELRO -**Full RELRO**は、**GOT全体(.gotと.got.pltの両方)および.fini_array**セクションを完全に**読み取り専用**にすることで保護を強化します。バイナリが起動すると、すべての関数アドレスが解決され、GOTにロードされ、その後、GOTは読み取り専用としてマークされ、実行時にそれに対する変更を効果的に防ぎます。 +* **両方の**フラグ`-Wl,-z,relro,-z,now`(別名`-z relro -z now`)で生成されます。`-z now`は動的ローダーに**すべての**シンボルを事前に解決させる(イager binding)ため、**.got.plt**は再度書き込まれる必要がなく、安全に読み取り専用としてマッピングできます。 +* **GOT**、**.got.plt**、**.fini_array**、**.init_array**、**.preinit_array**およびいくつかの追加の内部glibcテーブル全体が読み取り専用の`PT_GNU_RELRO`セグメント内に配置されます。 +* 起動時のオーバーヘッドが測定可能に追加されます(すべての動的再配置が起動時に処理される)が、**実行時のオーバーヘッドはありません**。 -ただし、Full RELROのトレードオフはパフォーマンスと起動時間にあります。GOTを読み取り専用としてマークする前に、すべての動的シンボルを起動時に解決する必要があるため、**Full RELROが有効なバイナリは読み込み時間が長くなる可能性があります**。この追加の起動オーバーヘッドが、すべてのバイナリでFull RELROがデフォルトで有効になっていない理由です。 +2023年以降、いくつかの主流のディストリビューションは、**デフォルトでFull RELROを使用してシステムツールチェーン**(およびほとんどのパッケージ)をコンパイルするように切り替えました – 例:**Debian 12 “bookworm” (dpkg-buildflags 13.0.0)**および**Fedora 35+**。したがって、ペンテスターとしては、**すべてのGOTエントリが読み取り専用である**バイナリに遭遇することを期待すべきです。 -バイナリでFull RELROが**有効**かどうかを確認することができます: +--- + +## バイナリのRELROステータスを確認する方法 ```bash -readelf -l /proc/ID_PROC/exe | grep BIND_NOW +$ checksec --file ./vuln +[*] '/tmp/vuln' +Arch: amd64-64-little +RELRO: Full +Stack: Canary found +NX: NX enabled +PIE: No PIE (0x400000) +``` +`checksec`([pwntools](https://github.com/pwncollege/pwntools)の一部であり、多くのディストリビューションに含まれています)は、`ELF` ヘッダーを解析し、保護レベルを表示します。`checksec`を使用できない場合は、`readelf`に依存してください: +```bash +# Partial RELRO → PT_GNU_RELRO is present but BIND_NOW is *absent* +$ readelf -l ./vuln | grep -E "GNU_RELRO|BIND_NOW" +GNU_RELRO 0x0000000000600e20 0x0000000000600e20 ``` -## バイパス -フル RELRO が有効な場合、バイパスする唯一の方法は、任意の実行を得るために GOT テーブルに書き込む必要のない別の方法を見つけることです。 +```bash +# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag +$ readelf -d ./vuln | grep BIND_NOW +0x0000000000000010 (FLAGS) FLAGS: BIND_NOW +``` +バイナリが実行中の場合(例:set-uid root ヘルパー)、**`/proc/$PID/exe`** を介して実行ファイルを検査することができます: +```bash +readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO +``` +--- -**LIBC の GOT は通常部分的な RELRO であるため、任意の書き込みで変更可能です。** 詳細は [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**。** +## 自分のコードをコンパイルする際のRELROの有効化 +```bash +# GCC example – create a PIE with Full RELRO and other common hardenings +$ gcc -fPIE -pie -z relro -z now -Wl,--as-needed -D_FORTIFY_SOURCE=2 main.c -o secure +``` +`-z relro -z now` は **GCC/clang**(`-Wl,` の後に渡される)と **ld** の両方で機能します。 **CMake 3.18+** を使用する場合、組み込みのプリセットを使用してフル RELRO を要求できます: +```cmake +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # LTO +set(CMAKE_ENABLE_EXPORTS OFF) +set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro,-z,now") +``` +--- + +## バイパステクニック + +| RELROレベル | 一般的なプリミティブ | 可能なエクスプロイト技術 | +|-------------|-------------------|----------------------------------| +| なし / 部分的 | 任意の書き込み | 1. **.got.plt** エントリを上書きして実行をピボットする。
2. **ret2dlresolve** – 書き込み可能なセグメントに偽の `Elf64_Rela` と `Elf64_Sym` を作成し、`_dl_runtime_resolve` を呼び出す。
3. **.fini_array** / **atexit()** リストの関数ポインタを上書きする。 | +| フル | GOTは読み取り専用 | 1. **他の書き込み可能なコードポインタ**(C++ vtables、`__malloc_hook` < glibc 2.34、`__free_hook`、カスタム `.data` セクションのコールバック、JITページ)を探す。
2. *相対読み取り* プリミティブを悪用してlibcをリークし、**SROP/ROPをlibcに対して行う**。
3. **DT_RPATH**/`LD_PRELOAD`(環境が攻撃者制御下にある場合)または **`ld_audit`** を介して悪意のある共有オブジェクトを注入する。
4. **フォーマット文字列**または部分的ポインタ上書きを利用してGOTに触れずに制御フローを逸らす。 | + +> 💡 フルRELROであっても、**ロードされた共有ライブラリ(例:libc自体)のGOT**は**部分的RELRO**である。なぜなら、これらのオブジェクトはローダーが再配置を適用する際にすでにマッピングされているからである。もし他の共有オブジェクトのページをターゲットにできる**任意の書き込み**プリミティブを得た場合、libcのGOTエントリや`__rtld_global`スタックを上書きすることで実行をピボットすることができる。この技術は現代のCTFチャレンジで定期的に悪用されている。 + +### 実世界のバイパス例 (2024 CTF – *pwn.college “enlightened”*) + +このチャレンジはフルRELROで提供された。エクスプロイトは**オフバイワン**を使用してヒープチャンクのサイズを破損させ、`tcache poisoning`でlibcをリークし、最終的に`__free_hook`(RELROセグメントの外)をワンガジェットで上書きしてコード実行を得た。GOTの書き込みは必要なかった。 + +--- + +## 最近の研究と脆弱性 (2022-2025) + +* **glibc 2.40が`__malloc_hook` / `__free_hook`を非推奨に (2025)** – これらのシンボルを悪用したほとんどの現代のヒープエクスプロイトは、**`rtld_global._dl_load_jump`**やC++例外テーブルなどの代替ベクトルに移行する必要がある。フックはRELROの**外部**に存在するため、その削除はフルRELROバイパスの難易度を上げる。 +* **Binutils 2.41 “max-page-size” 修正 (2024)** – バグにより、RELROセグメントの最後の数バイトが一部のARM64ビルドで書き込み可能なデータとページを共有することができ、`mprotect`の後に書き込むことができる小さな**RELROギャップ**が残っていた。アップストリームは現在、`PT_GNU_RELRO`をページ境界に整列させ、そのエッジケースを排除している。 + +--- + +## 参考文献 + +* Binutilsドキュメント – *`-z relro`, `-z now` および `PT_GNU_RELRO`* +* *“RELRO – フル、部分的およびバイパステクニック”* – blog post @ wolfslittlered 2023 {{#include ../../banners/hacktricks-training.md}}