针对 CVE-2021-40449(Win32k - LPE)的漏洞利用代码
CVE-2021-40449 是 Win32k 中的一个释放后使用(use-after-free)漏洞,可导致本地权限提升。
该漏洞由 卡巴斯基 在野外攻击中发现。
该已发现的漏洞利用代码支持以下 Windows 产品:
不过,该漏洞利用代码目前仅在以下版本上测试过:
强烈建议你在继续之前先阅读卡巴斯基的技术分析。
正如卡巴斯基技术分析文章中所提到的,漏洞存在于 GreResetDCInternal 中。如果攻击者挂钩了用户模式回调 DrvEnablePDEV(该回调在 hdcOpenDCW 期间被调用),则可以通过调用 ResetDC 来销毁原始设备上下文;当用户模式回调返回时,会在内核中造成释放后使用。
以下伪代码部分源自泄露的 Windows XP 源代码,并通过逆向工程 Win32kfull.sys 中(补丁发布前的)最新版 GreResetDCInternal 得出。不相关的部分已用 [...] 删除。请留意 VULN: 注释。
BOOL GreResetDCInternal(
HDC hdc,
DEVMODEW *pdmw,
BOOL *pbBanding,
DRIVER_INFO_2W *pDriverInfo2,
PVOID ppUMdhpdev)
{
// [...]
HDC hdcNew;
{
// Create DCOBJ from HDC
DCOBJ dco(hdc);
if (!dco.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
// Create DEVOBJ from `dco`
PDEVOBJ po(dco.hdev());
// [...]
// Create the new DC
// VULN: Can result in a usermode callback that destroys old DC, which
// invalidates `dco` and `po`
hdcNew = hdcOpenDCW(L"",
pdmw,
DCTYPE_DIRECT,
po.hSpooler,
prton,
pDriverInfo2,
ppUMdhpdev);
if (hdcNew)
{
po->hSpooler = NULL;
DCOBJ dcoNew(hdcNew);
if (!dcoNew.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
// Transfer any remote fonts
dcoNew->pPFFList = dco->pPFFList;
dco->pPFFList = NULL;
// Transfer any color transform
dcoNew->pCXFList = dco->pCXFList;
dco->pCXFList = NULL;
PDEVOBJ poNew((HDEV)dcoNew.pdc->ppdev());
// Let the driver know
// VULN: Method is taken from old (possibly destroyed) `po`
PFN_DrvResetPDEV rfn = po->ppfn[INDEX_DrvResetPDEV];
if (rfn != NULL)
{
(*rfn)(po->dhpdev, poNew->dhpdev);
}
// [...]
}
}
}
}
// Destroy old DC
// [...]
}
从伪代码中可以看出,旧的设备上下文可以在 hdcOpenDCW 调用的用户模式回调中被释放,随后会从旧的设备上下文中获取 DrvResetPDEV 方法,并以 (po->dhpdev, poNew->dhpdev) 作为参数进行调用。
要创建并挂钩设备上下文,可以执行以下操作:
EnumPrinters 查找可用的打印机OpenPrinter、GetPrinterDriver 和 LoadLibraryExA 将打印机驱动程序加载到内存中GetProcAddress 和 DrvEnableDriver 获取打印机驱动程序的用户模式回调表VirtualProtect 取消对打印机驱动程序用户模式回调表的保护CreateDC(NULL, printerName, NULL, NULL) 为打印机创建设备上下文现在,我们便拥有了一个带有已挂钩用户模式回调的打印机设备上下文。
我们只对其中一个挂钩感兴趣,即 DrvEnablePDEV。该挂钩之所以值得关注,体现在两个方面:触发 UAF 以及控制参数,如前所述。要触发 UAF 漏洞,我们需要在挂钩内部调用 ResetDC,这将销毁旧的设备上下文。当我们从挂钩返回时,仍处于第一次 GreResetDCInternal 调用之中;紧接着,它会从已销毁的旧设备上下文中获取 DrvResetPDEV 函数指针,并以 DrvEnablePDEV 返回的两个参数——旧的 DHPDEV 和新的 DHPDEV——调用该函数。
如果你的进程以中等完整性级别运行,借助 EnumDeviceDrivers 和 NtQuerySystemInformation,KASLR 应该不构成问题。
卡巴斯基提到,原始漏洞利用代码使用 GDI 调色板对象和单个内核函数调用来实现任意内存读写。本漏洞利用代码使用一种在大型池上分配 BitMapHeader 的技术以及 RtlSetAllBits,为当前进程令牌启用所有特权。BitMapHeader 将指向当前进程令牌的 _SEP_TOKEN_PRIVILEGES。通过调用 RtlSetAllBits(BitMapHeader),即可通过单个内核函数调用为当前进程令牌启用所有特权。由此,便可滥用这些新特权来获取 SYSTEM 权限。本漏洞利用代码使用 SeDebugPrivilege 将 shellcode 注入 winlogon.exe 进程。
