
CVE-2021-28476の概念実証。これはHyper-Vのvmswitch.sysにおける任意ポインタ参照の脆弱性で、ゲストからホストへのサービス拒否(DoS)および潜在的なリモートコード実行(RCE)を可能にします。
Hyper-V ゲスト仮想マシンは、vmswitch.sys 内の VmsIfrInfoParams_OID_SWITCH_NIC_REQUEST 関数による入力の安全でない解析により、その基盤となるホスト上でサービス拒否状態を引き起こす可能性があります。このクラッシュは、ゲストが制御する値をホストの仮想アドレスであるかのように関数が逆参照しようとすることで発生します。このバグは、特定の条件下、具体的にはリクエストが攻撃者が制御するデータへのポインタとともに基盤となるハードウェアデバイスに渡される場合に、RCE を取得するために使用される可能性がありますが、この手法が公開で実証された例は把握していません。
2021年5月11日
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-28476
任意逆参照
リバースエンジニアリング/コード監査
このバグは、vmswitch ドライバの初期偵察中に発見されました。MSDN のドキュメントを確認中に、NDIS_SWITCH_NIC_OID_REQUEST 構造体にポインタが含まれていることに気づき、関数名にその構造体名への参照があるのを思い出しました。ゲストがホストの仮想アドレスを知ることはできないはずですが、実際の安全性チェックは存在しなかったため、関数がどのように動作するかを理解するためにいくつかのリバースエンジニアリングが実行されました。その後、改変された Linux Hyper-V ドライバを使用して偽のリクエストをホストに送信し、即座にクラッシュを引き起こしました。
vmswitch.sys ドライバには、RNDIS OID リクエストを処理するための多数の関数が含まれており、その中には受信メッセージをログ記録するためのものも含まれます。そのうちの1つである VmsIfrInfoParams_OID_SWITCH_NIC_REQUEST は、OID_SWITCH_NIC_REQUEST タイプのリクエストを処理します。MSDN のドキュメントによると、このリクエストは、Hyper-V 拡張可能スイッチの外部ネットワークアダプタを介して、ゲストマシンから基盤となる物理ネットワークアダプタへ OID リクエストを転送できるように設計されています。
OID リクエストは、その外側のレイヤーに明確に定義された汎用メッセージ構造を使用し、特定の OID メッセージタイプに固有のデータを保持するように設計された「InformationBuffer」を含みます。OID_SWITCH_NIC_REQUEST の場合、InformationBuffer は以下の定義を持ちます(これは文書化された構造とは若干異なりますが、コード内で機能的に存在していたものと一致します):
typedef struct _NDIS_SWITCH_NIC_OID_REQUEST {
NDIS_OBJECT_HEADER Header; //u32 サイズの構造体
u32 Flags;
NDIS_SWITCH_PORT_ID SourcePortId; //u32 タイプ
NDIS_SWITCH_PORT_NIC SourceNicIndex; //u32 タイプ
NDIS_SWITCH_PORT_ID DestinationPortId; //u32 タイプ
NDIS_SWITCH_PORT_NIC DestinationNicIndex; //u32 タイプ
PNDIS_OID_REQUEST OidRequest; //**本来は**転送される NDIS_OID_REQUEST 構造体へのポインタであるべき
} NDIS_SWITCH_NIC_OID_REQUEST, *PNDIS_SWITCH_NIC_OID_REQUEST;
このオブジェクトの初期解析の一部として、VmsIfrInfoParams_OID_SWITCH_NIC_REQUEST は、カプセル化された _NDIS_SWITCH_NIC_OID_REQUEST から転送された NDIS_OID_REQUEST リクエストを展開しようとします。しかし、Hyper-V ゲストが OidRequest 値として無効なメモリ位置を提供した場合、逆参照によって範囲外読み取りが発生し、ルートパーティションのカーネルがクラッシュします。
影響を受ける関数の疑似コードは以下のとおりです:
void VmsIfrInfoParams_OID_SWITCH_NIC_REQUEST(void* unknown1, u64 unknown2, _NDIS_SWITCH_NIC_OID_REQUEST* request, u32 buffer_size) {
NDIS_OID_REQUEST* forwarded_request;
if((unknown1 != 0) && (buffer_size != 0)) {
if(buffer_size > 0x20) {
forwarded_request = (NDIS_OID_REQUEST*)request->OidRequest; //ここで逆参照とクラッシュが発生
}
}
...
//省略
...
return;
}