Shikata Ga Nai

Private? There is no such things.

第49回:ROP(リターンオリエンテッドプログラミング)超入門

Hello there, ('ω')ノ

🧠 そもそもROPとは何か?

ROP(Return-Oriented Programming)とは、脆弱なプログラムに対して任意の命令列を実行させる手法です。 といっても「攻撃コードを注入して実行する」わけではありません。

ポイントは:

🔁 既存の命令(コード)を“つなぎ合わせて”攻撃を成立させる

つまり、「積み木のように使える命令の断片(ガジェット)」を、メモリの中から探し出して組み立てるのです。


🔧 なぜROPが必要なのか?

昔の攻撃では、メモリ上にシェルコード(攻撃コード)を置いてジャンプさせればそれで完了でした。

ところが、最近のOSやコンパイラはセキュリティ対策を施しています:

対策 内容
DEP(実行防止領域) スタックに置かれたコードを実行できないようにする
ASLR(アドレス空間ランダム化) ライブラリやコードの場所を毎回バラバラに
Stack Canary スタックの改ざんを検知してクラッシュさせる

→ このため、「コードを注入して実行する」タイプの攻撃は難しくなったのです。

そこでROPの出番です。 ROPは、既に存在する命令を流用して実行するため、DEPやASLRをある程度回避できます。


📦 ROPのしくみ(ざっくり流れ)

① バッファオーバーフローなどでリターンアドレス(戻り先)を書き換える

→ 関数が終了したときに、攻撃者が指定した場所にジャンプ

② そのジャンプ先には、ちょうどいい命令がある(例:pop r0; ret;

→ このような短い命令列を「ガジェット(gadget)」と呼びます

③ 次のリターンアドレスも細工しておき、連続でガジェットを実行

→ 結果として、意図的にレジスタやメモリを操作し、最終的に任意の関数を呼び出せる


🧩 ガジェットって何?

ガジェットとは、以下のような短い命令とリターン命令のセットです:

pop {r0, pc}       ; レジスタr0に値を入れ、戻り先(pc)を指定

こういった断片をたくさん組み合わせて、最終的に system("/bin/sh") を呼び出す… そんなことも理論的には可能です。

ガジェットはバイナリ内に無数に存在しており、攻撃者はそれを自動で探し出して使います。


🔍 実践:ROPを診断で意識するタイミング

以下のような条件が揃っていたら、ROPのリスクを疑うべきです:

条件 説明
ネイティブコード(.soファイル)を使っている C/C++のコードにはメモリ操作のミスが起きやすい
strcpy, sprintf など危険関数を使っている バッファオーバーフローの発生源になる
Stack Canary や ASLR が無効 ROP実行のハードルが下がる
DEP だけでは防ぎきれない ROPは「注入」ではなく「再利用」なのでバイパスしやすい

🛠️ ROP診断の基本ステップ

  1. .so ファイルを Ghidra や ROPgadget で分析

    • ROPgadgetを使えば、ガジェットのリストアップが可能
   ROPgadget --binary libnative.so
  1. バッファオーバーフローの発生箇所を探す

    • MobSFやFridaでユーザー入力 → ネイティブ関数へ流れているか確認
  2. 動的診断(Fuzzingなど)で異常動作やクラッシュが発生するか調査

  3. クラッシュ時のレジスタ値・リターンアドレスを調査

    • ここにガジェットを積み込めるかが判断基準になります

🧠 注意:ROPは高度な攻撃手法

ROPは、実際に使いこなすには以下が必要です:

  • アセンブリ(ARM, x86)の基礎知識
  • メモリ構造・スタックの理解
  • ガジェット探索とPayload構築のスキル

ですが、診断者が**「このアプリはROPの土壌があるかも」と気づくだけでも意味があります。 それは、「修正の優先度が高い危険な脆弱性がある」**というシグナルになるのです。


✅ まとめ

  • ROPは、「既存のコード断片をつなぎ合わせて任意の処理を実行する」攻撃手法
  • バッファオーバーフロー + 実行可能なスタック + ガジェット群 があれば成立する
  • Stack Canary や ASLR だけでは不十分なこともある
  • 診断では、ROPが成立する「材料」が揃っているかを静的・動的に確認するのがポイント

Best regards, (^^ゞ