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に示します。
漏えいさせたアドレスのメモリーをダンプし、オフセット0xa0までのアドレスを見てみると、興味の対象が見つかるかもしれません。WinDbgからのダンプを図48に示します。
最初のエントリーに何らかのIDが含まれているらしいことがわかります。これは、tagWNDの親と子の構造体で共有されていた値です。
次の2つのエントリーはそれほど興味をそそるものではありません。次の3つのエントリーにはカーネル アドレスが続いているので、これらをダンプしてなにか気になるものがあるか確認してみてもよいでしょう。
オフセット0x30には、上記のユーザー モード セーフなspmenuオブジェクト(図44)のオフセット0x98にある値と同じ値があります。これはおそらくデスクトップ ヒープからのオフセットであろうと判断できます。
このオフセットをカーネル デスクトップ ヒープのアドレスに加算すると、0xffff8e8201000000 + 0x3a850 = 0xffff8e820103a850になります。これは上図48のspmenu+0x28に格納されている値と同です。この結果は私たちの推測したオフセットを支持するものです。
ここでは、ほかの2つのアドレスを見ても何も発見がなかったとしましょう。オフセットspmenu+0x50のメモリーをダンプすると、図49のようになります。
分析してみるとこのアドレスは見覚えがあるものです(図40のrsi)。実はこれは図50に示したWnd1の親のtagWND構造体のアドレスです。
これは、spmenuオブジェクトから親のtagWND構造体オブジェクトに移動する方法があることを意味します。PoC 507行目(図51の1行目)を見ると、漏えいさせたspmenu+0x50は、read64関数によって読み込まれています。この関数がどのように機能するかは次のセクションで説明しますが、さしあたっては、これが渡された引数のカーネル ポインターを取得する、ということだけ理解しておいてください。したがって、qwFristには親のtagWND構造体のアドレスが格納されると仮定できます。
tagWND構造体の内容をもう一度見直して(図38)、特権昇格のエクスプロイトのしくみについて少しわかってくると(トークンの窃取に関してはこちらの解説が参考になる)、オフセット0x10には THREADINFOというエントリーがあることがわかります。THREADINFOエントリーにはW32THREADというエントリーが含まれていて、このW32THREADというエントリーは、カーネル内の現在のスレッドのETHREADエントリーを指しています。THREADINFO構造体を図52に示します。
ETHREAD + 0x00 (図53)はKTHREAD構造体へのポインターです。そしてこの構造体はさらにオフセット0x220にEPROCESS構造体へのポインターを持っています。
このEPROCESS構造体内のtokenフィールドには、各プロセスに紐づく特権を決めるプロセス トークンが格納されています。これらは、このPoCのqwfourth、qwfifth、qwEprocess変数で取得されています(上図52)。ETHREADの内容のWinDbgによる出力を図53に示します。
WinDbgによるオフセットKTHREAD + 0x220の出力には、EPROCESS構造体へのポインターが含まれています(図54)。
現在のプロセスのEPROCESSがわかったので、ActiveProcessLinksを使って0x4のプロセスID (PID)がついている一意に識別されるシステム プロセスを検索できるようになりました。ActiveProcessLinksは現在実行中のプロセスのリンクリストで、EPROCESS構造体内にも存在しています。
PIDが見つかったら、上記と同じ方法でトークンを見つけて現在のプロセスのトークンにコピーすることにより、その特権をSystemのもつ特権に昇格させることができます。図55に示すPoC 601〜637行目がこれを達成しています。
以上のSystemトークンの窃取手法をまとめると、以下のようになります。
- EPROCESS構造体へのポインターであるKTHREAD.ApcState.Processを保存する
- システム上で現在実行中のプロセスのリンクリストであるEPROCESS.ActiveProcessLinks.Flinkへのポインターを保存する
- 各プロセスのPIDを0x4と比較してSystemプロセスを見つける
- PIDが見つかったらSystemプロセスのトークンを保存する
- 現在のプロセスのトークンをSystemプロセスのトークンで置き換える
- 新しいターミナル プロセスを起動してSystemトークンの特権を継承させる
これら構造体のメンバーはすべて、PoCのread64関数を使って読み込まれます。この関数は、偽のspmenuオブジェクトを利用し、GetMenuBarInfo関数を使ってこの構造体の読み取り/書き込みを行い、spmenuからトークンまでを走査します。
最後のセクションでは、分析の最終段階であるread64関数を分析し、結論をまとめます。