0%

CVE-2023-36802 mskssrv.sys漏洞分析与利用

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函数进行了修复.

image-20231016165206495

函数名在修复版本中变更为了FindStreamObject,并且在补丁中删除掉了第二个参数偏移0x30位置等于1时的处理流程,并且将判断[rdx+0x30]是否为2,说明在当[rdx+0x30]等于1的处理流程中,这里将会引发漏洞.

image-20231016165532392

以下函数会调用FindObject

FSRendezvousServer::Close

FSRendezvousServer::ConsumeRx

FSRendezvousServer::ConsumeTx

FSRendezvousServer::DrainTx

FSRendezvousServer::PublishRx

FSRendezvousServer::PublishTx

FindObject的第二个参数传入了扩展上下文的信息.这个信息在FSRendezvousServer中进行了填充.

image-20231017162517671

image-20231017162654155

在每次调用FSRendezvousServer::InitializeContext时都会初始化一个FsRegObject,进行一些填充,其中0x30处填充为1,然后添加进链表中.

image-20231017170031228

image-20231017170048537

这是FSRendezvousServer::FindObject在漏洞补丁中被删除的部分代码,首先如果FSRegObject对象0x30处为1,

这个函数会在上下文对象的链表中去验证匹配FSStreamRegObj的对象指针.

如果匹配到,会返回true.

image-20231017163311360

返回1之后,会接着执行FSStreamReg::PublishRx函数,在此函数中,会对ring3传入的systembuffer

image-20231017165010974

分析到此处,出现一个对象类型混淆的问题,这种情况下是不是FSRegObject和FSStreamReg对象产生了混淆呢?

可以看一下FSStream的构造函数,在初始化FSStreamReg对象时,它是将偏移0x30处的值初始化为2的.但是在

FSRendezvousServer::FindObject是没有对这个类型进行判断的,只要匹配到,就会返回true

image-20231017170908646

所以在补丁代码中,新增了对类型的判断,只有类型为2时,才会去匹配,否则返回false,并且对函数名的修改.名称改为FindStreamObject,确实更加合适.

image-20231017171459435

最终它会在FSStreamReg::PublishRx中读取FSStreamObj对象时,触发漏洞.

image-20231017171725427

poc构造如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define __STREAMS__
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <stdint.h>
#include <ks.h>
#include <Dshow.h>
#include <ksproxy.h>

#pragma comment(lib, "Ksproxy.lib")
#pragma comment(lib, "ntdll.lib")
DEFINE_GUIDSTRUCT("3C0D501A-140B-11D1-B40F-00A0C9223196", KSNAME_Server);
#define KSNAME_Server DEFINE_GUIDNAMED(KSNAME_Server)
#define IOCTL_IniContextRendezv 0x2F0400
#define IOCTL_PublishRx 0x2F040C
int main()
{
HANDLE handle;
DWORD ret;
byte buf[0x100] = { 0 };

KsOpenDefaultDevice(KSNAME_Server, GENERIC_READ | GENERIC_WRITE, &handle);

*(int32_t*)buf = 1;
*(int64_t*)(buf + 0x8) = GetCurrentProcessId();
*(int64_t*)(buf + 0x10) = 1;
*(int64_t*)(buf + 0x18) = 0;
DeviceIoControl(handle, IOCTL_IniContextRendezv, &buf, sizeof(buf), &buf, sizeof(buf), &ret, 0);

memset(buf, 0, sizeof(buf));
*(int32_t*)(buf + 0x20) = 1;
*(int32_t*)(buf + 0x24) = 1;
DeviceIoControl(handle, IOCTL_PublishRx, &buf, sizeof(buf), &buf, sizeof(buf), &ret, 0);
}

image-20231018094850033

FSContext对象的大小为0x78.+poolheader对齐后在内存池中占用0x90,而FSStremReg对象为0x1d8,所以会越界访问下一个未知的内存池.

image-20231031110136491

2.漏洞利用

FSContextReg对象的大小为0x78,FSStreamReg对象大小则为0x1d8.在FSStreamReg::PublishRx中是可以产生越界的.

并且需要注意的是这2个对象是由LFH段堆分配在非分页内存池当中的.进行堆布局时需要选择一些分配在非分页内存池的内核对象.

image-20231031174435689

我在这个漏洞的资料中看到了几种利用方式,感觉都很复杂.

第一种是通过FSStreamReg::PublishRx ObfDereferenceObject将KTHREAD中的PreviousMode递减至0.

然后调用NtReadVirtualMemory和NtWriteVirtualMemory完成提权.

但是在FSStreamReg::PublishRx中调用了KeSetEvent,该偏移位置与不受控制,是一个无效指针,会导致蓝屏.

需要让它一直处于循环中.另一个线程去读取令牌和覆盖.

image-20231101101515075

image-20231101102018354

最近还看到了一种通过ioring的利用方式,比较简单一点.在FSFrameMdl::UnmapPages中,会传入FSStreamObj_0x198位置的对象,将它偏移0x10处4字节修改为2.

ioring的利用方式非常灵活,即使只能修改1个字节也能完成利用,在CVE-2023-21768中就是这样完成利用的.

image-20231102161459886

但是,在执行到FSFrameMdl::UnmapPages时,还需要解决一些问题,因为它前面存在了一些判断,要控制传入buf中偏移0x20处的值与streamObj偏移0x198+0x20处的值相等,它才会执行,这个值位于堆布局后的ioring内存池的最后几个字节和下个内存池开头几个字节.

image-20231103154332652

解决这个问题只要在申请ioring对象前,先通过命名管道进行一次堆喷,让命名管道中数据可控,然后释放,ioring申请在这块池中,最后几个字节就可知了,就可以通过验证,执行到UnmapPages.

至于内存池的布局,因为是非分页内存池,比较通用的方式是使用命名管道进行喷射.

利用思路如下:

1.通过命名管道进行堆布局,命名管道中数据为0.创建ioring对象

2.在内存池中构造如下图的布局

3.在ring3获取ioring对象的内核地址.

4.将ioring对象RegBuffersCount的地址写入命名管道中

5.触发漏洞将RegBuffersCount修改为2

6.重新进行内存布局,将RegBuffers的地址写入命名管道中

7.再次触发漏洞修改RegBuffers高位为2

8.通过原语进行任意地址读写,替换system token提权.

image-20231102164540295

//todo exp非常不稳定,需要优化下….