CVE-2021-40449 Win32k提权漏洞及POC分析
字数 1509 2025-08-29 08:31:41

CVE-2021-40449 Win32k提权漏洞分析与利用

漏洞概述

CVE-2021-40449是卡巴斯基实验室在2021年8月下旬到9月上旬发现的Windows提权漏洞,存在于win32kfull.sys驱动中。攻击者可以利用该漏洞在Windows系统中完成从普通用户权限(User)到系统权限(System)的权限提升。

基本概念

内核对象

  • 内核对象存在于内核空间,只能由内核分配和访问
  • 应用程序通过操作系统提供的API间接操作内核对象

引用计数

  • 内核对象创建时引用计数为1
  • 调用CloseHandle()时引用计数减1
  • 引用计数为0时对象被释放
  • 类似于Java GC的引用计数法

句柄(Handle)

  • 应用程序操作内核对象的间接标识符
  • 内核对象创建后,操作系统返回句柄给应用程序
  • 应用程序通过API操作句柄,内核将句柄映射到实际的内核对象

句柄表

  • 进程初始化时分配句柄表
  • 创建内核对象时,内核在句柄表中设置对象指针
  • 返回句柄作为索引

DC(Device Context)

  • 内核对象,全称设备上下文对象
  • 用于图形设备接口(GDI)操作

HDC

  • DC对象的句柄

释放后重用(Use-After-Free)

  • 内存被释放后变为空闲状态
  • 如果立即申请内存,可能分配到刚释放的内存
  • 如果释放前有指针指向该内存,释放后指针未置空
  • 后续使用该指针可能导致非预期行为

漏洞分析

漏洞位置

漏洞存在于win32kfull!GreResetDCInternal函数中,该函数会获取DC对象内的函数指针并执行,但未检查DC对象是否有效。

漏洞原理

  1. 函数获取DC对象中的函数指针并执行
  2. 未检查DC对象是否已被释放
  3. 攻击者可以在调用函数指针前释放DC对象
  4. 重新申请内存并构造恶意函数指针
  5. 导致任意内核函数执行

关键代码

v10 = *(_QWORD *)(v8 + 48);  // 获取DC对象指针
v15 = *(void (__fastcall **)(_QWORD, _QWORD))(*v10 + 2768);  // 获取函数指针
if (v15)
    v15(*(_QWORD *)(v10 + 1824), *(_QWORD *)(v14[6] + 1824i64));  // 调用函数指针

漏洞触发流程

  1. 调用ResetDC函数进入内核
  2. 内核调用NtGdiResetDCGreResetDCInternal
  3. GreResetDCInternal调用hdcOpenDCW
  4. hdcOpenDCW执行用户模式回调函数
  5. 在回调函数中再次调用ResetDC
  6. 第二次调用GreResetDCInternal时释放DC对象
  7. 重新申请内存并构造恶意函数指针
  8. 第一次调用继续执行被篡改的函数指针

漏洞利用

利用条件

  1. 释放DC对象
  2. 重新申请原DC对象的内存空间
  3. 构造内存布局,修改函数指针

利用步骤

  1. 第一次调用ResetDC:

    • 创建DC对象和DCO对象
    • 获取DC对象中的函数指针
  2. hdcOpenDCW回调中:

    • 第二次调用ResetDC
    • 释放原始DC对象
  3. 内存布局:

    • 使用CreatePalette申请释放的内存
    • 精确构造内存覆盖函数指针
  4. 执行:

    • 第一次调用继续执行被篡改的函数指针
    • 实现任意内核函数调用

关键函数调用链

ResetDC (用户态)
  -> NtGdiResetDC (内核)
    -> GreResetDCInternal (漏洞函数)
      -> hdcOpenDCW
        -> 用户模式回调
          -> ResetDC (第二次调用)
            -> NtGdiResetDC
              -> GreResetDCInternal
                -> 释放DC对象
      -> 调用DC对象函数指针 (此时已被篡改)

补丁分析

补丁增加了对DC对象引用计数的检查:

if (*(_WORD *)(v30 + 6) > 1u) {
    // 引用计数异常处理
    EngSetLastError(6);
    // ...
}

补丁逻辑:

  1. 正常情况下DC对象引用计数不应大于1
  2. 如果发现引用计数异常,抛出错误
  3. 防止在回调中重复调用导致的UAF

调试分析

关键断点

win32kfull!NtGdiResetDC
win32kfull!NtGdiResetDC+0xc1 (调用GreResetDCInternal)
win32kfull!GreResetDCInternal+0x3a (调用DCOBJ构造函数)
win32kfull!GreResetDCInternal+0x116 (调用hdcOpenDCW)
win32kfull!GreResetDCInternal+0x136 (第二次DCOBJ)
win32kfull!GreResetDCInternal+0x1b5 (调用DC对象函数指针)
win32kfull!GreResetDCInternal+0x1d1 (调用HmgSwapLockedHandle)
win32kfull!GreResetDCInternal+0x20d (调用bDeleteDCInternal)

内存布局计算

函数指针 = ((DCO + 0x30) + 0xAD0)
其中DCO + 0x30指向DC对象

内存申请

使用CreatePalette申请释放的DC对象内存:

  • DC对象大小通常为0xE30
  • CreatePalette会调用ExAllocatePoolWithTag申请接近大小的内存

参考链接

  1. https://www.secrss.com/articles/35266
  2. https://mp.weixin.qq.com/s/AcFS0Yn9SDuYxFnzbBqhkQ
  3. https://bbs.pediy.com/thread-269930.htm
CVE-2021-40449 Win32k提权漏洞分析与利用 漏洞概述 CVE-2021-40449是卡巴斯基实验室在2021年8月下旬到9月上旬发现的Windows提权漏洞,存在于win32kfull.sys驱动中。攻击者可以利用该漏洞在Windows系统中完成从普通用户权限(User)到系统权限(System)的权限提升。 基本概念 内核对象 内核对象存在于内核空间,只能由内核分配和访问 应用程序通过操作系统提供的API间接操作内核对象 引用计数 内核对象创建时引用计数为1 调用CloseHandle()时引用计数减1 引用计数为0时对象被释放 类似于Java GC的引用计数法 句柄(Handle) 应用程序操作内核对象的间接标识符 内核对象创建后,操作系统返回句柄给应用程序 应用程序通过API操作句柄,内核将句柄映射到实际的内核对象 句柄表 进程初始化时分配句柄表 创建内核对象时,内核在句柄表中设置对象指针 返回句柄作为索引 DC(Device Context) 内核对象,全称设备上下文对象 用于图形设备接口(GDI)操作 HDC DC对象的句柄 释放后重用(Use-After-Free) 内存被释放后变为空闲状态 如果立即申请内存,可能分配到刚释放的内存 如果释放前有指针指向该内存,释放后指针未置空 后续使用该指针可能导致非预期行为 漏洞分析 漏洞位置 漏洞存在于 win32kfull!GreResetDCInternal 函数中,该函数会获取DC对象内的函数指针并执行,但未检查DC对象是否有效。 漏洞原理 函数获取DC对象中的函数指针并执行 未检查DC对象是否已被释放 攻击者可以在调用函数指针前释放DC对象 重新申请内存并构造恶意函数指针 导致任意内核函数执行 关键代码 漏洞触发流程 调用 ResetDC 函数进入内核 内核调用 NtGdiResetDC 和 GreResetDCInternal GreResetDCInternal 调用 hdcOpenDCW hdcOpenDCW 执行用户模式回调函数 在回调函数中再次调用 ResetDC 第二次调用 GreResetDCInternal 时释放DC对象 重新申请内存并构造恶意函数指针 第一次调用继续执行被篡改的函数指针 漏洞利用 利用条件 释放DC对象 重新申请原DC对象的内存空间 构造内存布局,修改函数指针 利用步骤 第一次调用 ResetDC : 创建DC对象和DCO对象 获取DC对象中的函数指针 在 hdcOpenDCW 回调中: 第二次调用 ResetDC 释放原始DC对象 内存布局: 使用 CreatePalette 申请释放的内存 精确构造内存覆盖函数指针 执行: 第一次调用继续执行被篡改的函数指针 实现任意内核函数调用 关键函数调用链 补丁分析 补丁增加了对DC对象引用计数的检查: 补丁逻辑: 正常情况下DC对象引用计数不应大于1 如果发现引用计数异常,抛出错误 防止在回调中重复调用导致的UAF 调试分析 关键断点 内存布局计算 内存申请 使用 CreatePalette 申请释放的DC对象内存: DC对象大小通常为0xE30 CreatePalette 会调用 ExAllocatePoolWithTag 申请接近大小的内存 参考链接 https://www.secrss.com/articles/35266 https://mp.weixin.qq.com/s/AcFS0Yn9SDuYxFnzbBqhkQ https://bbs.pediy.com/thread-269930.htm