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 议题回顾 | 芯片安全和无线电安全底层渗透技术

2、SWPUCTF 2021 新生赛-老鼠走迷宫

3、OWASP 实战分析 level 1

4、【远控木马】银狐组织最新木马样本-分析

5、自研Unidbg trace工具实战ollvm反混淆

6、2023 SDC 议题回顾 | 深入 Android 可信应用漏洞挖掘

球分享

球点赞

球在看