CVE-2010-0232の脆弱性を調べてみる
2010 年 1 月に Windows カーネルの権限昇格の脆弱性 CVE-2010-0232 が公開されました(セキュリティアドバイザリ 979682を参照)。発見者 Tavis Ormandy 氏が Full-Dsiclosure に投稿したアドバイザリ や 実証コード(Proof of Concept:PoC) を見てみると、この脆弱性が詳細に解説されていました。「これは学習用にぴったり!?」と思い、この脆弱性を色々と調べてみました。
なお、この日記を読んだからといって、この脆弱性をきちんと理解できるわけではありません。この脆弱性に興味がある方だけ読んでもらえばいいです(笑)。よりよい調査方法や間違い等があれば指摘ください。
実証コードを実行する
まずは論より証拠。実証コードを実行してみます。SecurityFocus から実証コードをダウンロードして展開します。展開されると、README や ADVISORY, 実証コードのソースファイルがありました。ビルド手順もきちんと記述されています。
// INSTRUCTIONS // C:\> nmake // C:\> vdmallowed.exe
nmake 等の開発ツールは Visual C++ に同梱されているようなので*1、Visual C++ 2008 Express Edition をインストールしました(インストール手順, Web インストーラ)。nmake 等の開発ツールにパスを通し、nmake を実行しました。ビルドが成功すると、vdmallowed.exe, vdmexploit.dll ができます。これを実行。あ、強制再起動。。。強制再起動がこの実証コードおよび動作環境(Windows XP SP3 の実機)の正しい挙動か確認するため、もう 1 度 vdmallowed.exe を実行してみます。やっぱり強制再起動しました。
試しに VMware Server 1.0.8 で動作している Windows XP SP3 を実行してみると、成功しました(下記画像を参照)。Users グループに所属する demo ユーザ(下記画像の青枠)で実証コードを実行すると、新たにコマンドプロンプトが立ち上がります。新しいコマンドプロンプトの実行ユーザが SYSTEM ユーザであることが分かります(下記画像の赤枠)。
実証コードを読む
実証コードのソースファイル vdmallowed.c, vdmexploit.c を MSDN を参照しながら、どんな処理が実施されるか確認します。ざっと読んだ限りでは、以下のような処理を実施するようです。
- vdmallowed.c(実行ファイル vdmallowed.exe のソース)
- SYSTEM 権限を付与させるプロセス(cmd.exe)を生成
- NTDLL の Ki386BiosCallReturnAddress 関数の最初の16バイトが実証コードの CodeSignatures 配列のものと一致するか確認(CodeSignatures 配列は OS 毎に用意されている)
- NTVDM subsystem で起動する 16bit アプリケーション(debug.exe)のプロセスを生成
- 16bit アプリケーションプロセスに DLL Injection を実行
- Injected DLL の終了ステータスから Exploit の成否を確認
- vdmexploit.c(DLL Injection する vdmexploit.dll のソース)
- 例外エラーが発生したときに実行させる関数(FirstStage)等を設定して、NTDLL の NtVdmControl 関数を実行
- FirstStage 関数が実行され、cmd.exe の実行権限を SYSTEM 権限に書き換える
カーネルデバッグしてみる
せっかくの ring0 exploit なので、デバッガで挙動を追っかけてみます。デバッガは windbg を使用します。Windows カーネルを直接デバッグすることになるため、デバッグするには準備が必要になります。今回は VMware Server 1.0.8 で動作する Windows XP SP3 に名前付きパイプ経由で windbg で接続して、カーネルデバッグします(下記画像)*2。
事前準備:実証コードのシンボルファイルの作成
そのままカーネルデバッグすると、実証コード vdmallowed.exe, vdmexploit.dll の関数アドレスが分かりません。カーネルデバッグ時に実証コードである vdmallowed.exe, vdmexploit.dll の関数アドレスを調べるため、実証コードのシンボルファイルを作成します。
実証コードの Makefile の 11-15 行目を以下のように修正します。具体的には、cl.exe の引数に「/link /debug」オプションを指定します。このオプションを指定することで、vdmallowed.exe, vdmexploit.dll のシンボルファイル vdmallowed.pdb, vdmexploit.pdb が生成されます。これらの pdb ファイルを windbg の symbol path に設定しておくことで、vdmallowed.exe, vdmexploit.dll の関数アドレスが確認できます。
vdmallowed.exe: vdmallowed.obj cl /Fe$(@F) $(**) /link /debug vdmexploit.dll: vdmexploit.obj cl /Fe$(@F) /LD $(**) /link /debug
カーネルデバッグ
では、「Windowsダンプの極意」を片手に Let's kernel debug!
この脆弱性では #GP Trap Handler を意図的に発生させるようなので、例外トラップ nt!KiTrap0D が呼ばれる過程を調べてみます。
// 実証コードを実行する前に KiTrap0D 関数のアドレスにブレイクポイントを設定 kd> x nt!KiTrap0D 80541d70 nt!KiTrap0D = <no type information> kd> bp 80541d70 kd> bl 0 e 80541d70 0001 (0001) nt!KiTrap0D kd> g ... ここで vdmallowed.exe をコマンドプロンプトから実行する ... Breakpoint 0 hit nt!KiTrap0D: 80541d70 f744240c00000200 test dword ptr [esp+0Ch],20000h // スタックの情報を表示する。 // nt!KiTrap0D のリターンアドレス(RetAddr)が 80543de2 であることが分かる kd> kb ChildEBP RetAddr Args to Child f5e88ddc 80543de2 f83e8b85 820cf748 00000000 nt!KiTrap0D f5e88de0 f83e8b84 820cf748 00000000 0000027f nt!KiThreadStartup+0x16 f5e88de4 820cf748 00000000 0000027f 00000000 NDIS!___PchSym_+0xc WARNING: Frame IP not in any known module. Following frames may be wrong. f5e88de8 00000000 0000027f 00000000 00000000 0x820cf748 // メモリアドレス 80543dd0 から 80543de6 までをアセンブリコードで表示する // nt!KiTrap0D のリターンアドレスが 80543de2 であることから、nt!Trap0D が // 呼び出されたのは、1 つ前の 80543de0 の命令だと分かる kd> u 80543dd0 80543de6 nt!KiThreadStartup+0x4: 80543dd0 33ff xor edi,edi 80543dd2 33ed xor ebp,ebp 80543dd4 b901000000 mov ecx,1 80543dd9 ff151c974d80 call dword ptr [nt!_imp_KfLowerIrql (804d971c)] 80543ddf 58 pop eax 80543de0 ffd0 call eax 80543de2 59 pop ecx 80543de3 0bc9 or ecx,ecx 80543de5 7407 je nt!KiThreadStartup+0x22 (80543dee) // レジスタを確認すると、eax の値は 56565656 となっている kd> r eax=56565656 ebx=00000000 ecx=00000001 edx=ffffffff esi=820e2020 edi=f5e88d64 eip=80541d70 esp=f5e88d84 ebp=f5e88d64 iopl=0 nv up di ng nz na po nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000082 nt!KiTrap0D: 80541d70 f744240c00000200 test dword ptr [esp+0Ch],20000h ss:0010:f5e88d90=00010082 // メモリアドレス 56565656 を調べる // このアドレスは使われていないことが分かる kd> dc 56565656 56565656 ???????? ???????? ???????? ???????? ???????????????? 56565666 ???????? ???????? ???????? ???????? ???????????????? 56565676 ???????? ???????? ???????? ???????? ???????????????? 56565686 ???????? ???????? ???????? ???????? ???????????????? 56565696 ???????? ???????? ???????? ???????? ???????????????? 565656a6 ???????? ???????? ???????? ???????? ???????????????? 565656b6 ???????? ???????? ???????? ???????? ???????????????? 565656c6 ???????? ???????? ???????? ???????? ???????????????? kd> !address 56565656 address 56565656 not found in any known Kernel Address Range ----
ここまでの windbg の結果から、使用されていないメモリアドレス 56565656 を call したことで、例外エラー 0Dh:General Protection Fault*3が生じたと考えます。80543dd9 に call 命令があり、その直後に pop 命令でスタックの値を eax に設定しています。80543dd9 で call した nt!_imp_KfLowerIrql での処理がくさいですね。
この nt!_imp_KfLowerIrql にブレイクポイントを設定しようとしましたが、この関数のメモリアドレスが分かりませんでした。例外エラーが生じた nt!KiThreadStartup は頻繁に呼び出されている関数であるため、ブレイクポイントを設定すると、すぐに windbg に処理がうつってしまうため、実証コードの実行どころではなくなります:( ここでアプローチを変えてみます。vdmexploit!FirstStage にブレイクポイントを設定して、FirstStage 関数が呼ばれる瞬間をとらえてみます。
ちなみに、この時点から「g」コマンドを複数回実行すると、STOP エラー(いわゆる Blue Screen of Death)が生じます。windbg でブレイクポイント(BreakPoint)を設定しないで、実証コードを実行すると STOP エラーが生じませんでした。STOP エラーを生じるのは、windbg でデバッグしている影響だと思います。
... まずは vdmallowed.exe をコマンドプロンプトから実行する ... // 実証コードに成功すると、以下のデバッグ情報が windbg に出力される。 // vdmexploit.dll の FirstStage 関数で呼び出している DbgPrint 関数が // このデバッグ情報を出力している FirstStage() Loaded, CurrentThread @821FD468 Stack F5AA5000 - F5AA1000 Repairing references to 0217D2A4-0217E2A0 in CurrentThread@821FD468... PsLookupProcessByProcessId(940) => 821C1928 PsInitialSystemProcess @823C2830 PsReferencePrimaryToken(821C1928) => E21389D0 PsReferencePrimaryToken(823C2830) => E10017E8 watchdog!WdUpdateRecoveryState: Recovery enabled. // DbgPrint 関数のメモリアドレスを調べて、そのアドレスに // ブレイクポイントを設定する kd> x nt!DbgPrint 80529e72 nt!DbgPrint = <no type information> kd> bp 80529e72 kd> bl 0 e 80529e72 0001 (0001) nt!DbgPrint kd> g ... 再度 vdmallowed.exe をコマンドプロンプトから実行する ... Breakpoint 0 hit nt!DbgPrint: 80529e72 8bff mov edi,edi // シンボルファイルを再読み込みする kd> !reload Connected to Windows XP 2600 x86 compatible target at (Sat Feb 27 13:34:39.656 2010 (GMT+9)), ptr64 FALSE Loading Kernel Symbols ............................................................... ....................................... Loading User Symbols ............ Loading unloaded module list .... // 実証コードの Payload に該当する vdmexploit.dll の // FirstStage 関数のアドレスを調べる kd> x vdmexploit!FirstStage 10001050 VDMEXPLOIT!FirstStage (void) // スタック情報を表示する。vdmexploit.dll の FistStage 関数が // 呼ばれていることが分かる。 kd> kb ChildEBP RetAddr Args to Child 0217d268 100010ad 1000d0bc 820e77f8 f5a0d000 nt!DbgPrint 0217d2c4 00000000 00000000 00000000 00000000 VDMEXPLOIT!FirstStage+0x5d [c:\download\37864\kitrap0d\vdmexploit.c @ 97] // 「g」コマンドで実証コードの実行を継続する kd> g FirstStage() Loaded, CurrentThread @81FF9DA8 Stack F5914000 - F5910000Breakpoint 0 hit nt!DbgPrint: 80529e72 8bff mov edi,edi kd> g Repairing references to 0217D2A4-0217E2A0 in CurrentThread@81FF9DA8...Breakpoint 0 hit nt!DbgPrint: 80529e72 8bff mov edi,edi kd> g PsLookupProcessByProcessId(812) => 821AEA70Breakpoint 0 hit nt!DbgPrint: 80529e72 8bff mov edi,edi kd> g PsInitialSystemProcess @823C2830Breakpoint 0 hit nt!DbgPrint: 80529e72 8bff mov edi,edi kd> g PsReferencePrimaryToken(821AEA70) => E1FEC640Breakpoint 0 hit nt!DbgPrint: 80529e72 8bff mov edi,edi kd> g PsReferencePrimaryToken(823C2830) => E10017E8 kd> g watchdog!WdUpdateRecoveryState: Recovery enabled. ...(ここで、実証コードの実行が成功する)... // vdmexploit.dll の FirstStage 関数にブレイクポイントを // 設定して、再度実証コードを実行する kd> bl 0 e 80529e72 0001 (0001) nt!DbgPrint kd> bc 0 kd> bl kd> bp 10001050 kd> bl 0 e 10001050 0001 (0001) VDMEXPLOIT!FirstStage kd> g ... 再び、vdmallowed.exe をコマンドプロンプトから実行する ... FirstStage() Loaded, CurrentThread @81FF9DA8 Stack F58F4000 - F58F0000 Repairing references to 0217D2A4-0217E2A0 in CurrentThread@81FF9DA8... PsLookupProcessByProcessId(1916) => 822501F8 PsInitialSystemProcess @823C2830 PsReferencePrimaryToken(822501F8) => E1F2D5A8 PsReferencePrimaryToken(823C2830) => E10017E8 ... あれ、ブレイクせずに実証コードの実行が成功した ...
実証コードの Payload に該当する vdmexploit.dll の FistStage 関数が呼ばれたことまでは確認できましたが、nt!KiTrap0D から vdmexploit!FirstStage が呼び出される過程は分かりませんでした。
まとめ
今回いろいろ調べた限りでは、nt!KiTrap0D を実行した後、例外エラーが生じたメモリアドレスを eip に設定する際に、本来設定すべきメモリアドレスではなく、FistStage 関数のメモリアドレスが設定されるのだと理解しました。しかし、まだまだ知識不足で vdmexploit!FirstStage を呼び出すまでの過程が追えませんでした。結局、実機の Windows XP SP3 で実証コードを実行すると再起動してしまう原因も不明のまま・・・
参考:「Windowsダンプの極意」における例外に関する記述
「Windowsダンプの極意」に例外に関する記述があったため、引用します。
これらの例外が発生すると、OS はその例外をトラップ(罠)します。専用の関数が呼び出され、発生した例外に基づいてさまざまな回復処理を行い、修復できなければ STOP エラーを発生させたりするのです。後で説明しますが、トラップする関数の名前の中に発生した例外のコードも含まれています。
p.35 ”2.12 例外とトラップ”,「Windows ダンプの極意」
「後で説明します」の記述が該当するのは、おそらく以下になります。
スタックトレース上部に、nt!KiTrap0E という関数が表示されていることがわかります。これがトラップフレームです(Trap という文字の後ろに 0E ちおう文字が含まれていますが、これは例外コードになります)。
p.130 ”8.1.1 トラップフレームとエラー発生コードの確認”,「Windows ダンプの極意」