Return-oriented programming
Return-oriented programming(ROP)は、実行保護やコード署名などのセキュリティ防御の機構が存在するマシンで任意コードの実行を可能にするセキュリティエクスプロイトである。
この手法では、攻撃者はコールスタックを操作してプログラムの制御フローを乗っ取り、マシンのメモリ上に存在する「ガジェット」と呼ばれる命令群を実行する。各ガジェットは通常return命令で終わり、既存のプログラムまたは共有ライブラリのコードのサブルーチンに存在する。これらのガジェットを組み合わせることで、単純な攻撃を無効化する防御機構が存在するマシンで攻撃者が任意の命令を実行できるようになる。
Return-oriented programmingは、スタックスマッシング攻撃の発展形である。一般にこの種類の攻撃は、攻撃者がバッファオーバーランなどのプログラムのバグを利用してコールスタックを操作することで可能になる。バッファオーバーランは、ユーザから受け取ったデータをメモリに保存する際に境界検査を正常に行わない関数が、正しく保存できる量よりも多くのデータを受け取ることで発生する。大きすぎるデータがスタックに書き込まれると、関数内の変数に割り当てられている空間をオーバーフローし、returnアドレスを上書きしてしまう場合がある。このアドレスはその後関数が制御フローを呼び出し元に戻すときに参照されるが、これが上書きされた場合、制御フローは新しいreturnアドレスが指定した位置に分岐してしまう。
一般的なバッファオーバーフロー攻撃では、攻撃者は単純に攻撃用のコード(ペイロード)をスタックに書き込み、returnアドレスをこれらの書き込んだ命令の位置に上書きする。1990年代まで、当時一般的に流通していたオペレーティングシステムにはこういった攻撃に対する防御機構が一切なかった。例えば、Microsoft Windowsには、2004年までバッファーオーバーランへの防御機構が存在しなかった。しかし最終的には、オペレーティングシステムは書き換え可能なメモリを実行不可能にする実行保護と呼ばれる仕組みを導入し、バッファオーバーフローバグの利用への対策を実装するようになった。実行保護が有効になっていると、マシンはユーザが書き込み可能な領域のメモリにある命令の実行を拒否するようになるため、攻撃者はスタックにペイロードを書き込み、returnアドレス上書きでペイロードにjumpすることができなくなる。この後、これらの保護を強化するため、ハードウェアサポートも一般に流通するようになった。
データ実行保護により、バッファーのメモリ部分は実行不可能とマークされているため、攻撃者はバッファーに書き込まれた命令を直接実行することができなくなる。この保護を回避するため、Return-oriented programmingは悪意のある命令を注入するのではなく、returnアドレスを書き換えることで、実行可能なメモリ領域にすでに存在する「ガジェット」と呼ばれる命令群を利用する。攻撃者は悪意のあるコードを直接実行するのではなく、returnアドレスを書き換えることで実行可能とマークされている命令群を組み合わせて実行するため、一般的なデータ実行保護はこのような攻撃に対しては無力である。
現在流通しているデータ実行保護は、前述した従来のバッファオーバーフロー脆弱性をほぼ不可能にし、攻撃者が実行可能とマークされてすでにメモリ上に存在しているプログラムおよび共有ライブラリのコードしか実行できないようにする。libcなどの共有ライブラリは、システムコールを実行するためのサブルーチンやその他の攻撃者にとって有用な関数を含んでいるため、攻撃を組み立てるためのコードを探す上で重要となる。
return-to-library攻撃では、攻撃者は前述の手法でバッファーオーバーラン脆弱性を悪用して制御フローを乗っ取る。ただし、攻撃者はペイロードをスタックに書き込む代わりに、利用可能なライブラリの関数を選んでその最初のアドレスにreturnアドレスを上書きする。残りのスタック位置は、攻撃者が望む動作を実行するため、正しいパラメータを関数に渡すよう呼び出し規約に従って上書きされる。この手法はSolar Designerが1997年に初めて提唱し、後に無限の関数呼び出しのチェーンを可能にするよう拡張された。
x64プロセッサの普及に伴い、バッファオーバーラン脆弱性を利用したコールスタック操作だけでは任意の引数のライブラリ関数呼び出しを組み立てることができなくなり、関数への最初の引数をスタックではなくレジスタに渡す新しいサブルーチン呼び出し規約に対応する必要が生じてきた。また、共有ライブラリの開発元も、システムコールのラッパー関数など、攻撃者にとって特に有用なライブラリ関数を除去したり、制限したりし始めた。結果、return-into-library攻撃を成功させることは困難になった。
これに対抗して、関数全体ではなく、ライブラリ内の関数の一部を利用して、より単純な攻撃に対する防御を備えたマシン上でバッファオーバーランの脆弱性を突く攻撃の手法が現れた。この手法は、スタックから値をpopしてレジスタに入力する命令群を含む関数を探索することで、攻撃者が適切な値を正しいレジスタに入れ、新しい呼び出し規約の下で関数呼び出しを実行することを可能にする。攻撃の残りの部分は、通常のreturn-into-library攻撃と同様である。
Return-oriented programmingは、先述のborrowed code chunks手法をベースとし、これにループや条件分岐を含むチューリング完全性を付け加えたものである。つまり、Return-oriented programmingは攻撃者が危殆化したマシン上で実行できる完全な言語を提供する。Hovav Shachamが2007年にこの手法を公表し、C標準ライブラリにリンクし、悪用可能なバッファオーバーラン脆弱性を含むアプリケーションに対してReturn-oriented programmingを用いることであらゆるプログラミングの要素が再現可能であることを示した。
Return-oriented programmingは、その有用性や防御機構に対する耐性などの点で他の種類の攻撃よりも優れている。共有ライブラリからの危険な可能性のある関数の除去を含め、前述したいずれのエクスプロイト対策もReturn-oriented programmingに対して有効ではない。
Return-oriented programmingは様々なアーキテクチャ上で実行可能であるが、Shachamの論文および多くのフォローアップ研究はIntel x86アーキテクチャを対象としている。x86アーキテクチャは可変長のCISC命令セットであり、x86におけるReturn-oriented programmingでは、この命令セットが非常に密集している、つまり、あらゆるランダムなバイド列が何らかの正当なx86命令群として解釈される可能性が高いという事実を利用する。
つまり、制御フローを変更するオペコード(特にreturn命令 0xC3)を探索し、その前に存在する有用な命令群を探索するという手法が可能になる。これらの命令ガジェット群は、バッファオーバーランエクスプロイト経由でreturnアドレスを最初のガジェットの先頭のアドレスに上書きし、以降のガジェットのアドレスをスタックに書き込むことでチェーンできる。最初のガジェットの実行が終了すると、return命令が実行され、2番目のガジェットのアドレスがスタックからpopされ、そのアドレスにjumpする。そのガジェットの実行が終了すると、同様にチェーンは3番目のガジェットに移り、これが繰り返される。小さな命令群をチェーンすることで、攻撃者は既存のライブラリのコードから任意のプログラムの挙動を得ることができる。Shachamは、あらゆる十分に大きいコード(C標準ライブラリを含む)において、チューリング完全性を得るのに十分な量のガジェットが存在すると主張している。
ガジェットの探索とバイナリに対する攻撃の組み立てを支援する自動ツールも開発されている。例えば、ROPgadgetは、バイナリから有用な可能性のあるガジェットを探索し、shellをspawnして攻撃者からの任意の命令を受け取る攻撃用ペイロードの組み立てを試みる。
アドレス空間配置のランダム化(ASLR)にも脆弱性は存在する。Shacamらによると、32ビットのアーキテクチャにおけるASLRはアドレスのランダム化に利用可能なビットの数によって制限される。32ビットのうち、16ビットのみがランダム化に利用可能であり、16ビット分のランダム化は数分でブルートフォース攻撃により突破可能である。なお、64ビットのアーキテクチャはより堅固で、64ビット中40ビットがランダム化に利用可能である。40ビット分のランダム化に対するブルートフォース攻撃は可能ではあるが、気づかれない可能性は低いDecember 2022。ブルートフォース攻撃に加えて、脱乱択化の手法も存在する。
完全なランダム化が行われている環境においても、何らかのメモリ内容のリークがあれば、ランタイムで共有ライブラリなどの基底アドレスを計算することが可能になる場合がある。
Checkowayらによると、x86およびARMアーキテクチャでreturn命令(x86では0xC3)を使わずにReturn-oriented programmingを実行することが可能である。Checkowayらの手法では、マシンのメモリにすでに存在する命令群から、return命令と同様の動作をするものを選択する。return命令には2つの動作がある。まず、スタックの最上位にある4バイトの値を探索して命令ポインタをその値に設定し、次にスタックポインタの値を4増やす(pop命令と同様)。x86アーキテクチャでは、jump命令およびpop命令の組み合わせによりreturn命令を実行することができる。ARMでは、load命令およびbranch命令の組み合わせにより同様のことが行える。
この新しい手法はreturn命令を使わないため、防御がより難しくなる。ただし、防御プログラムがreturn命令だけでなくjump命令も探索する場合、この攻撃は検出されうる。
G-Freeは、Kaan Onarlioglu、Leyla Bilge、Andrea Lanzi、Davide Balzarotti、Engin Kirdaが考案した手法であり、あらゆる形態のReturn-oriented programmingに対する実用的な防御法である。G-Freeは、実行可能バイナリ内の分岐命令(攻撃者が制御フローを変更するのに利用可能なreturn命令やcall命令)をすべて除去し、分岐命令が攻撃者に利用されるのを防ぐ。G-Freeがreturnアドレスを保護する仕組みは、StackGuardのRandom XORカナリアと類似している。また、バリデーションブロックを追加して関数呼び出しの真正性を確認し、予期される値が見つからない場合はアプリケーションをクラッシュさせる。
Return-oriented programmingをベースにした攻撃を防ぐため、多くの手法が提案されてきた。それらの多くは、プログラムおよびライブラリコードの位置をランダム化し、ガジェットとして利用可能な命令の位置を攻撃者が正確に予測できないようにすることで、Return-oriented programmingのチェーンを作成できないようにするものである。この手法の実装として一般的なアドレス空間配置のランダム化(ASLR)は、プログラムの読み込み時に毎回共有ライブラリをメモリ上の別の場所に読み込む。この手法は最近の主要なオペレーティングシステムの多くで実装されているが、ASLRはサイバー攻撃やその他のメモリ上の既知のライブラリの関数の位置を特定する手法に対して脆弱である。もし攻撃者が既知の命令の位置を1つ見つけるのに成功すると、その他すべての命令の位置も推測でき、Return-oriented programming攻撃を組み立てることができるからである。
さらに一歩進んで、ライブラリの位置だけでなくあらゆる命令およびその他のプログラムの状態(レジスタやスタックオブジェクト)の位置を変更することもできる。ただし、ランタイムでランダム化された命令を元に戻すため、software dynamic translatorなどの高度なランタイムサポートが必要であり、ガジェットの発見および利用を難しくする一方で、大きなオーバーヘッドが伴う。
kBouncerの手法では、オペレーティングシステムを修正し、return命令が実際にcall命令の直後の位置に制御フローを戻しているかどうかを確認する。これにより、ガジェットのチェーン化は防がれるが、大きなパフォーマンス上の負担があるほかJune 2015、return命令ではなくjump命令やその他の制御フローを変更する命令を書き換えるJump-oriented programmingに対しては有効ではない。
Structured Exception Handler Overwrite Protection(SEHOP)は、システムを一般的なオーバーフロー攻撃(特に構造化例外処理に対する攻撃)から保護するWindowsの機能である。