Shuffler: Fast and Deployable Continuous Code Re-Randomization の紹介
はじめに
情報セキュリティ系論文紹介 Advent Calendar 2016 - Adventarの23日目の記事として、Shuffler: Fast and Deployable Continuous Code Re-Randomization | USENIXの紹介をやっていく。今日は12月23日であり、明日はクリスマス・イブである。
これはJIT-ROPに代表される、memory disclosure vulnerabilityを利用した攻撃に対する防衛機構の話である。 JIT-ROPに関してはx64でDynamic ROPによるASLR+DEP+RELRO回避をやってみる - ももいろテクノロジーを見てほしい。
- 論文: https://www.usenix.org/system/files/conference/osdi16/osdi16-williams-king.pdf
- スライド: https://www.usenix.org/sites/default/files/conference/protected-files/osdi16_slides_williams-king.pdf
この記事で扱う図はすべて上の2つのリンクから借用した。
概要
現代のシステムでは、NX bitやASLRによりコードインジェクション攻撃は実質的に排除されているが、今日のプログラムはROPのようなコードを再利用する攻撃に対して脆弱である。また、Anti-ROP: A Moving Target Defense によると、ソフトウェアに対する攻撃の90%でROPが用いられている。
特に悪意のあるものはJust-In-Time ROP(JIT-ROP)であり、攻撃者はmemory disclosure vulnerabilityを利用して実行時にgadgetを発見する。これに対する対抗策として、攻撃者がgadgetを見つけるよりはやく、コードを再ランダム化しようというアイデアのもと、Shufflerは開発された。Shufflerは、コード位置を20-50ミリ秒単位で連続的に再ランダム化し、攻撃者に時間の制限をかけることで対処する。 この制限はネットワーク経由での攻撃に特に有用で、攻撃者マシンから数十ミリ秒離れた場所にあるサーバープログラムに対して、攻撃を最後まで行うことは困難となる。
また、本研究の貢献は以下の4点だと述べられている。
Deployability: Shufflerによる再ランダム化は、ソースコード、コンパイラ、リンカを変更とせず、ローダーへのミニマムな変更だけで行うことができる。
Speed: レイテンシ、オーバーヘッドが低い、非同期の再ランダム化によって、disclosure-based attackに対しミリ秒のオーダーのリアルタイムデッドラインを導入する。
Egalitarianism: 我々は防衛機構をどのようにセルフホストし、trusted computing base (TCB)の拡大を回避するかについて説明する。 Trusted computing base - Wikipedia
Augmented binary analysis: コンパイラから得られる情報(シンボルや再配置)を活用することで、バイナリ上で完全で正確な分析が可能であることを示す。
Implementation
Shufflerは x86-64 Linuxのユーザー空間で動作し、バイナリが依存するすべての共有ライブラリと、バイナリ自体をシャッフルする。 シャッフルするプロセスは、シャッフルされるプログラムのスレッドの実行をさまたげることなく、スレッド内で非同期に実行される。
以下の図は、シャッフルされたコードのスナップショットである。 code pointerはcode pointer tableを介して指され、リターンアドレスはXORで暗号化され、スタックに積まれる。
Shufflerはシャッフルする各期間で、コードの新しいコピーを作成し、code pointer tableを更新、すべてのスレッド(自身を含む)にシグナルを送る。 各スレッドはそのスタックをunwindsし修正する。
Shufflerはすべてのスレッドが unwindingを終えるまでバリアーで待機し、以前のコピーしたコードを消去する。Shufflerの実装は、共有ライブラリ、複数のスレッド、フォーク(それぞれの子は独自のシャッフラースレッドを取得する)、{set、long} jmp、システムコールの再エントリ、シグナルなど、多くのシステムレベルの機能をサポートしている。
Transformations to Support Shuffling
元のコードの前後に以下のように命令を挿入することで、return addressをxorしている。
func: mov %fs:0x28,%r11 xor %r11,(%rsp) ; original code mov %fs:0x28,%r11 xor %r11,(%rsp) ret
関数を呼び出す際のアドレス解決は以下のように行われる。
call命令はそのとき以下のように書き換えられる。
callq *%rax => callq *%gs:(%rax)
また上のときのraxは以下のように書き換えられ、データセグメントのindexとして扱われる。
mov $0x40054d, %rax => mov $0x20, %rax
Bootstrapping
2つのライブラリ(stage1, stage2)を使って、シャッフルされたコードを起動するので、 システムは現在実行中のモジュールのコードポインタを上書きしません。 これらのライブラリは、LD_PRELOADを使用してターゲットにinjectionされる。 ローダー機能を再実装するのではなく、システムローダーに任せて有効なプロセスを作成し、 プログラム(またはそのコンストラクター)が実行を開始する前に引き継ぐ。
stage1のコンストラクタは、リンカメカニズム-z initfirst
を介して他のもののより早く呼び出される。
次に、ローダ自体にブレークポイントを設定することによって、stage1では、他のすべてのコンストラクタがシャッフルされたコードで実行されるようになる。
最後に呼び出されるコンストラクタ(LD_PRELOADの副作用)はstage2自身のコンストラクタである。
stage2は専用のShufflerスレッドを作成し、他のすべてのコードの元のコピーを消去し、シャッフルされたELFエントリポイントで実行を再開する。
評価
ROP、direct JIT-ROP、indirect JIT-ROP、およびBlind ROPを含む、コードを再利用するすべての既知の形態の攻撃に対し、Shufflerが防御できることを示した。 50 msごとにシャッフルするとSPEC CPUのオーバーヘッドは14.9%となり、Nginxなどの現実のアプリケーションの上でShufflerを実行できることを確認した。シャッフルされたNginxは12コアで最大24のワーカープロセスをスケーリングできた。
また、CVE-2013-2028によってBlind-ROPが可能なNginx 1.4.0を用いて、攻撃が完了するまでに7分かかることを測定した。 このBlind-ROPをシャッフルされたNginxで試したとき、攻撃者はPLT、stack canaryを見つけられなかった。 親プロセスと子プロセスが独立してランダム化されるため、攻撃は成立しなかった。
感想
gadgetが発見される前にコードを再ランダム化するというアイデアだけではなく、再ランダム化することに対する攻撃手法も述べられており、面白い論文だった。是非コードを読みたい。