
Exploit de preuve de concept pour CVE-2022-42046 démontrant une escalade de privilèges locale via la manipulation directe d'objets noyau des drapeaux Protection et SignatureLevel de l'EPROCESS dans le pilote wfshbr64.sys.
CVE-2022-42046 Preuve de concept de l'escalade de privilèges locaux via wfshbr64.sys
Les fichiers wfshbr64.sys et wfshbr32.sys, par le biais d'une charge utile spécialement conçue, permettent à un utilisateur arbitraire d'effectuer une opération bitwise avec un offset EPROCESS arbitraire et une valeur de flags pour élever délibérément le processus de jeu à la protection CodeGen Full en manipulant les flags EPROCESS.Protection et EPROCESS.SignatureLevel (une faille de sécurité considérée comme une fonctionnalité).
Le pilote est signé par l'éditeur de compatibilité matérielle de Microsoft et soumis via le programme Microsoft Hardware.
Ce projet a été co-recherché avec @DoranekoSystems
MIT. Voir LICENSE
ObRegisterCallbacks au lieu d'élever de force la protection du processus en effectuant une manipulation directe des objets du noyau. Un bon exemple se trouve ici.Ne référencez pas l'IRP après son achèvement. Si vous avez activé le vérificateur de pilotes, vous serez détecté.
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return Irp->IoStatus.Status;
Au lieu de cela, vous devriez utiliser une variable locale.
NTSTATUS status = STATUS_SUCCESS;
Irp->IoStatus.Status = status;
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return status;
Il semble que vous vérifiez un pointeur nul par rapport à la valeur de retour de IoGetCurrentProcess, mais par conception, il ne renvoie jamais de pointeur nul, vous n'avez donc pas à le vérifier.
PEPROCESS CurrentProcess = IoGetCurrentProcess();
if ( !CurrentProcess ) // no need to check for null pointer
break;
Quelque temps après le rapport, le développeur a mis en œuvre une "vérification supplémentaire" sournoise pour contrer notre premier PoC, plutôt que de renoncer à exploiter les failles de sécurité comme fonctionnalité.
Vérifications ajoutées à :
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;
Cette routine est conçue pour être appelée chaque fois que le client demande une modification de l'EPROCESS, et effectue une vérification de l'Offset fourni par le champ ArbitraryEProcessOffset dans ce PoC ― ainsi que des Flags fournis par le champ DesiredFlags dans ce PoC.
La vérification est assez simple : elle compte les bits 1 dans chaque champ de bits des flags fournis, et si le compte est supérieur à huit, elle échoue.
La carte des motifs de flags possibles est simplement de quatre :
22 00 00 0000 22 00 0000 00 22 0000 00 00 22Cela dit, effectuer les opérations suivantes 4 fois peut garantir qu'au moins une des tentatives devrait réussir :
ArbitraryEProcessOffset par index : offset - index,DesiredFlags par index : flag << (index * 8).L'offset est décrémenté, de sorte que l'ajustement du champ de bits entraînera un ajustement de l'offset dans les opérateurs bitwise.
*(ULONG*)(IoGetCurrentProcess() + offset) |= flags;
*(ULONG*)(IoGetCurrentProcess() + offset) &= ~flags;
Nous avons ajouté les fonctions WfsProtectProcessSupreme et WfsUnprotectProcessSupreme qui effectuent la tentative et ont vaincu la nouvelle astuce.
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;
}