CVE-2022-42046 wfshbr64.sys 本地权限提升的概念验证
wfshbr64.sys 和 wfshbr32.sys 的特制载荷允许任意用户以任意的 EPROCESS 偏移量和标志值执行位运算,通过操纵 EPROCESS.Protection 和 EPROCESS.SignatureLevel 标志,故意将游戏进程提升到 CodeGen Full 保护级别(将安全漏洞作为功能)。
该驱动程序由通过 Microsoft 硬件计划提交的 Microsoft 硬件兼容发布者签名。
本项目与 @DoranekoSystems 共同研究。
MIT。参见 LICENSE
ObRegisterCallbacks 而不是通过直接操作内核对象来强行提升进程保护。在此处有一个很好的示例。不要在完成后引用 IRP。如果你启用了驱动程序验证器,你就会被捕获。
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return Irp->IoStatus.Status;
相反,你应该使用局部变量。
NTSTATUS status = STATUS_SUCCESS;
Irp->IoStatus.Status = status;
IofCompleteRequest(Irp, IO_NO_INCREMENT); // IRP is freed here
return status;
看起来你在检查 IoGetCurrentProcess 返回值是否为空指针,但它在设计上永远不会返回空指针,因此你不必检查它。
PEPROCESS CurrentProcess = IoGetCurrentProcess();
if ( !CurrentProcess ) // no need to check for null pointer
break;
报告发布后不久,开发者实现了偷偷摸摸的“附加验证”,以击败我们的第一个 PoC,而不是放弃将安全漏洞作为功能的做法。
新增了以下检查:
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;
该例程被设计为在客户端每次请求修改 EPROCESS 时调用,并验证此 PoC 中 ArbitraryEProcessOffset 字段提供的 Offset,以及此 PoC 中 DesiredFlags 字段提供的 Flags。
验证相当简单:它计算所提供标志的每个位字段中 1 位的数量,如果数量大于八,则验证失败。
可能的标志模式映射只有四种:
22 00 00 0000 22 00 0000 00 22 0000 00 00 22也就是说,重复执行以下操作 4 次,可以保证至少有一次尝试会成功:
ArbitraryEProcessOffset 字段减去索引:offset - index,DesiredFlags 字段中的位: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 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;
}