
Exploit de prueba de concepto para CVE-2022-42046 que demuestra la escalada de privilegios local mediante la manipulación directa de objetos del kernel de los indicadores EPROCESS Protection y SignatureLevel en el controlador wfshbr64.sys.
CVE-2022-42046 Prueba de concepto de escalada de privilegios local de wfshbr64.sys
wfshbr64.sys y wfshbr32.sys permiten que un usuario arbitrario realice operaciones bit a bit con un desplazamiento EPROCESS arbitrario y un valor de banderas para elevar intencionadamente el proceso del juego a la protección completa de CodeGen manipulando las banderas EPROCESS.Protection y EPROCESS.SignatureLevel (agujero de seguridad como característica).
El controlador está firmado por el editor de compatibilidad de hardware de Microsoft que se envía a través del Programa de Hardware de Microsoft.
Este proyecto fue co-investigado con @DoranekoSystems
MIT. Ver LICENCIA
ObRegisterCallbacks en lugar de elevar forzosamente la protección del proceso mediante manipulación directa de objetos del núcleo. Hay un buen ejemplo aquí.No hagas referencia a IRP después de su finalización. Si tienes el verificador de controladores habilitado, serás atrapado.
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return Irp->IoStatus.Status;
En su lugar, debes usar una variable local.
NTSTATUS status = STATUS_SUCCESS;
Irp->IoStatus.Status = status;
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return status;
Parece que estás verificando un puntero nulo contra el valor de retorno de IoGetCurrentProcess, pero nunca devuelve un puntero nulo por diseño, por lo que no es necesario verificarlo.
PEPROCESS CurrentProcess = IoGetCurrentProcess();
if ( !CurrentProcess ) // no need to check for null pointer
break;
Un tiempo después del informe, el desarrollador implementó una "verificación adicional" sigilosa para derrotar nuestra primera PoC en lugar de renunciar a hacer agujeros de seguridad como característica.
Verificaciones añadidas a:
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;
Esta rutina está diseñada para ser llamada cada vez que el cliente solicita la modificación de EPROCESS, y realiza la verificación del Offset proporcionado por el campo ArbitraryEProcessOffset en esta PoC ― y también de las Flags proporcionadas por el campo DesiredFlags en esta PoC.
La verificación es bastante simple: cuenta los bits 1 en cada campo de bits de las banderas proporcionadas y si el recuento es mayor a ocho, fallará.
El mapa de patrones de banderas posibles es solo cuatro:
22 00 00 0000 22 00 0000 00 22 0000 00 00 22Dicho esto, realizar las siguientes operaciones 4 veces puede garantizar que al menos uno de los intentos sea exitoso:
ArbitraryEProcessOffset por índice: offset - index,DesiredFlags por índice: flag << (index * 8).El desplazamiento se decrementa, por lo que el ajuste de bits causaría que el desplazamiento se ajuste en los operadores bit a bit.
*(ULONG*)(IoGetCurrentProcess() + offset) |= flags;
*(ULONG*)(IoGetCurrentProcess() + offset) &= ~flags;
Hemos añadido las funciones WfsProtectProcessSupreme y WfsUnprotectProcessSupreme que realizan el intento y derrotan el nuevo truco.
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;
}