
免責事項: このコードは教育および防御研究目的でのみ提供されます。カーネルエクスプロイトの理解を深め、防御側が同様の脆弱性から保護する手助けをするために作成されました。このプロジェクトの不正、違法、または悪意のある使用は固く禁じられています。
ハッシュ: ab01485bb7c8bc1a9c86096eeea6d31d8fad557bf4d44072b46373d2203faa6e
ドライバ名: pstrip64.sys
CVE: CVE-2026-29923
「Bring Your Own Vulnerable Driver」(BYOVD)攻撃は古くからある手法ですが、攻撃者が最新のWindowsセキュリティ保護を回避するために、オペレーティングシステムが依然として正式に信頼するレガシードライバを利用するため、依然として非常に効果的です。ドライバが読み込まれると、攻撃者はその欠陥を武器化して、標準的な非特権プロセスと完全なシステムレベルの制御との間のギャップを埋めます。
今週初め、pstrip64.sys ドライバに新しい脆弱性が公開され、CVE-2026-29923 として追跡されています。このブログ記事では、エクスプロイトの全ライフサイクルを解説します。初期の脆弱性調査と概念実証(PoC)の開発から、環境を保護する防御側のための実用的な緩和戦略までをカバーします。
pstrip64.sys ドライバは、EnTech Taiwan PowerStrip(バージョン3.90.736まで)に関連するレガシーカーネルモードコンポーネントです。その正当な目的は高度なグラフィックカードディスプレイの調整を可能にすることですが、その深いシステム特権により、攻撃者にとって非常に魅力的なターゲットとなっています。
脆弱性が最初に公開されたとき、私はまずその DriverEntry 関数を分析することから始めました。これはカーネルドライバのメイン初期化ルーチンとして機能し、\Device\PSTRIP64 デバイスオブジェクトを作成し、\DosDevices\PSTRIP64 シンボリックリンクを介してユーザーモードアプリケーションに公開します。さらに重要なのは、ドライバのディスパッチテーブルを設定することです。私の目を引いたエントリはインデックス14(IRP_MJ_DEVICE_CONTROL)でした。これはユーザーが提供するすべてのIOCTLリクエストを直接 sub_11340 ハンドラ関数にルーティングし、これが私たちの主要な関心領域です。
sub_11340 関数は主要なIOCTLディスパッチャとして機能し、ユーザーモードからのリクエストを解釈します。
公開されているすべてのIOCTLの中で、0x80002008 は間違いなく最も興味深いものです。デフォルトのケースはマイナーなI/Oポートの相互作用を処理しますが、0x80002008 は sub_11000 へのゲートウェイとして機能します。SystemBuffer をこの関数に直接渡すことによって。
この sub_11000 ルーチンが決定的な証拠です。まず、HalTranslateBusAddress を使用して、ユーザーが提供したアドレスを有効なシステム物理アドレスに変換します。次に、\Device\PhysicalMemory を開き、ZwMapViewOfSection を使用してマッピングします。ターゲットプロセスハンドルを (HANDLE)0xFFFFFFFFFFFFFFFFLL(ZwCurrentProcess() を表す)にハードコーディングすることで、ドライバはこの物理メモリを呼び出し元のプロセスの仮想アドレス空間に直接マッピングします。重要なのは、この新しくマッピングされた仮想アドレスを SystemBuffer に書き戻してユーザーに返し、アプリケーションに物理メモリを読み書きするための直接ポインタを正式に提供することです。
脆弱性を完全に理解し、物理的な読み取り/書き込みプリミティブを確立したので、必要なパズルのピースはすべて揃いました。いよいよ概念実証の作成を開始します。
注: このPoCは Windows 10 22H2 環境で特別に開発およびテストされました。エクスプロイトは生の物理メモリ操作に依存しているため、カーネル構造のオフセットと物理メモリの境界は現在、私のセットアップ用にハードコードされています。自分のマシンでテストするには、Windowsカーネルオフセットを更新し、物理アドレススキャン範囲を自分のOSビルドとRAM構成に合わせて調整する必要があります。
エクスプロイトの最初のステップは、ドライバとの通信を確立することです。私はドライバのシンボリックリンク(\\.\PSTRIP64)に対して CreateFileA を呼び出すことでこれを行いました。有効なハンドルを取得したら、以前分析した 0x80002008 IOCTLを悪用するためのクリーンな方法が必要でした。MapPhysicalMemory() というラッパー関数を作成しました。この関数は、ターゲットの物理アドレスと読み取りたいメモリチャンクの長さを指定して、カスタムの PSTRIP_MAP_REQUEST 構造体を設定します。
次に、この構造体を DeviceIoControl を介してドライバに直接送信します。成功すると、ドライバはその物理メモリをユーザーモードアプリケーションに直接マッピングし、OutputResult フィールドに仮想ベースアドレスを返します。これで、返されたアドレスを標準のC++ポインタにキャストでき、システムの物理RAMへの生の、非特権アクセスが可能になります。
物理的な読み取り/書き込みプリミティブが完全に機能するようになったので、私の目標はプロセス特権を含むカーネルデータ構造を見つけることでした。Windowsでは、実行中のすべてのプロセスは EPROCESS 構造体で表されます。
Windowsは、Pool Tagと呼ばれる特定の4バイト識別子を使用して、カーネルプールに EPROCESS 構造体を割り当てます。プロセスの場合、このタグは文字列 Proc(16進数で 0x636F7250 に変換)です。システムの物理RAMをスキャンすることで、この正確な文字列を検索できます。
私のエクスプロイトは、物理メモリ空間を 0x10000000 から 0x140000000 までループし、メモリを2MBチャンク(STEP_SIZE = 0x200000)でマッピングします。各マッピングされたチャンクを生のバイト配列にキャストし、16バイト(sizeof(_POOL_HEADER))単位でスキャンします。
ただし、物理メモリ内で Proc タグを見つけるだけでは十分ではありません。メモリは乱雑で、そのタグは終了したプロセスから残ったアーティファクトであるか、たまたま16進数値に一致するランダムなデータである可能性があります。すべての Proc タグが有効な EPROCESS 構造体であると盲目的に仮定してメモリを変更し始めると、すぐにBSODが発生します。
安定性を確保するために、ヒューリスティックを使用して構造体を検証する必要がありました。まず、プールタグからわずかにオフセットした EPROCESS 構造体の開始位置を計算します。そこから、実行中のプロセスに関するいくつかの既知の定数を確認します。
0x2(通常の優先度)であることを確認します。0x0 であることを確認します。これらすべてのヒューリスティックが合格した場合、有効でアクティブなプロセスを見ている可能性が非常に高いです。次に、そのUnique Process ID(PID)を読み取ります。PIDが自分のエクスプロイトプロセスと一致する場合、そのトークンポインタの物理アドレスを保存します。PIDが 4(Windows System プロセス)の場合、その高い特権を持つトークンの実際の値を抽出して保存します。
最後に、自分のプロセスのトークンポインタの保存された物理アドレスを最も近い4KB境界にアライメントし、MapPhysicalMemory() をもう一度使用してその特定のページだけをマッピングします。
次に、正確なオフセットに移動し、自分のトークンをSystemトークン値で上書きします。即座に、Windowsカーネルはエクスプロイトプロセスを NT AUTHORITY\SYSTEM として扱います。
ページをアンマッピングしてシステムの安定性を確保した後、単純に CreateProcessA を呼び出して cmd.exe を起動します。現在のプロセスが昇格されているため、新しいコマンドプロンプトはこれらのトップレベルの特権を継承し、攻撃は成功です!
注: 初期のデバッグ段階で発見した重要な詳細は、ドライバがマッピングされたポインタをどのように処理するかです。SystemBuffer->LowPart = (unsigned int)BaseAddress; を実行することにより、ドライバは64ビット仮想ベースアドレスを32ビット値にキャストしてから返します。このトランケーションはアドレスの上位ビットを失い、64ビットエクスプロイトでそれをデリファレンスしようとすると即座にアクセス違反が発生しました。この問題をクリーンに回避するために、ユーザーモードPoCを32ビットアプリケーションとしてコンパイルし、返されるポインタが完全に有効なままであることを保証しました。
注: 初期テスト中に、興味深いエッジケースに遭遇しました。PoCはメモリ内でエクスプロイトプロセスを正常に特定しましたが、System プロセス(PID 4)を見つけることができませんでした。
その理由を理解するために、物理メモリを直接検査する必要がありました。カーネルデバッガ(WinDbg)をアタッチし、コマンドを使用して System プロセスの仮想アドレスとディレクトリベースを取得しました。次に、!vtop を使用してその仮想アドレスをRAM内の正確な物理アドレスに変換しました。
PoCにアタッチされたユーザーモードデバッガに戻りました。メモリスキャンループに条件付きブレークポイントを設定し、MapPhysicalMemory() 関数が System プロセスの物理アドレスを含む2MBチャンクを取得した瞬間に実行を一時停止するように指示しました。
ブレークポイントがヒットしたら、マッピングされたメモリの生のバイトを手動で検査し始めました。ここで、Windowsカーネルプール割り当てに関する重要な詳細を発見しました。
Windowsがプロセスにメモリを割り当てるとき、_POOL_HEADER(Proc タグを含む)で始まり、次に _OBJECT_HEADER、最後に EPROCESS 構造体自体が続きます。標準的なユーザーモードアプリケーションの場合、これらのヘッダーには追加の追跡データが含まれているため、実際の EPROCESS 構造体はプールタグから 0x80 バイト後から始まります。
しかし、System プロセスのメモリを検査すると、異なるレイアウトが明らかになりました。System プロセスには、これらの標準的な追跡ヘッダーの一部が欠けています。Proc タグから EPROCESS 構造体の開始までのオフセットはわずか 0x40 バイトでした!
修正は簡単でした。PoCを更新して、Proc タグに遭遇したときに可能なオフセット(0x40 と 0x80)の配列をループすることで、両方のプールヘッダーサイズを処理できるようにしました。
サイバーセキュリティは、攻撃者と防御者の間の終わりのない猫と鼠のゲームです。攻撃者が常に脆弱なドライバを探し求める一方で、現代のセキュリティ製品とブルーチームは、この正確な操作を検出してブロックするためのいくつかの堅牢な方法を持っています。
BYOVD(Bring Your Own Vulnerable Driver)攻撃を阻止する最も効果的な方法は、最初からドライバの読み込みを防ぐことです。
pstrip64.sys のハッシュがブロックリストに追加されていることを確認する必要があります。ドライバが既に読み込まれている場合でも、セキュリティ製品はトークン操作フェーズ中にエクスプロイトを検出できます。
NT AUTHORITY\SYSTEM に昇格させることは、重大なレッドフラグです。cmd.exe など)を生成する場合、特に親プロセスが SYSTEM として実行される理由がない場合に、それを検出するルールを作成できます。