脆弱性

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

Clock Icon 9 min read
Related Products

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

目次

10. NtUserMessageCallを呼び出してWndMagicのフックされた関数をトリガーする
11. SetWindowLongAを使って任意のwriteプリミティブを作成する(PoC 534〜542行目)

図表の目次

図25. PoC 530行目
図26. NtUserMessageCall呼び出し前のWndMagicのtagWND構造体
図27. NtUserMessageCall呼び出し後のWndMagicのtagWND構造体
図28. Wnd0のtagWND構造体
図29. NtUserMessageCall呼び出し後のメモリー レイアウト
図30. SetWindowLongWの関数プロトタイプ
図31. PoC 534行目
図32. PoC 542行目

10. NtUserMessageCallを呼び出してWndMagic上のフックされた関数をトリガーする

ここまでで任意のウィンドウ オブジェクト(ステップ9で説明したようにここではWnd0)に対する潜在的なカーネル デスクトップ ヒープ ポインターを取得する準備ができたので、あとはフックされたユーザーモード コールバックxxxClientAllocWindowClassExtraBytesを呼び出す関数を見つけるだけです。

ここでこのxxxClientAllocWindowClassExtraBytesは、CVE-2021-1732で悪用されたものと同じユーザーモード コールバックであることを念頭に置くとよいでしょう。あちらの場合、xxxClientAllocWindowClassExtraBytesxxxCreateWindowEX内で呼び出される前にフックされていました。xxxCreateWindowEXは、CreateWindowファミリーのいずれかの関数の呼び出した後で呼び出されます。

この脆弱性を緩和する修正プログラムの一部としてMicrosoftはxxxCreateWindowEX内にチェックを追加し、ユーザーモード コールバック中にtagWND.dwExtraFlag(ステップ7で解説)が変更されないようにしました。ですがたとえば、Microsoftが修正していない、xxxClientAllocWindowClassExtraBytesを呼び出す関数がほかにもあったとしたらどうでしょうか。

CVE-2022-21882の場合、NtUserMessageCallが、そうした関数の1つであることがのちに特定されました。実際、CVE-2022-21882を発見した人物は、単にCVE-2021-1732の修正プログラムをレビューして脆弱性の原因を特定した上で、「xxxClientAllocWindowClassExtraBytesを呼び出すほかの関数のなかには、まだtagWND.dwExtraFlagの検証を行う修正が反映されていないものがあるのではないか」と考え、それらの関数を確認した可能性が高いものと思われます。

NtUserMessageCallの呼び出し(行530)を図25に示します。使用されているパラメーターは、最終的にxxxWrapSwitchWndProcを実行させ、次にxxxSwitchWindowProcを実行させ、さらにxxxClientAllocWindowClassExtraBytesを呼び出します。

画像25は、PoC 530行目のスクリーンショットです。NtUserMessageCallを呼び出しています。
図25 PoC 530行目

これでxxxClientAllocWindowClassExtraBytesが悪意のある関数にフックされたので、NtUserMessageCallの呼び出しはg_newxxxClientAllocWindowClassExtraBytesの呼び出しにつながります。この結果、WndMagicはコンソール ウィンドウに変換され、そのpExtraBytesWnd0のカーネル デスクトップ ヒープ ベース オフセットに設定されます。これは、WndMagictagWND.pExtraBytes値への参照はすべてWnd0のカーネル デスクトップ ヒープの位置を指すことを意味します。

あるウィンドウのpExtraBytesフィールドを変更する方法があるなら、同じ方法でエクスプロイトを有効にして、少なくともtagWND.cbWndExtraフィールドに格納されているtagWND.pExtraBytesの長さまでは、Wnd0tagWND構造体を変更できるようになります。実際、SetWindowLong関数ファミリーはまさにこの目的のために作られています。

NtUserMessageCall呼び出し前後のWndMagictagWND構造体のメモリー レイアウトをそれぞれ図26と図27に示します。

画像26は、NtUserMessageCall呼び出し前のWndMagicのtagWND構造体のスクリーンショットです。黄色い枠で「018」をハイライトしています。
図26. NtUserMessageCall呼び出し前のWndMagictagWND構造体
画像26は、NtUserMessageCall呼び出し前のWndMagicのtagWND構造体のスクリーンショットです。黄色い枠で「018」をハイライトしています。
図27. NtUserMessageCall呼び出し後のWndMagictagWND構造体

WndMagictagWND.dwExtraFlag0x100100818で、これはうまくコンソール ウィンドウに変換されたことを示しています。tagWND.pExtraBytesの値は0x2ad30に変更されています。

WndMagicはコンソール ウィンドウになったので、この値はカーネル デスクトップ ヒープ ベースからのオフセットを表しています。ただし、これがWnd0が配置されているのと同じオフセットであることを思い出してください。これはつまり、WndMagictagWND.pExtraBytesフィールドは、カーネル デスクトップ ヒープ上のWnd0tagWND構造体を指すようになったということです。

図28は、Wnd0tagWND構造体とそのOffsetToDesktopHeapの値である0x2ad30を示しています。WndMagictagWND.pExtraBytesフィールドは今この値を指しています。

画像28は、Wnd0のtagWND構造体のスクリーンショットです。黄色い枠で0002ad30がハイライトされています。
図28. Wnd0tagWND構造体

メモリー レイアウトと、この段階でのウィンドウのフィールド参照を図29に示します。

画像29は、NtUserMessageCall呼び出し直後のメモリー レイアウトを表す図です。左側がユーザー ランドとそのレイアウト、右側がカーネル ランドです。
図29. NtUserMessageCall呼び出し後のメモリー レイアウト
11. SetWindowLongAを使って任意のwriteプリミティブを作成する(PoC 534〜542行目)

図30に示すように、SetWindowLongW関数はウィンドウ ハンドルを最初のパラメーターとして受け取ります。パラメーターnIndexは、tagWND.pExtraBytesのメモリー割り当てのインデックスです。

標準ウィンドウの場合、このインデックスはtagWND.pExtraBytesに格納されたポインターからのオフセットとして参照されます。ですがコンソール ウィンドウの場合、このインデックスはtagWND.pExtraBytesに格納されているオフセット値とカーネル デスクトップ ヒープ ベースから参照されます。最後のパラメーターdwNewLongは、nIndexにあるメモリーを変更する値です。

画像30はSetWindowLongW関数プロトタイプのスクリーンショットです。
図30. SetWindowLongWの関数プロトタイプ

したがって、ウィンドウの追加バイトの内容を変更するには、SetWindowLongWを呼び出せばよいことになります。WndMagicに対してSetWindowLongWが呼び出されると、SetWindowLongWWndMagictagWND.pExtraBytes内にあるオフセットを処理実行用のメモリー空間として参照します。

要するに、WndMagicHWNDパラメーターとしてSetWindowLongWを呼び出した場合、Wnd0tagWNDベース アドレスにnIndexが設定されたオフセット値を加えた範囲内にあるメモリーが変更されることになります。これは、WndMagicpExtraBytesが現在Wnd0tagWNDベースを指しているためです。

図31に示すSetWindowLongWの最初の呼び出し(行534)では、WndMagicへのハンドルが渡され、オフセット0x128 + 0x10が、ステップ6でWnd0へのオフセットとして定義されたkernel_desktop_heap_base_offset_Minに変更されています。このオフセットは、実際には0x128のオフセットに過ぎません。なぜ0x10が含まれているかについては、以下の注を参照してください。

ただし、WndMagicpExtraBytesオフセットはWnd0を参照しているため、このSetWindowLongWへの呼び出しはWnd0のオフセット0x128(tagWND.pExtraBytes)を自身のtagWND.OffsetToDesktopHeapのオフセットに変更します。したがって、これ以降Wnd0SetWindowLongファミリーの関数を呼び出す場合、機能的にはWnd0tagWND構造体の開始位置からの相対オフセットで操作することになります。

また、SetWindowLongWSetWindowLongPtrへの呼び出しは、その関数呼び出しが行われる前の、指定されたオフセット(nIndex)の以前の値を返すことにも注意が必要です。したがって、dwRet変数(図31に示す)は、SetWindowLongWを呼び出す前のWnd0(上の図16に示す0x2ae80)のtagWND.pExtraBytesと等しくなります。

: 0x10の追加は、xxxSetWindowLongW内で行われる減算を考慮したものです。これは奇妙に思えるかもしれませんが、これらの関数はサードパーティ コードからの呼び出しを想定していないので、Microsoftはとくにコードをユーザーフレンドリーなものに変更する必要性を感じていない点を思い出してください。

画像31は、PoC 534行目のスクリーンショットです。これは DWORD dwRet = SetWindowLongW で始まっています。
図31 PoC 534行目

図32に示すSetWindowLongWへの次の呼び出し(542行目)では、Wnd0tagWND.cbWndExtraフィールドを0xFFFFFFFに変更しています。cbWndExtraフィールドがpExtraBytesフィールドのサイズを定義していることを思い出してください。これによって、追加のバイト フィールド サイズが非常に大きな数字になります。これにより、オーバーフローの条件が有効になり、エクスプロイトによる隣接するカーネル メモリーへの書き込みが可能になります。

画像32は、PoC 542行目のスクリーンショットです。これはSetWindowLongWで始まっています。
図32 PoC 542行目

次のステップでは、SetWindowLongPtrAを使ってWnd1spmenuカーネル アドレスを漏えいさせ、偽のspmenuオブジェクトに置き換えます。

続きを読む ➠ セクション 5 – 詳細分析ステップ12

トップに戻る

Enlarged Image