CVE-2023-36802 mskssrv.sys漏洞分析与利用
CVE-2023-36802是9月被修复的一个mskssrv.sys权限提升漏洞,与我上一篇分析的漏洞CVE-2023-29360 是同一模块的.CVE-2023-29360在10.0.22621.1848版本中被修复.
这个漏洞在win11 22h2补丁 KB5030219 发布后,10.0.22621.2283版本中被修复.
分析环境是win11 22h2.
1.漏洞分析
通过对比2个版本的文件,发现在补丁中主要对FSRendezvousServer::FindObject函数进行了修复.
函数名在修复版本中变更为了FindStreamObject,并且在补丁中删除掉了第二个参数偏移0x30位置等于1时的处理流程,并且将判断[rdx+0x30]是否为2,说明在当[rdx+0x30]等于1的处理流程中,这里将会引发漏洞.
以下函数会调用FindObject
FSRendezvousServer::Close
FSRendezvousServer::ConsumeRx
FSRendezvousServer::ConsumeTx
FSRendezvousServer::DrainTx
FSRendezvousServer::PublishRx
FSRendezvousServer::PublishTx
FindObject的第二个参数传入了扩展上下文的信息.这个信息在FSRendezvousServer中进行了填充.
在每次调用FSRendezvousServer::InitializeContext时都会初始化一个FsRegObject,进行一些填充,其中0x30处填充为1,然后添加进链表中.
这是FSRendezvousServer::FindObject在漏洞补丁中被删除的部分代码,首先如果FSRegObject对象0x30处为1,
这个函数会在上下文对象的链表中去验证匹配FSStreamRegObj的对象指针.
如果匹配到,会返回true.
返回1之后,会接着执行FSStreamReg::PublishRx函数,在此函数中,会对ring3传入的systembuffer
分析到此处,出现一个对象类型混淆的问题,这种情况下是不是FSRegObject和FSStreamReg对象产生了混淆呢?
可以看一下FSStream的构造函数,在初始化FSStreamReg对象时,它是将偏移0x30处的值初始化为2的.但是在
FSRendezvousServer::FindObject是没有对这个类型进行判断的,只要匹配到,就会返回true
所以在补丁代码中,新增了对类型的判断,只有类型为2时,才会去匹配,否则返回false,并且对函数名的修改.名称改为FindStreamObject,确实更加合适.
最终它会在FSStreamReg::PublishRx中读取FSStreamObj对象时,触发漏洞.
poc构造如下
1 |
|
FSContext对象的大小为0x78.+poolheader对齐后在内存池中占用0x90,而FSStremReg对象为0x1d8,所以会越界访问下一个未知的内存池.
2.漏洞利用
FSContextReg对象的大小为0x78,FSStreamReg对象大小则为0x1d8.在FSStreamReg::PublishRx中是可以产生越界的.
并且需要注意的是这2个对象是由LFH段堆分配在非分页内存池当中的.进行堆布局时需要选择一些分配在非分页内存池的内核对象.
我在这个漏洞的资料中看到了几种利用方式,感觉都很复杂.
第一种是通过FSStreamReg::PublishRx ObfDereferenceObject将KTHREAD中的PreviousMode递减至0.
然后调用NtReadVirtualMemory和NtWriteVirtualMemory完成提权.
但是在FSStreamReg::PublishRx中调用了KeSetEvent,该偏移位置与不受控制,是一个无效指针,会导致蓝屏.
需要让它一直处于循环中.另一个线程去读取令牌和覆盖.
最近还看到了一种通过ioring的利用方式,比较简单一点.在FSFrameMdl::UnmapPages中,会传入FSStreamObj_0x198位置的对象,将它偏移0x10处4字节修改为2.
ioring的利用方式非常灵活,即使只能修改1个字节也能完成利用,在CVE-2023-21768中就是这样完成利用的.
但是,在执行到FSFrameMdl::UnmapPages时,还需要解决一些问题,因为它前面存在了一些判断,要控制传入buf中偏移0x20处的值与streamObj偏移0x198+0x20处的值相等,它才会执行,这个值位于堆布局后的ioring内存池的最后几个字节和下个内存池开头几个字节.
解决这个问题只要在申请ioring对象前,先通过命名管道进行一次堆喷,让命名管道中数据可控,然后释放,ioring申请在这块池中,最后几个字节就可知了,就可以通过验证,执行到UnmapPages.
至于内存池的布局,因为是非分页内存池,比较通用的方式是使用命名管道进行喷射.
利用思路如下:
1.通过命名管道进行堆布局,命名管道中数据为0.创建ioring对象
2.在内存池中构造如下图的布局
3.在ring3获取ioring对象的内核地址.
4.将ioring对象RegBuffersCount的地址写入命名管道中
5.触发漏洞将RegBuffersCount修改为2
6.重新进行内存布局,将RegBuffers的地址写入命名管道中
7.再次触发漏洞修改RegBuffers高位为2
8.通过原语进行任意地址读写,替换system token提权.
//todo exp非常不稳定,需要优化下….