CVE-2021-40449|CVE-2021-40449 NtGdiResetDC UAF
背景
??CVE-2021-40449是一个存在于Win32k内核驱动中的UAF漏洞。该漏洞在2021年八月下旬九月上旬被Kaspersky发现用于野外攻击活动中。通过Hook win32k驱动执行NtGdiResetDC
过程中发生的用户模式回调,完成对目标对象的释放和占用,最终实现指定内核函数的调用,以进行内核内存的读写操作,修改利用对象的Token权限,实现EOP。
分析
??此次分析是在Windows 10 1809中进行。
??首先在用户模式调用CreateDC
时,会执行至win32k内核调用win32kfull!NtGdiResetDC
,再执行至win32kbase!hdcOpenDCW
,调用堆栈如下:
......??win32kbase!PDEVOBJ::PDEVOBJ【CVE-2021-40449|CVE-2021-40449 NtGdiResetDC UAF】??执行的用户回调主要发生在
......??win32kbase!hdcOpenDCW+0x240
......??win32kfull!GreResetDCInternal+0x11a
......??win32kfull!NtGdiResetDC+0xd6
......??nt!KiSystemServiceCopyEnd+0x25
......??win32u!NtGdiResetDC+0x14
......??gdi32full!ResetDCWInternal+0x16b
......??GDI32!ResetDCW+0x31
......??CVE_2021_40449!main
win32kbase!PDEVOBJ::PDEVOBJ
中,该函数应是一个PDEV
对象的初始化函数,和win32kfull!NtGdiResetDC
传入参数中的HDC
有关联。初始化函数中有两个用户回调:PDEVOBJ::EnablePDEV
、PDEVOBJ::CompletePDEV
。这两个用户回调主要是对HDC
中的PDEV
对象进行操作,PDEV
对象通过PDEV::Allocate
分配内存。文章图片
文章图片
??执行完初始化函数,回到
hdcOpenDCW
,继续执行至GreCreateDisplayDC
,该函数初始化一个PDC
对象,并将上面初始化的PDEV
对象的内存地址放到PDC
偏移+0x30
处。文章图片
??然后返回
PDC
0
偏移处的DC
句柄值HDC
,该值也作为win32kbase!hdcOpenDCW
的返回值,返回值win32kfull!GreResetDCInternal
。??
hdcOpenDCW
返回的HDC
传入DCOBJ::DCOBJ
,返回hdcOpenDCW
初始化的PDC
对象的内存地址。文章图片
??接着读取
PDEV
对象0xAB8
偏移处的函数指针并执行,注意此处的PDEV
并不是在上一步的hdcOpenDCW
中初始化的,而是在用户态调用ResetDC
前,调用CreateDC
生成的。为进行区分,本文中将其称为HDC_user
。??
GreResetDCInternal
的函数参数HDC_user
,同样通过DCOBJ::DCOBJ
返回PDC_user
对象,该对象偏移0x30
处为PDEV_user
对象的内存地址。??取
PDEV_user
偏移0xAB8
处函数指针,执行UMPDDrvResetPDEV
,传入参数分别为PDEV_user
和PDEV_kernel
偏移0x708
处的指针,指向各自的DEVMODE
结构,这里同样会发生一次用户态函数回调,不过该回调不进行考虑,因为此漏洞利用范围内,被利用的主要是该指针。文章图片
??完成
UMPDDrvResetPDEV
回调后,执行win32kbase!HmgSwapLockedHandleContents
,该函数会将PDC_user
和PDC_kernel
首部的HDC
值和PDC
的引用计数值
进行了互换,从而完成devmode
修改的功能。文章图片
??后面则是将两个
PDC
对象的引用计数值分别减1
,并调用win32kbase!bDeleteDCInternal
将HDC_kernel
索引到的PDC
对象偏移0x30
处指针指向的PDEV
对象引用计数值减1
,值变为0
。而又因为之前的HmgSwap
操作,这里的PDC
和PDEV
实际都是用户传入的HDC
原本指向的对象。??根据MSDN所说,“当该计数器降至零,该对象就会被释放”、“一旦句柄计数减为零,对象的名称就会从对象管理器的命名空间中删除”。意味着该对象可以被占用,而
hdcOpenDCW
中又存在用户回调,在用户回调中再对相同的HDC
执行一次ResetDC
,那么该HDC
对应PDEV
对象引用值将减为0
,占用该PDEV
对象后结束回调,回到内核。??至于漏洞的触发点,在原本的
UMPDDrvResetPDEV
调用处,该调用发生在hdcOpenDCW
之后,调用函数的地址从PDEV_user
中获取,通过占用,可以获取到修改器调用目标为一个内核读写函数。文章图片
利用 ??该UAF漏洞的利用主要为以下几个步骤:
- 使用
NtQuerySystemInformation
获取利用进程Token.Privileges
在内核中的位置; - 泄露出一个可以用于内核写的内核函数,这里比较通用是
nt!RtlSetAllBits
; - 构造一个
Fake_RTL_BITMAP
,作为nt!RtlSetAllBits
函数参数,大多使用ThreadName
的方式进行构造,不过同样也可以手动申请一片用户态内存进行构造; - HOOK用户回调
DrvEnablePDEV
(HookDrvCompletePDEV
虽然可以成功占用,但执行不到漏洞触发点),在Hook函数中对相同HDC
再执行一次ResetDC
,返回后使用构造的Fake Palette
去占用被释放的PDEV
对象,然后结束当前回调; - 漏洞触发,当前进程权限位全部被启用,完成提权。
文章图片
??PDEV对象占用成功后,完成回调,返回
GreResetDCInternal
,可以看到成功地调用到nt!RtlSetAllBits
。文章图片
??
nt!RtlSetAllBits
中仅将rcx
作为参数,而漏洞触发处的第一个参数rcx
同样可以通过占用指定。??
nt!RtlSetAllBits
中取rcx
地址0x08
偏移处的QWORD
作为写入的目标地址,而rcx
偏移0
处的DWORD
值整除0x40
后作为计数值,每次向目标地址写入rax
寄存器的值,rax
固定为0xffffffffffffffff
。文章图片
文章图片
文章图片
??POC代码。
总结 ??这次我分析这个漏洞时尝试尽量不看网上公开的POC,仅根据Kaspersky的文章寻找漏洞位置,结果花了很多时间,遇到挺多问题的。比如寻找漏洞点时,不会出现
BSOD
,并且!pool
不能马上看到对象内存状态变成free
,还是去瞄了一些公开的POC,确认自己方向没问题。??emmm最后好歹自己完成了POC,虽然耗时长且代码拉胯,相比那些优秀的POC通用性低,但是收获也很多,起码漏洞前后附近的代码各个角落都翻了一遍,而且一些坑下次可以避免。
参考 [1] MysterySnail attacks with Windows zero-day
[2] CVE-2021-40449 Exploitation
推荐阅读
- Spring认证指南-了解如何使用 Spring Boot Actuator 创建 RESTful Web 服务
- Unity3D|【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)
- python如何攻击网站_GitHub - wuhuanyan/buy_pig_plan_python: 用Python写的『电话攻击,电话轰炸,电话炸弹』...
- python京东抢购|python京东抢购 github_GitHub - DevGuan/jd-autobuy: Python爬虫,京东自动登录,在线抢购商品...
- Java不支持协程(那是你不知道Quasar!)
- Typescript 一些令人又爱又恨的内容 — Type Guard、Narrowing
- Quarkus中的依赖注入DI和面向切面aop编程
- 笔记|UA到底是什么
- Opening|Opening Doors within 敞開內心之門 January 15
- React-kua:基于Web的跨平台方案