freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

CVE-2023-29360 mskssrv.sys漏洞分析与利用
2023-10-16 16:19:38

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

CVE-2023-29360是微软驱动程序mskssrv.sys的一个权限提升漏洞,mskssrv.sys是微软流媒体服务相关组件,漏洞在6月份已被修复.本文分析调试环境为win11 22h2 22000.51

1.漏洞分析

该漏洞的修复版本是10.0.22621.1848,之前一个版本为10.0.22621.1,两个版本改动非常小,只有FsAllocAndLockMdl函数发生了变化.

image

在FsAllocAndLockMdl函数中调用MmProbeAndLockPages时,第二个参数rdx中的值由原来的0,修改为了1.也就是从KernelMode修改为了UserMode.

通过补丁来看,可以基本确定此函数引发了漏洞.

image

void MmProbeAndLockPages(
  [in, out] PMDL            MemoryDescriptorList,
  [in]      KPROCESSOR_MODE AccessMode,
  [in]      LOCK_OPERATION  Operation
);

MmProbeAndLockPages函数用于探测指定的虚拟内存页,使其常驻,并将其锁定在内存中(比如在驱动开发ring3和ring0进行数据通信时,使用MDL锁定内存,可以在直接进行内存访问,不需要进行额外的内存copy)这样可以确保在设备驱动程序中仍在使用这些页时,停止分页产生的页交换,并且页无法被释放和重新分配.

MemoryDescriptorList:指向虚拟内存缓冲区的指针,如果函数成功将页面锁定在内存中,则会更新MDL以描述底层物理页面

AccessMode:检测参数的访问模式,KernelMode:0,UserMode:1

Operation:调用者希望检测问权限并锁定页面的操作类型,在上面的调用中,这里是IoWriteAccess表示驱动可以读写缓冲区.

MDL

关于mdl,微软官方文档是这样解释的,跨连续虚拟内存地址范围的IO缓冲区可以分布在多个物理页上,而且这些页是可以不连续的.操作系统使用内存描述符表MDL,来描述这些虚拟内存缓冲区的物理页布局.

MDL由一个MDL结构组成,后面是一个数据数组,用于描述Io缓冲区所在的物理内存,大小随着MDL所描述的io缓冲区的特性变化.

MDL的结构体,驱动程序只能访问其中的Next和MdlFlags成员,其余成员是不透明的,无法访问.

typedef struct _MDL {
  struct _MDL      *Next;//指向MDL链中下一个MDL的指针
  CSHORT           Size;
  CSHORT           MdlFlags;//锁定状态
  struct _EPROCESS *Process;
  PVOID            MappedSystemVa;
  PVOID            StartVa;
  ULONG            ByteCount;
  ULONG            ByteOffset;
} MDL, *PMDL;

MDL的创建通过函数IoAllocateMdl进行分配,释放则使用IoFreeMdl.

在mskssrv.sys的函数FsAllocAndLockMdl中,对Mdl相关操作做了封装,内部调用IoAllocateMdl和MmProbeAndLockPages锁定指定长度的虚拟内存.

image

看一下FsAllocAndLockMdl的调用链.

SrvDispatchIoControl

FSRendezvousServer::PublishTx

FSStreamReg::PublishTx

FSFrameMdl::AllocateMdl

FsAllocAndLockMdl

在SrvDispatchIoControl中可以计算出FSRendezvousServer::PublishTx对应的io code为0x2f0408

image

然后在FSRendezvousServer::PublishTx函数中,将SystemBuffer数据传入FSStreamReg::PublishTx->FSFrameMdl::AllocateMdl->FsAllocAndLockMdl

image

SystemBuffer偏移0x88*i+0x28

image

image

从上面的流程中可知 FsAllocAndLockMdl的创建MDL时,是由ring3的SystemBuffer中一些配置参数创建的,而在FsAllocAndLockMdl中 MmProbeAndLockPages的参数却为KernelMode,因此这里的检测实际上是无效的.ring3可以传递过来一个内核的内存地址.

在修复补丁中设置为了UserMode,修复了漏洞.

2.漏洞利用

目前为止,由于MmProbeAndLockPages的错误检查,如果可以成功的调用到此函数,可以通过该漏洞创建一个内核地址的MDL.

如果有方法将这个MDL映射到ring3,那么将可以实现任意内核地址的读写,完成提权.而在这个驱动中存在通过函数

FSRendezvousServer::ConsumeTx是可以完成这个操作的,它对应的iocode为0x2f0410,在其内部,会调用MmMapLockedPagesSpecifyCache将Mdl映射到ring3.

image

image

目前为止,FSRendezvousServer::PublishTx和FSRendezvousServer::ConsumeRx函数是无法直接调用的.需要初始化一个结构,每次在调用函数时,都会通过FsGetRendezvousServer去获取这个结构,否则会返回0xC0000010.

并且传入的inputbuffer数据的结构需要逆向出来.让其可以正常的执行到目标位置

所以要通过调用一些前置的iocode来初始化.

image

首先看下FSRendezvousServer::PublishTx接收的InputBuff的结构,从判断条件可以分析出:

该结构的大小是大于0xb0的结构体.

offset_0x20-1<=0x12B

offset_0x24<=offset_0x20的

inputBuf长度>=0x88*(offset_0x20-1+0xb0)

image

在FSStreamReg::CheckRecycle中对以下偏移位置进行计算,0x5c,0x7c,0x24.,

在FSStreamReg::PublishTx函数中,可以确定偏移0x24处应该是一个循环次数,偏移0x70处是一个大于0的值.

然后将偏移0x28指针和申请的内存池传入FSFrameMdl::AllocateMdl.最后将这个内存池添加进链表.

image

image

然后在FSFrameMdl::AllocateMdl中将偏移为0x38,0x48,0x58,0x68,0x78,0x88,0x98,0xa8的值写入至了前面创建的内存池中.

然后通过偏移0x70处的值执行FsAllocLockMdl,如果为1,那么会将偏移0x60值作为创建MDL的虚拟地址.

如果为4,8,那么会将偏移0x48值和偏移0x60值作为创建MDL的虚拟地址

偏移0x6c和0x5c是长度.

image

在这个函数执行完后,inputbuf种0x28之后的数据会写入到内存池中,然后添加进链表.

在FSRendezvousServer::ConsumeTx的调用过程中,会从链表中取出,并将input_buf偏移0x70处8字节数据作为MmMapLockedPagesSpecifyCache的参数,这个参数非常重要,MmMapLockedPagesSpecifyCache函数在这里将MDL描述的物理内存映射到了ring3的一个虚拟地址.最后一个参数如果被设置为了MdlMappingNoWrite标志,那么映射的ring3虚拟地址,将没有写入权限.

#define MdlMappingNoWrite       0x80000000  // Create the mapping as nowrite
#define MdlMappingNoExecute     0x40000000  // Create the mapping as noexecute

image

这里的数据是结构体偏移0x70的8字节数据,右移1d位后,取反,然后与运算0C0000010,or 0x40000010.

得出的4字节数据,最高位不为1就可以了.

这里将8字节数据第32位设置位1,即可以通过验证.

image

image

image

通过以上对其结构的分析,可以总结出如下结构体

typedef struct _EvilBuffer {
	uint64_t size;             //0x0
	uint64_t txsize;           //0x8 
	uint64_t rxsize;           //0x10
	uint32_t txcount;          //0x18
	uint32_t rxcount;          //0x1c
	uint64_t value;            //0x20
	uint64_t value2;           //0x28
	uint64_t virtualAddress1;  //0x30
	uint64_t timestamp;        //0x38
	uint64_t field9;           //0x40
	uint64_t virtualAddress2;  //0x48
	uint64_t field10;          //0x50
	uint64_t size1;            //0x58
	uint64_t virtualAddress3;  //0x60
	uint64_t size2;            //0x68
	uint32_t Priority;         //0x70
	uint32_t flag;             //0x74
	uint64_t resolution;       //0x78
	uint64_t field11;          //0x80
	uint64_t field12;          //0x88
	uint64_t format;           //0x90
	uint64_t field13;          //0x98
	uint64_t dimension;        //0xA0
	uint64_t field14;          //0xA8
	uint8_t reserved2[0x110];  //0xB0
} EvilBuffer;

通过这个结构体调用FSRendezvousServer::PublishTx创建MDL,然后再调用FSRendezvousServer::ConsumeTx映射回ring3就可以通过写入任意内核地址完成提权了.

至于前置通过FSRegisterStream,FSInitializeStream,FSInitializeContextRendezvous,FSRendezvousServerRegisterContext进行初始化需要的一些结构体就不再另行分析了.

这个漏洞原理并不复杂,只是想要完成利用,需要逆向很多结构,设置一些固定的标志位...

# 漏洞
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录