该漏洞与Windows窗口管理和图形化设备接口相关(Win32kfull.sys),通过该漏洞可以让普通权限用户提权到system权限。
作者:维阵漏洞研究员--hk
概念说明
1、UAF漏洞
UAF漏洞全称是use after free,free是指函数是在堆上动态分配空间后不再使用该数据从而被回收。但是由于程序员的一些不适当的操作,会导致攻击者能够操控已经被释放的区域,从而执行一些byte codes。
利用uaf漏洞主要是注意几点:
1)在free()函数被调用回收buffer之后,指向该地址的指针是否被重置。
2)后续是否存在malloc()函数可以将新申请分配的空间分配到之前被free()回收的buffer区域。
3)利用没有被重置的指针进行攻击
2、数据结构
我们先了解一下相关结构体的定义,参考自NT4.0的源码dcobj.hxx#L97、dcobj.hxx#L236、dcobj.hxx#L1282、dcobj.hxx#L1686,另外HDC犹如其名,handle to device context,图形设备信息对象的句柄。
3、C++ namespace(命名空间)关键字
在C++中,您可能会写一个名为xyz()的函数,在另一个可用的库中也存在一个相同的函数xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz()函数。因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
定义命名空间:
namespace namespace_name { // 代码声明 }
引用命名空间中的变量:
name::code; // code 可以是变量或函数
漏洞背景
2021年10月12日,卡巴斯基博客公开披露了在8月下旬捕获到的Windows内核提权0day漏洞的相关信息。该漏洞与Windows窗口管理和图形化设备接口相关(Win32kfull.sys)。通过该漏洞可以让普通权限用户提权到system权限。
漏洞现状
以下版本均受影响。卡巴斯基已经发现在野利用,且官方已发布补丁,请及时安装相关补丁。
Windows Server, version2004/20H2(Server Core Installation)
Windows 10 Version1607/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
漏洞成因
win32kfull的NtGdiResetDC()函数中存在一个use after free漏洞,攻击者可以利用该漏洞将权限提升到NT AUTHORITY\SYSTEM的权限。
由于此函数调用hdcOpenDCW(),该函数执行用户模式回调,因此存在该缺陷。在此回调期间,攻击者可以使用与之前相同的句柄再次调用NtGdiResetDC()函数,这将导致该句柄引用的PDC对象被释放。
关于对象被释放这一点,在微软官方的文章中提到:“调用CreateDC的线程拥有创建的HDC。当这个线程被销毁时,HDC不再有效。因此,如果您创建HDC并将其传递给另一个线程,然后退出第一个线程,第二个线程将无法使用HDC。”
然后,攻击者可以用自己的对象替换句柄引用的内存,然后将执行传递回原始NtGdiResetDC()调用,该调用现在将在没有适当验证的情况下使用攻击者的对象。这可以允许攻击者操纵内核的状态,并结合其他利用技术,以NT AUTHORITY\SYSTEM的身份获得代码执行。
在win32kfull!NtGdiResetDC中调用了win32kfull!GreResetDCInternal:
在win32kfull!GreResetDCInternal中调用了用户态的win32kbase!hdcOpenDCW:
在win32kfull!GreResetDCInternal的85行,v11+0xAB8可以设置为我们想要执行的函数,同时v11+0x708和(new_dcobj[0] + 6) + 0x708i64两个入参的值可以在用户态函数中被修改。内核函数接受两个用户态的参数,因此可以利用此漏洞造成任意地址读写。
漏洞利用
根据卡巴斯基的揭露,在执行ResetDC的回调函数时,对相同的句柄再次调用ResetDC,即可触发漏洞。利用此漏洞需要使用GDI palette对象和一个内核函数达到任意地址读写。可以利用NtQuerySystemInformation和EnumDeviceDrivers去泄露内核模块地址,最终可以进行权限提升。为了便于理解,下文将结合exp的内容进行说明。
步骤0、获取exp自身的token对象地址
准备后续提权所需的信息。先获取exp自身的token、特权信息在内核中的地址等信息。
获取exp自身的token方法如下:
进而筛选出PoolFlag=“ThNm”的堆。
安装Windows WDK后会自动生成一个pooltag.txt。pooltag.txt里有poolflag相对应的内容。如下图所示,不同的poolfalg标志代表了不同的数据类型。
步骤1、hook对应的函数
在下图736行中,调用SetupUsermodeCallbackHook()函数来hook可用打印机驱动的回调表。
hook步骤:
1、枚举可用打印机并获取驱动信息。
2、遍历每个驱动,如果找到目标函数则修改回调表。
步骤2、创建一个全局的DC对象并保存其句柄
步骤3、调用ResetDC函数
调用ResetDC函数,并向其传递全局DC的句柄。ResetDC函数执行系统调用NtDgiResetDC及其内部函数GreResetDCInternal并取得传入的HDC所对应的PDC对象,然后会调用hdcOpenDCW函数。
步骤4、执行hook函数
⚠️malloc()函数存在一个特性是会将新申请分配的空间分配到之前被free()回收的buffer区域。
⚠️free()函数不会处理申请的内存空间的内容。所以数据会被保留下来。
调用ResetDC函数后会执行回调函数,由于已经hook对应的回调函数,所以代码流程进入HOOK函数。
如上图所示191行,再次执行ResetDC函数并传入全局的HDC值,由于第188行已经把globals::should_trigger设为true,所以会按照正常的流程执行。
此时在GreResetDCInternal函数内会对该值对应的PDC对象进行释放,造成该对象结构的错误。
步骤5、第二次ResetDC调用完成
完成调用后继续运行到下图所示的70行。
对应的汇编代码如下:
_guard_dispatch_icall_nop的操作是jmp rax:
向上查看,可以看到rax=*(rbx+0xAD0):
而RBX寄存器的值则是使用由用户层传入的HDC创建的DCOBJ对象的指针。此时,所对应的PDC对象已经在第四步的过程中被释放掉了,但是由于未对该对象进行判断,此时可以进行任意内核函数的调用。整个过程如下图所示:
由于前期已经设置token的0x40位置的Privileges,从而给winlogon进程添加SE_DEBUG_PRIVILEGE权限。
通过获取TheadName从而来泄露其内核地址空间,进而调用RtlSetAllBits函数实现对Fake_RtlBitMapAddr中BitMapHeader buffer的设置。通过申请堆的大小和UAF中堆的大小相同,那么就可能申请到我们的这块内存,从而进行堆喷射。
如下图所示,正好获取到构造好了这块内存中的数据,最终实现指针的利用,从而达到提权的目的。
步骤6、向winlogon注入启动cmd的shellcode
视频演示
https://www.bilibili.com/video/BV1nM4y1P7nC/
参考链接:
1、https://hack-big.tech/2019/01/24/uaf漏洞原理实例浅析/
2、https://www.kaspersky.com/blog/mysterysnail-cve-2021-40449/42448/
3、https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-40449
4、https://www.cve.org/CVERecord?id=CVE-2021-40449
5、https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createdca
6、https://ti.qianxin.com/blog/articles/CVE-2021-40449-vulnerability-recurrence-process/
- 本文作者: 极光无限
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/932
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!