JIT-ROP緩和手法Heisenbyteについてまとめてみる

この記事は「情報セキュリティ系論文紹介 Advent Calendar 2015」24日目の記事です。

JIT-ROP緩和手法として発表されたHeisenbyteについてまとめてみる。

tl;dr

実行可能領域の読み取りが行われた直後にその箇所を適当なバイトで上書きして破壊することで、読み取られた命令列が攻撃者に悪用されることを防ぐ。 また、あらかじめ静的解析を用いた実行ファイルの書き換えにより実行可能セクションからデータ部分を分離しておくことで、パフォーマンスへの影響を抑える。

任意コード実行これまでのあらすじ

  • シェルコード送り込んでジャンプさせれば任意コード実行できるよ → NX/DEPによりデータの実行を禁止
  • 実行可能領域から適当な命令列(ROP gadget)かき集めたらシェルコード実行可能にできるよ → Code randomizationで命令列をランダム化
  • 攻撃コード中で実行可能領域読み取ればCode randomizationされた後でもROP gadget収集できるよ

JIT-ROP(Just-In-Time ROP)は適当にリークさせたアドレスをもとに実行可能領域を読み取り、その中に含まれるROP gadgetを用いてROP chainを構築する攻撃手法である。 論文では、この一連の流れを指してMemory disclosure attackと呼んでいる。 具体的な攻撃例については「x64でDynamic ROPによるASLR+DEP+RELRO回避をやってみる」を参照。

既存のMemory disclosure attack防御手法

実行可能領域を指すアドレスのリークとその周囲の読み取りにより、攻撃者はROP gadgetを収集しon-the-flyでROP chainを構築することが可能となる。 これに対し、以下のような防御手法が提案されている。

  • Oxymoron(USENIX Security 2014)は実行可能領域を指すポインタを隠すことでアドレスのリークを防ぐが、アドレスのリークはスタックやヒープなどに置かれたデータからも起こりうる。
  • XnR(ACM CCS 2014)は実行可能領域を実行不可にした上でpage fault handlerによりコード部分の不正な読み取りについて判定を行うが、間接的にコードが読み取られるようなケースについてうまく働くかははっきりしない。
  • HideM(CODASPY 2015)はAMDプロセッサのspilt-TLBを利用することで透過的に実行可能領域のdereferenceを防ぐが、split-TLBを実行可能セクションからデータ部分を除くことにのみ使っているためデータ部分自体は残る。
  • Readactor(IEEE S&P 2015)はコードとデータの分離をコンパイラを利用したテクニックにより行っており、実行のみが可能なメモリ領域の実現にはハードウェア仮想化によるサポートを用いている。
  • Isomeron(NDSS 2015)はJITコードも考慮した実行時のコントロールフローランダム化により、読み取られたROP gadgetの使用を確率的に妨害する。

これらの防御手法は、対応する攻撃ステップに合わせて三つに分類することができる(下図)。 Heisenbyteはこの分類のうち、読み取られたROP gadgetの使用を防ぐ手法に該当する。

f:id:inaz2:20151223181020p:plain

設計コンセプト

Heisenbyteのゴールはソースコードのない実行プログラム(close-sourced COTS binaries)についてMemory disclosure attackを防ぐことである。 そのため、実行プログラムから静的なデータ領域を特定することについても触れられている。

Destructive Code Reads

f:id:inaz2:20151223185619p:plain

上の図において、物理メモリ上のexecutable-only pageに対してreadが行われたら(3)、その箇所を破壊(4)した後、複製しておいたページからreadを行う(6)。 これにより、攻撃者がROP gadgetとして使用したとき、意図した命令は実行されなくなる。

Statically Separating Code and Data

多くのWindowsプログラムでは、Import Address Tableや.rdataなどのread-onlyなデータも実行可能領域である.textセクションに置かれている。 この状態で扱おうとすると大きなオーバーヘッドが生じるため、静的解析を用いてこれらをrelocationする。

実装

下図のように、システムの実装はバイナリ書き換えを行うOffline preparation、execute-only pageを特定するInitialization mode、EPT例外に応じてDestructive Code Readsを行うActive monitoring modeの三つに分けられる。

f:id:inaz2:20151223192045p:plain

Offline Static Binary Rewriting

IDA Pythonで解析して、実行可能領域中のデータ部分を新たに追加したnon-executableなセクションにrelocationする。 システムファイルの置き換えについてはWindows Resource Protection (WRP)による保護が存在するが、管理者権限を駆使して対処する。 ntdll.dllについてはbootloaderのboot-time intergrityを無効にすることで対処する。

Heisenbyte Core Monitoring Components

f:id:inaz2:20151223194643p:plain

execute-only pageの実装にはReadactorと同様、ハードウェア仮想化支援機能であるIntel Extended Page Tables (EPT)を用いる。 プロセスのロードおよびJITバッファの生成に合わせて実行可能領域を特定し、EPTでexecute-onlyなページとして扱う。 そしてこの領域に対する読み込みの発生を#EPT violation handlerで処理することにより、Destructive Code Readsを行う。 ここで、読み込み時に参照する複製領域への切り替えはEPTエントリの書き換えによって行われる。

Intel EPTは基本的に仮想マシン上のOSに対して用いられるものであるが、著者らはHeisenbyteを非仮想化環境でも使えるようWindowsドライバとして実装している。

性能評価

Heisenbyte上でJIT-ROPを実行したところ、実行可能領域の読み込みにより収集したstack pivot gadgetが実行されるタイミングで攻撃は停止した。 また、このタイミングで自動起動させたWindbgは破壊前のバイトを正しく表示できており、データに対する読み込みとして正しく処理できていることが確認できた。

実行時のオーバーヘッドは平均して18.3%、メモリ使用量のオーバーヘッドはおおむね0.8%である。

所感

パフォーマンスへの影響を抑えるために実行ファイルを書き換えている点はいまひとつだが、Intel EPTを用いて実行時に参照する領域と読み込み時に参照する領域を切り替えている点が興味深い。 また、一度読み込んだ領域を破壊するという発想が、実行可能領域の読み込みを許容しつつ実行を抑制するという要求をうまく満たしていておもしろい。