脆弱性

インサイドWin32kエクスプロイト: CVE-2022-21882とCVE-2021-1732の分析

Clock Icon 9 min read
Related Products

This post is also available in: English (英語)

目次

13. 任意のread/writeプリミティブによりSystemのトークンを複製して特権昇格する

図表の目次

図47. PoC 355〜359行目
図48. 漏えいさせたspmenuオブジェクトのWinDbgダンプ
図49. WinDbgによるspmenu + 0x50の出力
図50. WinDbgによる*spmenu + 0x50の出力
図51. PoC 570~598行目
図52. THREADINFO構造体
図53. WinDbgによるETHREAD構造体の出力
図54. EPROCESSエントリーを示すWinDbgによるKTHREAD + 0x220の出力
図55. PoC 601〜637行目

13. 任意のread/writeプリミティブによりSystemのトークンを複製して特権昇格する

この時点で、Wnd1の元のspmenuオブジェクトへのポインターが得られました。これはカーネル メモリーのアドレスでもあります。また元のspmenuポインターを、Wnd1の親のtagWND構造体のspmenuエントリーに格納されている偽のspmenuオブジェクトによって置き換えました。

これから説明するように、このspmenuオブジェクトを使って、ウィンドウのメニュー バーに関する情報を取得します。今の偽spmenuは、ユーザーモード アドレスになっています。したがって直接読み取り可能です。ここでWindowsを騙して偽のspmenuのメンバーの1つまたはそれ以上にカーネル アドレスを入力させることができれば、カーネルの任意のreadプリミティブを手に入れられることになります。

ただしMicrosoftはこの構造体用のシンボルを提供していません。したがって、この構造体にどのようなメンバーが含まれているのか特定するには、多少の分析が必要です。ここでは手を抜いてエクスプロイトのPoCを利用することにします。g_pMem4の割り当て(355〜359行目)から、spmenu構造体のサイズは0xa0バイトであろうと見当をつけられます。これをPoC 355〜359行目にあたる図47に示します。

画像47は、PoC 355〜359行目までのスクリーンショットです。最初の4行はQWORDで始まっています。最後の5行目はHLOCALで始まっています。
図47. PoC 355〜359行目

漏えいさせたアドレスのメモリーをダンプし、オフセット0xa0までのアドレスを見てみると、興味の対象が見つかるかもしれません。WinDbgからのダンプを図48に示します。

画像48はWinDbgからのダンプのスクリーンショットです。この図には漏えいさせたspmenuオブジェクトが表示されています。
図48. 漏えいさせたspmenuオブジェクトのWinDbgダンプ

最初のエントリーに何らかのIDが含まれているらしいことがわかります。これは、tagWNDの親と子の構造体で共有されていた値です。

次の2つのエントリーはそれほど興味をそそるものではありません。次の3つのエントリーにはカーネル アドレスが続いているので、これらをダンプしてなにか気になるものがあるか確認してみてもよいでしょう。

オフセット0x30には、上記のユーザー モード セーフなspmenuオブジェクト(図44)のオフセット0x98にある値と同じ値があります。これはおそらくデスクトップ ヒープからのオフセットであろうと判断できます。

このオフセットをカーネル デスクトップ ヒープのアドレスに加算すると、0xffff8e8201000000 + 0x3a850 = 0xffff8e820103a850になります。これは上図48のspmenu+0x28に格納されている値と同です。この結果は私たちの推測したオフセットを支持するものです。

ここでは、ほかの2つのアドレスを見ても何も発見がなかったとしましょう。オフセットspmenu+0x50のメモリーをダンプすると、図49のようになります。

画像49はspmenu+0x50のWinDbgの出力のスクリーンショットです。
図49. WinDbgによるspmenu + 0x50の出力

分析してみるとこのアドレスは見覚えがあるものです(図40のrsi)。実はこれは図50に示したWnd1の親のtagWND構造体のアドレスです。

画像50は*spmenu + 0×50のWinDbg出力のスクリーンショットです。
図50. WinDbgによる*spmenu + 0x50の出力

これは、spmenuオブジェクトから親のtagWND構造体オブジェクトに移動する方法があることを意味します。PoC 507行目(図51の1行目)を見ると、漏えいさせたspmenu+0x50は、read64関数によって読み込まれています。この関数がどのように機能するかは次のセクションで説明しますが、さしあたっては、これが渡された引数のカーネル ポインターを取得する、ということだけ理解しておいてください。したがって、qwFristには親のtagWND構造体のアドレスが格納されると仮定できます。

画像51はPoC 570〜598行目のスクリーンショットです。この図は渡された引数のカーネル ポインターを取得する関数を含んでいます。
図51. PoC 570~598行目

tagWND構造体の内容をもう一度見直して(図38)、特権昇格のエクスプロイトのしくみについて少しわかってくると(トークンの窃取に関してはこちらの解説が参考になる)、オフセット0x10には THREADINFOというエントリーがあることがわかります。THREADINFOエントリーにはW32THREADというエントリーが含まれていて、このW32THREADというエントリーは、カーネル内の現在のスレッドのETHREADエントリーを指しています。THREADINFO構造体を図52に示します。

画像52はTHREADINFO構造体を示したスクリーンショットです。
図52. THREADINFO構造体

ETHREAD + 0x00 (図53)はKTHREAD構造体へのポインターです。そしてこの構造体はさらにオフセット0x220EPROCESS構造体へのポインターを持っています。

このEPROCESS構造体内のtokenフィールドには、各プロセスに紐づく特権を決めるプロセス トークンが格納されています。これらは、このPoCのqwfourthqwfifthqwEprocess変数で取得されています(上図52)。ETHREADの内容のWinDbgによる出力を図53に示します。

画像53は、ETHREAD構造体をWinDbgで出力したスクリーンショットです。t nt!+exeee +ex438 +0<438 +ex448 +0<448 +ex45e +ex458 +ex46e +0<468 +0<488 +ex488 +8x4a8 +ex4be +ex4ce +ex4c8 +ex4de Tcb CreateTime ExitT ime KeyedWaitChain PostB10ckList ForwardLinkShadow : StartAddress TerminationPort ReaperLink KeyedWaitVa1ue KTHREAD LARGE INTEGER exe LARGE INTEGER exe _ LIST_ENTRY [ exeeeeøeee•eeøeeeee - _ LIST ENTRY [ exfffff807* 70e36f6e oxfffff8eT70e36f6e void • exffffd4e1* øfOf3688 Void : (null) (null) • (null) ActiveTimerListLock : Ox144decø1* møødeee ActiveTimerListHead . _LIST_ENTRY [ øxe1d81f9f' 9658137e oxeeeeeeee• eeeøe394 - exffffd4Ø1*0f0f3688 ] - exøeeøeeøø• eeee1ca8 ] Cid CLIENT ID KeyedWaitSemaphore : _ KSEMAP}ORE Alpcwaitsemaphore : KSEMAPHORE ClientSecurity ps CLIENT SECURITY CONTEXT IrpList _ LIST_ENTRY [ exffffd4Ff5d82b6e - TopLeve11rp DeviceToverify : (null) (null) Win32StartAddress . exffffc2øa• 89e9267e ]
図53. WinDbgによるETHREAD構造体の出力
WinDbgによるオフセットKTHREAD + 0x220の出力には、EPROCESS構造体へのポインターが含まれています(図54)。

画像54は、EPROCESSエントリーを示すKTHREAD + 0x220のスクリーンショットです。ここにはポインターが含まれています。
図54. EPROCESSエントリーを示すWinDbgによるKTHREAD + 0x220の出力

現在のプロセスのEPROCESSがわかったので、ActiveProcessLinksを使って0x4のプロセスID (PID)がついている一意に識別されるシステム プロセスを検索できるようになりました。ActiveProcessLinksは現在実行中のプロセスのリンクリストで、EPROCESS構造体内にも存在しています。

PIDが見つかったら、上記と同じ方法でトークンを見つけて現在のプロセスのトークンにコピーすることにより、その特権をSystemのもつ特権に昇格させることができます。図55に示すPoC 601〜637行目がこれを達成しています。

画像55は、PoC 601〜637行目のスクリーンショットです。ここで特権昇格を行っています。
図55. PoC 601〜637行目

以上のSystemトークンの窃取手法をまとめると、以下のようになります。

  1. EPROCESS構造体へのポインターであるKTHREAD.ApcState.Processを保存する
  2. システム上で現在実行中のプロセスのリンクリストであるEPROCESS.ActiveProcessLinks.Flinkへのポインターを保存する
  3. 各プロセスのPIDを0x4と比較してSystemプロセスを見つける
  4. PIDが見つかったらSystemプロセスのトークンを保存する
  5. 現在のプロセスのトークンをSystemプロセスのトークンで置き換える
  6. 新しいターミナル プロセスを起動してSystemトークンの特権を継承させる

これら構造体のメンバーはすべて、PoCのread64関数を使って読み込まれます。この関数は、偽のspmenuオブジェクトを利用し、GetMenuBarInfo関数を使ってこの構造体の読み取り/書き込みを行い、spmenuからトークンまでを走査します。

最後のセクションでは、分析の最終段階であるread64関数を分析し、結論をまとめます。

続きを読む ➠ セクション 7 – 詳細分析ステップ14、read64関数の分析と結論

トップに戻る

Enlarged Image