Relro

Reading time: 11 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

Relro

RELRORelocation Read-Onlyの略で、リンカー(ld)によって実装された緩和策であり、ELFのデータセグメントのサブセットをすべての再配置が適用された後に読み取り専用にするものです。目的は、攻撃者がプログラム実行中に参照される**GOT(Global Offset Table)**やその他の再配置関連テーブルのエントリを上書きするのを防ぐことです(例:__fini_array)。

現代のリンカーは、GOT(およびいくつかの他のセクション)を**.bss前に配置し、最も重要なことに、動的ローダーが再配置を適用した後にR–Xとして再マッピングされる専用のPT_GNU_RELROセグメントを作成することによってRELROを実装します。その結果、.bss**内の典型的なバッファオーバーフローはGOTに到達できなくなり、任意の書き込みプリミティブを使用してRELRO保護ページ内の関数ポインタを上書きすることはできません。

リンカーが出力できる保護の2つのレベルがあります:

Partial RELRO

  • フラグ-Wl,-z,relro(またはldを直接呼び出すときは単に-z relro)で生成されます。
  • GOT非PLT部分(データ再配置に使用される部分)のみが読み取り専用セグメントに配置されます。実行時に変更が必要なセクション、特に遅延バインディングをサポートする**.got.plt**は書き込み可能なままです。
  • そのため、任意の書き込みプリミティブはPLTエントリを上書きすることによって実行フローをリダイレクトすることができます(またはret2dlresolveを実行することによって)。
  • パフォーマンスへの影響は無視できるため、ほぼすべてのディストリビューションは、少なくともPartial RELROを持つパッケージを何年も出荷しています(2016年以降、GCC/Binutilsのデフォルトです)

Full RELRO

  • 両方のフラグ-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セグメント内に配置されます。
  • 起動時のオーバーヘッドが測定可能に追加されます(すべての動的再配置が起動時に処理される)が、実行時のオーバーヘッドはありません

2023年以降、いくつかの主流のディストリビューションは、デフォルトでFull RELROを使用してシステムツールチェーン(およびほとんどのパッケージ)をコンパイルするように切り替えました – 例:Debian 12 “bookworm” (dpkg-buildflags 13.0.0)およびFedora 35+。したがって、ペンテスターとしては、すべてのGOTエントリが読み取り専用であるバイナリに遭遇することを期待すべきです。


バイナリのRELROステータスを確認する方法

bash
$ checksec --file ./vuln
[*] '/tmp/vuln'
Arch:     amd64-64-little
RELRO:    Full
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

checksecpwntoolsの一部であり、多くのディストリビューションに含まれています)は、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
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

自分のコードをコンパイルする際の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 nowGCC/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_RelaElf64_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

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする