
Windows x64 手書きのトークン窃取カーネルモードシェルコード
Windows x64 カーネルモードのハンドクラフト(手書き)シェルコード。実行中プロセスのプライマリアクセストークンを SYSTEM プロセストークンに置き換え、Elevation of Privilege(EoP)(特権昇格)を行います。
Windows 7/Windows Server 2008 R2 Build 7601Windows 8/Windows Server 2012 Build 9200Windows 8.1/Windows Server 2012 R2 Build 9600Windows 10 1507/TS1 Build 10240Windows 10 1511/TS2 Build 10586Windows 10 1607/RS1/Windows Server 2016 Build 14393Windows 10 1703/RS2 Build 15063Windows 10 1709/RS3 Build 16299Windows 10 1803/RS4 Build 17134Windows 10 1809/RS5/Windows Server 2019 Build 17763Windows 10 1903/19H1 Build 18362Windows 10 1909/19H2 Build 18363Windows 10 2004/20H1 Build 19041Windows 10 2009/20H2 Build 19042Windows 10 2104/21H1 Build 19043Windows 10 2110/21H2 Build 19044このプロジェクトをビルドするための前提条件は次のとおりです。
Visual Studio 2019(any edition will do fine)Windows 10 SDK, version 2004Windows 10 WDK, version 2004Python3ここで注意すべき点として、アセンブラ(MASM)だけでも十分にやっていけます。技術的にはそれだけで済むからです。
上記をインストールした後は、Visual Studio でソリューションを開き、x64 ターゲットでビルドするだけです。
ビルドが成功すると、バイナリは Bin ディレクトリ配下の適切なビット数サブディレクトリに生成されます。
あるいは、Releases からそのままデプロイ可能な位置独立シェルコードをダウンロードすることもできます。
仕組みが分からない場合は、作業に依存しているマシンにペイロードをデプロイしようと絶対にしないでください。
追加情報については、Microsoft ドキュメントを参照してください。
テスト目的には、カーネルモードシェルコードをテスト VM 上にデプロイするための flare-kscldr と、フルカーネルデバッグサポート付きの Hyper-V Guest VM をセットアップするための CodeMachine のカーネル開発・デバッグ用システムセットアップガイド を強くお勧めします。
あるいは、kdbg-driver-vagrant を使ってプロセスを自動化し、Vagrant を使用してフルカーネルデバッグ対応のテスト VM をすばやく起動することも検討できます。

Dmytro Oleksiuk(@d_olex) 氏から指摘されたとおり、このコードには、いくつかのかなり明白なレースコンディションが存在します。具体的には以下に関連します:
nt!_EPROCESS 構造体を手動で走査していること現状、これらの処理中にオブジェクトが変更されるのを防ぐ保護は一切ありません。
これは問題なのでしょうか? はい、レースコンディションは常に問題であり、あらゆる種類の未定義動作やバグチェックといった不快な事態を引き起こす可能性があります。
このペイロードを使用すると、エクスプロイトの安定性に影響はありますか? あるかもしれません。
では、修正方法は? 修正は2段階です。
パート1では、プロセスリストを走査する前に nt!ExAcquirePushLockExclusive を使用して、Pushlock のような待機型ロック(nt!PspActiveProcessLock(プッシュロックポインタ))を排他アクセス用に取得します(事前に通常のカーネル APC 配信を無効化しておく必要があります)。そして、リストの使用が完了したら nt!ExReleasePushLockExclusive でロックを解放し、その時点で通常のカーネル APC 配信を再び有効にします。
しかし、このグローバル変数は nt カーネルによってエクスポートされていないため、より適切で安全な方法は、nt!ZwQuerySystemInformation API を SYSTEM_INFORMATION_CLASS == SystemProcessInformation と共に使用して ImageName から PID を見つけ、nt!PsLookupProcessByProcessId を使用して PID から nt!_EPROCESS VA を取得することです。
もし、カーネルが前者の処理をどのように行っているのか気になるなら、nt!PsGetNextProcess をディスアセンブラで調べてみてください。
パート2では、nt!ObReferenceObject 系の API を使ってプロセスオブジェクトの参照カウントを増やし、安全に参照します。これにより、最後に nt!ObDereferenceObject で明示的に参照カウントを減らして処理を終えるまで、オブジェクトが削除されることはありません。
参照カウントを手動で増やすのは冗長であることに注意してください。nt!PsLookupProcessByProcessId への呼び出しが成功した場合、その呼び出しが自動的に参照カウントを増やしてくれるからです。
ただし、これらの修正を実装するには、ntoskrnl.exe のベースアドレスを特定し、EAT を走査して何らかの文字列ハッシュアルゴリズムで関数ポインタを見つけるシンボル解決を行う必要があり、そのすべてがペイロードサイズを大幅に増加させることになります。
いつの日かそれを実装するかもしれませんし、単に C で書いてコンパイラに出力を大量生成させるかもしれません :)
エラーを指摘し、修正方法を提案してくれた Dmytro Oleksiuk(@d_olex) 氏と Paul L.(@am0nsec) 氏に感謝します。