Windows NTFS本地提权漏洞 CVE-2021-31956
Windows NTFS本地提权漏洞 CVE-2021-31956
hml189 看雪学苑 2023-12-02 17:59
参考链接:
https://research.nccgroup.com/2021/07/15/cve-2021-31956-exploiting-the-windows-kernel-ntfs-with-wnf-part-1/
https://research.nccgroup.com/2021/08/17/cve-2021-31956-exploiting-the-windows-kernel-ntfs-with-wnf-part-2/
https://paper.seebug.org/1743
使用PipeAttribution构造任意地址读后,修改_WNF_NAME_INSTANCE结构体内的指针_WNF_STATE_DATA实现任意地址写。
https://dawnslab.jd.com/CVE-2021-31956/
https://github.com/hzshang/CVE-2021-31956
使用使用NtQueryWnfStateData和NtUpDateWnfStateData API来造成任意地址的读写(需要构造AllocateSize和DataSize成员)。
https://bbs.kanxue.com/thread-271140.htm
https://github.com/aazhuliang/CVE-2021-31956-EXP
漏洞在windows的NTFS文件系统驱动上(C:\Windows\System32\drivers\ntfs.sys)的NtfsQueryEaUserEaList函数中。
NTFS文件系统允许为每一个文件额外存储若干个键值对属性,称之为EA(Extend Attribution) 。可以通过ZwSetEaFile为文件创建EA,ZwQueryEaFile查询文件EA。
泄露的NT5.1中有NtfsQueryEaUserEaList的源码。
NT5.1泄露的源码
https://github.com/0x5bfa/NT5.1/blob/master/Source/XPSP1/NT/base/fs/ntfs/ea.c#L1461
Ntfs IDA
NtfsQueryEaUserEaList从 循环遍历文件的每个 NTFS 扩展属性 (Ea),并根据ea_block->EaValueLength + ea_block->EaNameLength + 9的大小从 Ea 块复制到输出缓冲区。
有一个检查确保ea_block_size小于或等于out_buf_length – padding。然后,out_buf_length会减去ea_block_size及其填充的大小。填充是通过((ea_block_size + 3) 0xFFFFFFFC) – ea_block_size来计算的。因为每个EA块应该填充为32位对齐。
假设文件的扩展属性中有两个扩展属性。
正常情况下:
第一次迭代:
因此18 < out_buf_length – 0,数据将被复制到缓冲区中。
第二次迭代:
在文件中添加一个具有相同值的第二个扩展属性。
由于缓冲区太小,第二次内存复制将不会发生。
整数溢出
第一次迭代:
检查结果为:18 <= 18 – 0 // is True and a copy of 18 occurs.
第二个扩展属性具有以下值:
结果检查将是:ea_block_size <= out_buf_length – padding137 <= 0 – 2
发生下溢,137 个字节将被复制到缓冲区末尾,从而损坏相邻内存。
查看NtfsQueryEaUserEaList函数的调用者NtfsCommonQueryEa,我们可以看到输出缓冲区是根据请求的大小在分页池上分配的。
NtfsCommonQueryEa函数可通过ZwQueryEaFIle函数调用。
可以看到输出缓冲区Buffer以及该缓冲区的长度都是从用户空间传入的。这意味着我们根据缓冲区的大小控制内核空间的内存分配。
该漏洞对攻击者来说:溢出拷贝时数据和大小均可控。可以覆盖下一个内核池块。内核池分配时大小可控,并且可以进行堆布局。
Windows10引入了新的方式进行堆块管理,称为Segment Heap,具体可看以下论文:
https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf
EXP:
https://github.com/aazhuliang/CVE-2021-31956-EXP
仿照EXP改的可以触发溢出的POC。
可以看到整数下溢。
Windows10引入了新的方式进行堆块管理,称为Segment Heap。
详见:
https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf
中文翻译:
https://paper.seebug.org/1743
POOL_HEADER
POOL_HEADER 在池中,适合单个页面的所有块都以POOL_HEADER结构开头,POOL_HEADER包含分配器所需信息和Tag信息。当试图在Windows内核中利用堆溢出漏洞时,首先要覆盖的就是POOL_HEADER结构。攻击者有两个选择:重写一个正确的POOL_HEADER结构,并用来攻击下一个块的数据,或者直接攻击POOL_HEADER结构。
PoolType是一个位域,存储若干信息:使用的内存类型,可以是NonPagedPool、PagedPool、SessionPool或NonPagedPoolNx;如果分配是关键的(bit 1)并且必须成功。那么当分配失败,就会触发BugCheck;如果分配与缓存大小对齐(bit 2)如果分配使用了PoolQuota机制(bit 3)
其他未文档化的机制:
WNF
WNF Windows Notification Facitily 是 Windows 中的一个通知系统。应用程序可以订阅特定类型的事件(StateName标识),在每次状态更改时可以进行通知。
WNF 利用相关文章:
https://docplayer.net/145030841-The-windows-notification-facility.html
https://blog.quarkslab.com/playing-with-the-windows-notification-facility-wnf.html
结构体_WNF_STATE_DATA大可以由用户自定义。
用户可以通过NtCreateWnfStateName创建一个WNF对象实例,实例的数据结构为_WNF_NAME_INSTANCE;WNF对象大小为0xb8(WNF_NAME_INSTANCE + POOL_HEADER )。
NtUpdateWnfStateData可以往对象里写入数据,使用_WNF_STATE_DATA结构存储写入的内容:
通过NtQueryWnfStateData可以读取之前写入的数据,通过NtDeleteWnfStateData可以释放掉这个对象。NtDeleteWnfStateDat会调用ExpWnfDeleteStateData。
DataSize表示内存中_WNF_STATE_DATA结构的实际数据的大小,并用于NtQueryWnfStateData函数内的边界检查。_WNF_STATE_DATA结构存储写入的内容的内存复制操作发生在函数ExpWnfReadStateData中。
通过堆喷控制内存,使用NTFS的堆溢出越界写_WNF_STATE_DATA中的DataSize,接下来通过NtQueryWnfStateData实现相对偏移地址读写。
通过PipeAttribute实现任意地址读。
PipeAttribute详见:
https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf
PipeAttribute块的大小也是可控的,并且分配在分页池上,因此可以将块放置在与易受攻击的 NTFS 块或允许相对写入的 WNF 块相邻的位置。两个指针AttributeName、AttributeValue 正常情况下是指向PipeAttribute.data[]后面的,通过堆布局,将AttributeValue的指针该为任意地址,就可以实现任意地址读。遗憾的是,windows并没有提供直接更新该数据结构的功能,不能通过该方法进行任意地址写。
使用这个布局,修改PipeAttribute的Flink指针,并将其指向一个伪造的管道属性。
// 使指向下一个属性的指针在用户层overwritten_pipe_attribute->list.Flink = (LIST_ENTRY *)xploit->fake_pipe_attribute;
分页池创建管道后,用户可以向管道添加属性,同时属性值分配的大小和填充的数据完全由用户来控制。
AttributeName和AttributeValue是指向数据区不同偏移的两个指针。同时在用户层,可以使用0x110038控制码来读取属性值。AttributeValue指针和AttributeValueSize大小将被用于读取属性值并返回给用户。
属性值可以被修改,但这会触发先前的PipeAttribute的释放和新的PipeAttribute的分配。这意味着如果攻击者可以控制PipeAttribute结构体的AttributeValue和AttributeValueSize字段,它就可以在内核中任意读取数据,但不能任意写。
所以,控制Pipe_Attribute的List_next指针值,使其指向用户层的Pipe_Attribute,也就意味着用户层的PipeAttribute结构体的AttributeValue和AttributeValueSize字段我们可以任意指定,也就可以在内核中任意读取数据数据,即获得了一个任意地址读原语。
释放掉堆喷未修改的其他的Pipe_Attribute结构,使用_WNF_NAME_INSTANCE重新进行堆喷,通过局部地址读写,覆盖掉下一个Wnf结构体里的_WNF_STATE_DATA,将其指向当前进程的EPROCESS,使用NtUpdateWnfStateData操作,即可实现写操作。
_WNF_NAME_INSTANCE结构的CreatorProcess包含_EPROCESS指针。
遍历进程链表,获得pid4的token。
修改 WNF的StateData指向当前进程的token。
调用NtUpdateWnfStateData替换当前进程的token为 system的。
在卡巴发现的在野利用样本中,CVE-2021-31956利用了CVE-2021-31955来解决EPROCESS地址泄漏问题。
CVE-2021-31955漏洞是ntoskrnl.exe中的一个信息泄露漏洞。
NtQuerySystemInformation 函数返回的 SuperFetch信息类SuperfetchPrivSourceQuery中包含当前执行的进程的EPROCESS kernel 地址。
https://github.com/freeide/CVE-2021-31955-POC
看雪ID:hml189
https://bbs.kanxue.com/user-home-865065.htm
*本文为看雪论坛优秀文章,由 hml189 原创,转载请注明来自看雪社区
#往期推荐
1、2023 SDC 议题回顾 | 芯片安全和无线电安全底层渗透技术
6、2023 SDC 议题回顾 | 深入 Android 可信应用漏洞挖掘
球分享
球点赞
球在看