Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
cve-2019-1458_POC — cve-2019-1458 の PoC | Kitploit
ツール/GitHubGitHub/piotrflorczyk/cve-2019-1458_poc
脆弱性分析エクスプロイトリバースエンジニアリング学習と教育バイナリエクスプロイト
GitHubpiotrflorczyk/cve-2019-1458_poc

cve-2019-1458_POC

cve-2019-1458 の PoC

リポジトリを見る
1815394年前Kitploit レビュー済み

人気

すべて見る →

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

すべてのツールを探索

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

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

CVE-2019-1458: 「in the wildレポート」からPOCへ

12月にKasperskyは、[実際の攻撃で使用された0dayエクスプロイトに関するブログ記事][1]を公開しました。彼らはエクスプロイトの動作を説明していましたが、分析ではPOCを提供していなかったため、興味を引かれました。 そこでKasperskyのブログ記事とパッチ解析に基づいて、この脆弱性のPOCの作成を試みることにしました。
この記事は、その過程をまとめたものです。

情報収集:

まず最初に、この脆弱性についてできるだけ多くの情報を集めました。 前述のブログ記事を読んで、以下の情報を得ました:

  • 脆弱性はウィンドウ切り替え機能に関連している
  • トリガーにはALTキーの押下をシミュレートする必要がある
  • 非公開のNtUserMessageCall APIへの呼び出しが2回必要
  • 特別な切り替えウィンドウを作成する必要がある
  • カーネル関数win32k!DrawSwitchWndHiliteへの参照があった

その他に、前述のいくつかの項目を示す逆コンパイルコードのスクリーンショットがあります。 具体的には、切り替えウィンドウの作成、toggle_alt_keyという名前の関数の呼び出し、複数回のNtUserMessageCall呼び出しを示しています。

逆コンパイルされたエクスプロイトコードの一部 [画像ソース][1]

有用な情報は多いですが、この脆弱性が正確にどのように機能し、どのようにトリガーするかはまだ説明されていません。

パッチ差分

[影響を受けるモジュールはwin32k.sysでした][2]。このモジュールのパッチ適用版と未適用版の両方をダウンロードしました。
Win7 x64の場合、次の通りです:

  • パッチ適用: KB4530692
  • 未適用: KB4525233

これらは[Microsoft Update Catalog][3]からダウンロードできます。

両バージョンを比較したbindiff結果は次のとおりです。

win32k比較

DebugHook機能に関連する関数を除外した後、残るのはわずかに変更されたInitFunctionTables()関数だけです。

InitFunctionTablesの変更

確かにこれまでで最大のパッチではありません。
これだけでは、この脆弱性の根本原因を即座に特定する助けにはなりません。ただし、*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180)の変数に対する初期値が追加されていることは注目に値します。つまり、これは初期化されていない変数に関連するバグかもしれません。

POC構築 - ステップバイステップ

このセクションでは、この脆弱性をトリガーするPOCを段階的に構築し、同時に脆弱性の正体を解明した過程を紹介します。

どこから始めるか

最初の時点ではパッチ差分はあまり有用な情報を提供しなかったため、開発の初期段階では主にKasperskyのブログ記事に頼りました。
適切なテスト環境のために、最後の脆弱なバージョンのwin32kが動作するWin7 SP1 x64 VMを準備しました。さらに、このVMにWindbgを接続してカーネルデバッグを行い、その際にシンボルサーバーパスも設定しました。
調査は、ブログ記事で言及されていたwin32k!DrawSwitchWndHiliteを調べることから始めました。これは、xxxMoveSwitchWndHiliteとxxxPaintSwitchWindowの2か所から呼び出されています。後者は、元のレポートで言及されていたGetKeyState/GetAsyncKeyState呼び出しで囲まれているため、すぐに注目しました。さらに、これらの呼び出しはALTキーが押されているかどうかをチェックしています。

DrawSwitchWndHiliteへの注目すべき呼び出し箇所
xxxPaintSwitchWindowからDrawSwitchWndHiliteへの呼び出し

呼び出しのクロスリファレンス(xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite)をさらに追跡すると、そのチェーンの最初の要素が、パッチで修正されたInitFunctionTablesで参照されていることを発見しました。

次に、逆コンパイルコードのスクリーンショットからNtUserMessageCallを調べました。 この関数の宣言は次のとおりです。```cpp NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)

エクスプロイトは `msg = 0x14` と `dwType = 0xE0` で呼び出しています。何をするか見てみましょう。```cpp
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(wcx));
wcx.hInstance = hInstance;
wcx.cbSize = sizeof(wcx);
wcx.lpszClassName = L"SploitWnd";
wcx.lpfnWndProc = DefWindowProc;

printf("[*] Registering window\n");
ATOM wndAtom = RegisterClassEx(&wcx);
if (wndAtom == INVALID_ATOM) {
    printf("[-] Failed registering SploitWnd window class\n");
    exit(-1);
}

printf("[*] Creating instance of this window\n");
HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
if (sploitWnd == INVALID_HANDLE_VALUE) {
    printf("[-] Failed to create SploitWnd window\n");
    exit(-1);
}
NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0xE0, 1);

ここでは、単純なウィンドウクラスを登録し、そのクラスのウィンドウを作成しました。次に、エクスプロイトと同じパラメータで NtUserMessageCall を呼び出しました。内部で何が起こっているのかを確認するために、ブレークポイント kd> ba e 1 win32k!NtUserMessageCall を設定してコードを実行しました。 この関数への呼び出しはかなり多いため、正しい呼び出しを捉える必要がありましたが、それほど難しくはありませんでした。コールスタックが非常に短い呼び出しでした。

NtUserMessageCall
NtUserMessageCall

コードをステップ実行すると、gapfnMessageCall 配列の関数を呼び出していることがわかりました。インデックスは msg の値に基づいて計算され、0 になるため、NtUserfnDWORD が呼び出されます。

NtUserfnDWORD
NtUserfnDWORD

次の呼び出しは dwType の値を使用して行われ、今度は gpsi のオフセットが 0x40 になり、呼び出しは xxxWrapSwitchWndProc につながります(この関数は、DrawSwitchWndHilite の呼び出しチェーンを確認したときにも登場しました)。
xxxWrapSwitchWndProc は単に xxxSwitchWndProc を呼び出します。

xxxSwitchWndProc
xxxSwitchWndProc

そしてここが終点です。コードはここで失敗し、msg の値(0x14)から到達したい場所である xxxPaintSwitchWindow には進みません。理由を確認しましょう。

正しいパスをトリガーする

コードがこの段階で失敗するのは、前の画像で強調したように、ウィンドウの fnid が 0x2A0(FNID_SWITCH)ではなく、送信しているメッセージも 1 ではないため、xxxDefWindowProc に到達してしまうからです。このシナリオを回避するには、fnid を FNID_SWITCH に設定して xxxSwitchWndProc を呼び出し、switch 文に直接進んでから xxxPaintSwitchWindow に到達する必要があります。
正しい fnid を設定するにはどうすればよいでしょうか。実は、同じ関数が最初の if ブロック内で設定しています。fnid を設定する命令に到達するには、その中のすべてのチェックを失敗させるだけでよいのです。

3つの if チェックをすべて失敗させるために満たすべき条件は次のとおりです:

  • fnid == 0 かつ cbwndExtra + 0x128 >= *(gpsi + 0x154)
    新しく作成されたユーザーウィンドウでは、fnid は 0 です。 *(gpsi+0x154) は未パッチの win32k! では 0 です。しかし、パッチ適用済みバージョンのように 0x130 に設定されていたとしても、cbwndExtra を 8 以上に設定すれば、最初のチェックを回避できます。
  • msg == 1
    NtUserMessageCall の呼び出しで設定できます。msg を 1 に設定すると、制御フローは NtUserfnDWORD の代わりに NtUserfnINLPCREATESTRUCT を通りますが、それでも xxxSwitchWndProc に到達します。
  • extraData == 0
    ExtraData のサイズは、前述の cbwndExtra を使用してウィンドウクラスを登録するときに設定できます。ExtraData は tagWND 構造体の直後に追加されます(私は、逆コンパイルされたコードを少し見やすくするために、このフィールドを IDA の tagWND 構造体に sizeof(tagWND) オフセットの QWORD として追加しました)。その値は SetWindowLongPtr の呼び出しで設定できます。

これらの条件がすべて満たされると、ウィンドウの fnid は FNID_SWITCH に設定されます。
そこで、NtUserMessageCall を2回呼び出す必要があります。1回目は msg を 1 にして目的の fnid を設定し、2回目で xxxPaintSwitchWindow に到達します。```cpp HINSTANCE hInstance = GetModuleHandle(NULL); WNDCLASSEX wcx; ZeroMemory(&wcx, sizeof(wcx)); wcx.hInstance = hInstance; wcx.cbSize = sizeof(wcx); wcx.lpszClassName = L"SploitWnd"; wcx.lpfnWndProc = DefWindowProc; wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc

printf("[*] Registering window\n"); ATOM wndAtom = RegisterClassEx(&wcx); if (wndAtom == INVALID_ATOM) { printf("[-] Failed registering SploitWnd window class\n"); exit(-1); }

printf("[*] Creating instance of this window\n"); HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); if (sploitWnd == INVALID_HANDLE_VALUE) { printf("[-] Failed to create SploitWnd window\n"); exit(-1); }

printf("[] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); NtUserMessageCall(sploitWnd, WM_CREATE/ = 1*/, 0, 0, 0, 0x0, 1);

printf("[] Calling NtUserMessageCall second time"); NtUserMessageCall(sploitWnd, WM_ERASEBKGND/ = 0x14*/, 0, 0, 0, 0x0, 1);

`extraData` をウィンドウクラスに追加し、`NtUserMessageCall` への2回目の呼び出しを追加しました。これで制御フローは `xxxPaintSwitchWindow` に到達できます。
(余談: `dwType` は `0xE0` である必要はなく、`0` でも問題ありません。`NtUserfnDWORD` 内で `0x1F` と AND 演算されるためです)

![xxxPaintSwitchWindow](https://assets.kitploit.com/production/public/readmes/30684/0a794f2d2af1e438ad43ce6f36921871ba04eeadeba67141388de7163ceaa0fa.png)
*`xxxPaintSwitchWindow`*

詳しく調べると、ウィンドウオブジェクトから取得した値 `extraWndData`(25行目)が書き込み先ポインタとして使われていることに気づきました(46〜52行目)! もし `extraWndData` を自分が制御する値に設定するコードに到達できれば、任意のメモリを破壊できます!  
そこに到達するには、まずさらにいくつかのチェック(赤でマーク)を通過する必要があります。

- ウィンドウに `WS_VISIBLE` フラグが設定されているかどうかを確認。  
このフラグは `CreateWindowEx` で設定できます。
- `fnid == 0x2A0` かつ `cbwndExtra + 0x128 == *(gpsi + 0x154)`  
Fnid は最初の `NtUserMessageCall` で既に設定されています。  
問題はこのチェックの2番目の部分で、`*(gpsi + 0x154)` が脆弱な `win32k` モジュールでは初期化されていないため、このチェックは常に失敗します。何らかの方法で `*(gpsi+0x154)` を正しい値に設定しない限りは。Kaspersky の投稿で言及されている特別なスイッチウィンドウを作成すると、まさにそれが実現されることがわかりました。
- ウィンドウが破棄されていないかどうかを確認。  
このケースでは既に満たされています。
ツールをダウンロード