不久之前,微软修复了Microsoft Kernel Streaming Server(一个Windows内核组件,用于相机设备虚拟化和共享)中的一个关键漏洞,即CVE-2023-36802。该漏洞是一个内核漏洞,将允许本地攻击者将权限升级为SYSTEM。
在这篇文章中,我们将对Windows内核安全进行剖析,然后会详细介绍该漏洞,并讨论跟该漏洞相关的类文件,以及该漏洞的利用探索过程。
Microsoft Kernel Streaming Server(mskssrv.sys)是Windows多媒体框架服务中的一个组件,该服务可以虚拟化相机设备,并允许在多个应用程序之间共享该设备。
在发现了漏洞CVE-2023-29360(一个TPM驱动程序漏洞)后,我们便开始探索这一个攻击面。于是乎,我们便开始在反汇编工具中分析相关的二进制文件。通过分析后,我们不仅发现该漏洞是一个逻辑错误漏洞,而且这个漏洞很容易被触发,利用起来也相对简单。
首先,我们需要能够从一个用户空间应用程序来访问到驱动程序。而存在漏洞的函数功能可以通过驱动程序的DispatchDeviceControl程序来访问,这也就意味着,我们可以通过向驱动程序发送一个IOCTL来访问该功能。要实现这一点,我们需要通过使用设备路径调用CreateFile来获得驱动程序设备的句柄。一般来说,设备名称或路径的识别相对容易:在驱动程序中找到对IoCreateDevice的调用,并检查包含设备名称的第三个参数即可。
我们可以看到,设备名称的参数为NULL,调用函数的名称表明mskssrv是PnP驱动程序,对IoAttachDeviceToDeviceStack的调用表明创建的设备对象是设备堆栈的一部分。实际上,这意味着当I/O请求发送到设备时,会调用多个驱动程序。对于PnP设备,需要设备接口路径才能访问该设备。
使用WinDbg内核调试器,我们可以看到哪些设备属于mskssrv驱动程序和设备堆栈:
如上所示,mskssrv的设备连接到了属于swenum.sys驱动程序的下游设备对象上,并且拥有一个属于ksthunk.sys的上游设备。
从设备管理器中,我们可以找到目标设备实例ID:
我们现在有足够的信息来使用配置管理器或SetupApi函数获取设备接口路径了,使用检索到的设备接口路径之后,我们就可以打开设备的句柄了。这样一来,我们就可以在mskssrv.sys中触发代码执行。创建设备时,会调用驱动程序的PnP调度创建函数。为了触发额外的代码执行,我们可以发送IOCTL与将在驱动的调度设备控制功能中执行的设备进行对话。
我们当时在分析驱动程序通信机制时发现了这个漏洞,首先我们需要看一看用户模式下的两个组件,即fsclient.dll和frameserver.dll。下面给出的是PublishRx IOCTL函数:
从FsContext2检索流对象后,将会调用FSRendezvousServer::FindObject以验证指针是否与全局FSRendezvousServer存储的两个列表中的对象相匹配。一开始,我们假设这个函数有某种方法来验证请求的对象类型。但是,如果指针位于上下文对象列表或流对象列表中,则函数返回TRUE。请注意,这里并没有向FindObject传递关于对象类型的信息。这也就意味着,上下文对象可以作为流对象传递,这就是一个对象类型混淆漏洞,在对流对象进行操作的每个IOCTL函数中都存在该漏洞。为了修复该漏洞,微软使用FSRendezvousServer::FindStreamObject替换了FSRendezvousServer::FindObject。
由于上下文注册对象小于(0x78字节)流注册对象(0x1D8字节),因此可以在越界内存上执行流对象操作:
为了利用漏洞原语,我们需要能够控制访问的越界内存,这里可以通过触发在易受攻击对象的同一内存区域中分配许多对象来实现,这种技术被称为堆喷射或池喷射。易受攻击的对象是在非分页碎片堆池中分配的。我们可以使用Alex Ionescu的经典喷射技术来喷射缓冲区,这将允许我们对0x30字节DATA_QUEUE_ENTRY Header以下的内存内容进行完全控制。通过使用这种技术进行喷射,我们可以拿到如下图所示的内存结构:
使用选定的池喷射方法,我们可以控制0xC0-0x10F和0x150-0x19F范围内的对象偏移量。
我们在PublishRx IOCTL中找到了一个非常合适的常量写入原语,这个原语可用于在任意内存地址写入常量值。下面是FSStreamReg::PublishRx函数的部分代码段:
流对象包含偏移量为0x188的列表Header,该列表Header描述了FSFrameMdl对象列表。在上面的反编译代码段中,会迭代此列表,如果FSFrameMdl对象中的标记值与应用程序传入系统缓冲区中的标记相匹配,则会调用函数FSFrameMdl::UnmapPages。
使用上述漏洞利用原语,可以完全控制FSFrameMdlList,从而可以完全控制pFrameMdl指向的FsFrameMdl对象。下面给出的是UnmapPages部分代码段:
在上面反编译函数的最后一行,常数值2被写入了这个FSFrameMdl对象的可控制偏移值。这种常量写入可以与I/O环技术结合使用,以实现任意内核读写和权限提升。
虽然我选择了使用常量写入原语,但另一个有用的漏洞利用原语也出现在了这个函数中。MmUnmapLockedPages调用的参数BaseAddress和MemoryDescriptorList都是可控的,这样就可以取消映射任意虚拟地址处的映射,并构造类似释放后使用(UAF)的原语。
从该漏洞中我们可以总结出一个经验,即不要对所执行的检查做出假设。这种情况非常常见,也是很多开发人员容易忽视的地方,特别是在处理进程间通信的驱动程序中,很可能还存在该漏洞的各种变种版本,因此更加需要注意