R 3.4.4 におけるスタックベースのバッファオーバーフロー。x86 では完全なエクスプロイトが可能だが、x64 ではプログラムの制約により、gadget 分析による RIP 制御のみ可能。同一の脆弱性が 2 つのアーキテクチャに存在し、異なるエクスプロイト経路をもたらす。
R 3.4.4 におけるスタックベースのバッファオーバーフロー。x86 では完全なエクスプロイトが可能だが、x64 ではプログラム上の制約によりガジェット分析での RIP 制御に留まる。同一の脆弱性が 2 つのアーキテクチャで異なるエクスプロイト経路を生む。
このリポジトリは、メモリ破壊エクスプロイトを教える際に使用する教材の一部です(本業に加え、私はさまざまなサイバーセキュリティコースで講師を務め、次世代のリバースエンジニアを育成しています)。
CVE-2019-25485 は、学生に同一の脆弱性を 2 つの異なるアーキテクチャで扱わせ、その違いを実体験させたい場合に使用する事例です。R 3.4.4 は x86 と x64 の両バージョンで提供されており、まったく同じオーバーフロー(同じ GUI フィールド、同じ入力ハンドラ、同じクラッシュ)が両方に存在します。両方のアーキテクチャについて、ここで個別の演習として文書化し、エクスプロイトを示します。
R 3.4.4 は統計計算アプリケーションであり、ネットワークサービスやブラウザではありません。オーバーフローはデスクトップ GUI フィールドを通じてトリガーされるため、攻撃対象領域は私が教える他のすべてのケースとはまったく異なります。このケースが教育上有用な理由:
R は統計計算およびグラフィックス環境で、Windows、macOS、Linux で利用可能です。脆弱性は GUI の環境設定ダイアログ、具体的には「メニューとメッセージの言語」フィールドにあり、ユーザー入力を長さを検証せずに固定サイズのスタックバッファにコピーします。
主な技術的詳細:
R 3.4.4 は「メニューとメッセージの言語」フィールドを処理する際、指定された文字列を固定サイズのスタックバッファに長さをチェックせずにコピーします。脆弱なロジックの簡略版は次のようになります:
char language_buffer[256];
strcpy(language_buffer, user_input);
十分に長い文字列を送信すると、コピーがバッファの終端を超えて書き込まれ、保存された戻りアドレスが上書きされるまでスタックが破損します。関数が戻るとき、CPU はスタックから攻撃者制御の値を RIP にロードし、そのアドレスへのジャンプを試みます。
x64 では、Windows はジャンプが発生する前に正規アドレス検証を強制します。0x4141414141414141 のような非正規値は、RIP がロードされる前に即座にアクセス違反を引き起こすため、クラッシュの様子は x86 とは異なります。つまり、RIP = 4141414141414141 という明確な表示はありません。オフセットは、RIP から直接ではなく、クラッシュ後のスタックから巡回パターンを読み取ることで見つける必要があります。
長い文字列を言語フィールドに貼り付けることでクラッシュを再現できます。認証は不要です。Python を使用したペイロード生成例:
import struct
payload = b'A' * 400
with open('payload.txt', 'wb') as f:
f.write(payload)
R 3.4.4 x64 を起動
編集 -> GUI 環境設定
payload.txt の内容を「メニューとメッセージの言語」に貼り付け
OK をクリック
このリポジトリの目的は、クラッシュを実証するだけでなく、両アーキテクチャにおける完全なエクスプロイトプロセスを順を追って説明し、x86 で機能すること、x64 で破綻すること、そしてさらに重要なことに、その理由を文書化することです。
メインの README をクリーンに保つため、詳細なエクスプロイトノート、スクリプト、デバッガの手順は、このリポジトリの Vulnerability 📂 フォルダ内に、x86 と x64 のサブフォルダに分けて配置しています。
そこでは、両アーキテクチャの完全なワークフローを見つけることができます:
x86 - 完全なエクスプロイト:
x64 - RIP 制御とエクスプロイト分析: