
Proof-of-Concept-Exploit für CVE-2022-42046, das eine lokale Rechteausweitung durch direkte Manipulation von Kernel-Objekten der EPROCESS-Protection- und SignatureLevel-Flags im wfshbr64.sys-Treiber demonstriert.
CVE-2022-42046 Proof of Concept von wfshbr64.sys lokaler Privilegieneskalation
wfshbr64.sys und wfshbr32.sys speziell erstellte Payload ermöglicht es einem beliebigen Benutzer, bitweise Operationen mit beliebigem EPROCESS-Offset und Flags-Wert durchzuführen, um den Spielprozess gezielt auf CodeGen Full Schutz zu erhöhen, indem die Flags EPROCESS.Protection und EPROCESS.SignatureLevel manipuliert werden (Sicherheitslücke als Feature).
Der Treiber ist vom Microsoft Hardware Compatibility Publisher signiert, der über das Microsoft Hardware Program eingereicht wurde.
Dieses Projekt wurde gemeinsam mit @DoranekoSystems erforscht.
MIT. Siehe LICENSE
ObRegisterCallbacks anstatt den Prozessschutz zwangsweise durch direkte Kernelobjektmanipulation zu erhöhen. Ein gutes Beispiel finden Sie hier.Referenzieren Sie IRP nicht nach Abschluss. Wenn Sie den Treiberverifizierer aktiviert haben, werden Sie erwischt.
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return Irp->IoStatus.Status;
Stattdessen sollten Sie eine lokale Variable verwenden.
NTSTATUS status = STATUS_SUCCESS;
Irp->IoStatus.Status = status;
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return status;
Es scheint, dass Sie einen Nullzeiger gegen den Rückgabewert von IoGetCurrentProcess prüfen, aber dieser gibt per Design niemals einen Nullzeiger zurück, daher müssen Sie ihn nicht überprüfen.
PEPROCESS CurrentProcess = IoGetCurrentProcess();
if ( !CurrentProcess ) // no need to check for null pointer
break;
Eine Weile nach dem Bericht implementierte der Entwickler eine hinterhältige "zusätzliche Überprüfung", um unseren ersten PoC zu besiegen, anstatt davon abzulassen, Sicherheitslücken als Feature zu machen.
Prüfungen hinzugefügt zu:
IOCTL_WFSHBR_REMOVE_FLAGIOCTL_WFSHBR_ADD_FLAGIOCTL_WFSHBR_AND_FLAGcase IOCTL_WFSHBR_ADD_FLAG: // 0xAA013884
if ( !KwfsVerifyCaller(Buffer) ) // verify caller
break;
- if ( Buffer->ArbitraryEProcessOffset >= 0x1000 ) // offset limitation check
+ if ( !KwfsVerifyOffsetAndFlags(Buffer->ArbitraryEProcessOffset,
+ Buffer->DesiredFlags) ) // verify the offset and flags
break;
*(ULONG*)(IoGetCurrentProcess() + Buffer->ArbitraryEProcessOffset) |= Buffer->DesiredFlags;
Diese Routine ist dafür ausgelegt, jedes Mal aufgerufen zu werden, wenn der Client eine Änderung des EPROCESS anfordert, und führt eine Überprüfung des durch das Feld ArbitraryEProcessOffset in diesem PoC bereitgestellten Offset durch – sowie der durch das Feld DesiredFlags in diesem PoC bereitgestellten Flags.
Die Überprüfung ist recht einfach: Sie zählt die 1-Bits in jedem Bitfeld der bereitgestellten Flags, und wenn die Anzahl größer als acht ist, schlägt sie fehl.
Die mögliche Flags-Pattern-Map besteht nur aus vier:
22 00 00 0000 22 00 0000 00 22 0000 00 00 22Das heißt, die folgenden Operationen viermal auszuführen kann garantieren, dass mindestens einer der Versuche erfolgreich ist:
ArbitraryEProcessOffset field by index: offset - index,DesiredFlags field by index: flag << (index * 8).Der Offset wird dekrementiert, sodass die Bitfeldanpassung dazu führt, dass sich der Offset bei den bitweisen Operatoren anpasst.
*(ULONG*)(IoGetCurrentProcess() + offset) |= flags;
*(ULONG*)(IoGetCurrentProcess() + offset) &= ~flags;
Wir haben die Funktionen WfsProtectProcessSupreme und WfsUnprotectProcessSupreme hinzugefügt, die den Versuch durchführen und den neuen Trick besiegt haben.
enum KwfsState {
KwfsStateOnceCall = 0,
KwfsStateNeedsValueEquality = 1,
KwfsStateValueHasBeenSet = 2,
};
bool KwfsVerifyOffsetAndFlags(_In_ ULONG offset, _In_ ULONG offset flags)
{
if (KwfsState::KwfsState == KwfsState::KwfsStateOnceCall) {
g_KwfsVerifyState = KwfsState::KwfsStateValueHasBeenSet;
g_KwfsVerifyStateOffset = offset;
g_KwfsVerifyStateFlags = flags;
if (offset < 0x1000) { // offset limitation check moved here
auto bitcount = 0;
for (auto i = 0; i < 32; ++i) { // count `1` bits in flags
if (flags & (1 << i)) {
++bitcount;
}
}
if (bitcount <= 8) { // count must less than nine
g_KwfsVerifyState = 1;
return true;
}
}
}
else
{
if (g_KwfsVerifyState != KwfsState::KwfsStateValueHasBeenSet
|| offset != g_KwfsVerifyStateOffset
|| flags != g_KwfsVerifyStateFlags) {
return false;
}
}
return false;
}