CVE-2026-50416: Windows 11 KASLR回避
影響を受けるシステムでは、Win32デスクトップヒープのユーザーマッピングからのこの読み取りにより、カーネルアドレスが返されました:
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
それが開示のすべてだ。最初は、説得力のある上位ビットを持つ8バイトの値にすぎなかった。私はその値を読むよりも、その値が何に続くのかを確認するほうに多くの時間を費やした。
Microsoftはこの問題を CVE-2026-50416 として公開しており、CWE-200 に分類される Win32k の情報漏えいである。
PoC は x64 TEB から始まる。GS:[0x30] で現在の TEB が得られ、私がテストしたビルドでは TEB + 0x800 で Win32 クライアントデータに到達し、スロット 5 にはデスクトップヒープのユーザーアドレスが格納されている:
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
スロット5は、そのクライアントデータのさらに 0x28 バイト先です。Trellix は Windows 10 1909 で tagWND マッピングを調査した際に、同じ TEB + 0x800 + 0x28 の経路を文書化しています。これらのオフセットは ABI ではありません。私はこれをハードコードし、マップされた領域を使用する前にその結果に対して VirtualQuery を呼び出しています。
ポインタチェックは、正規の x64 符号拡張、8 バイトアラインメント、および既知のセンチネルパターンをカバーしています。その後、値は以下の動作テストを通過しなければなりません。
メインの PoC はオフセット 0x100 を読み取り、1 つの STATIC、1 つの BUTTON、1 つの EDIT ウィンドウを作成し、再び読み取り、3 つのウィンドウを破棄し、その後もう一度読み取ります。
ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);
/* Create STATIC, BUTTON and EDIT windows. */
ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);
/* Destroy all three. */
ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);
3つのQWORDが一致した。これらのウィンドウ割り当ては生成されては消えていったが、0x100 の値は変わらなかった。
kaslr_sandbox_proof.c はプロセス比較を処理する。親プロセスは各子プロセスの stdout をパイプ経由でリダイレクトし、子プロセスが読み取った QWORD を解析してベースラインと比較する。
デフォルトのデスクトップにアタッチされた子プロセスは同じ値を返した。CreateDesktop で作成されたデスクトップに割り当てられた子プロセスは異なる値を返した。このハーネスはさらに Low 整合性、AppContainer、その LPAC 構成、および Low 整合性 AppContainer の子プロセスも実行する。これらはすべて親プロセスの値を受け取るのではなく、自身の TEB とマッピングから読み取りを行う。
supporting_proof_no_window.c は CreateWindowEx を呼び出さずに読み取りを実行する。新しい USER ウィンドウは必要ない。
メインの PoC は、リークした値から 0x40 を引いたものを、デスクトップヒープに対応するカーネル側のベースとして扱う:
ULONG64 candidate_kernel_heap_base = leaked - 0x40;
次に user32!gSharedInfo を参照し、HWND の下位 16 ビットを USER ハンドルテーブルのインデックスとして使用し、HeEntrySize ずつ aheList を進めます。そのエントリの最初の QWORD が非ゼロかつマップされたヒープより小さい場合、PoC はそれをデスクトップヒープの相対オフセットとして使用します:
ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;
if (heap_offset > 0 && heap_offset < heap_size) {
BYTE *user_candidate = desktop_heap + heap_offset;
ULONG64 kernel_candidate = candidate_kernel_heap_base + heap_offset;
}
これは、古いデスクトップヒープ手法で使われていたのと同じ一般的な関係です。ユーザービューとカーネルビューは、それぞれのヒープベースから同じオフセットにオブジェクトを配置します。Unit 42 は USER ハンドルテーブルと、古い gSharedInfo および HMValidateHandle のアドレスリークについて有用な背景情報を提供しています。
得られたアドレスが PoC のカーネルオブジェクト候補です。0x40 の関係、ハンドルエントリのレイアウト、TEB オフセットは、テストに使用したビルドに固有のものです。
オブジェクト候補についてはここまでにしておきます。desktop_heap + 0x100 にある QWORD は、ユーザーが読み取り可能なマッピング内の安定したカーネル空間アドレス情報です。別の Win32k メモリ破壊バグは、アドレスの推測の代わりにこれを使用できます。