Streaming vulnerabilities from Windows Kernel - Proxying to Kernel - Part I
===========================================================================
[](https://devco.re/blog/author/angelboy)[Angelboy](https://devco.re/blog/author/angelboy)2024-08-23
![](https://images.seebug.org/1728983391750-w331s)
***
[English Version](https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1-en/) ,[中文版本](https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1/)
在过去的几十年中Windows Kernel 的漏洞层出不穷,热门的攻击面逐渐从Win32k 慢慢转移到CLFS (Common Log File System) 上。微软也持续且积极地修补这些漏洞,使得这些元件越来越安全。而下一个热门的目标会是哪个元件呢?去年开始,MSKSSRV (Microsoft Kernel Streaming Service) 成为骇客喜爱的目标之一。这个驱动程式小到可以在几天内完成分析。这是否意味着可能不太会有新的漏洞了?
在这篇研究将讲述一个长期被忽视的攻击面,让我们在两个月内就找出了超过10 个漏洞。此外,也将深入探讨了一种Proxy-Based 的逻辑漏洞类型,使我们可以忽略掉大多数的检查,最终成功在Pwn2Own Vancouver 2024 中,攻下Windows 11 的项目。
这份研究将分成数个部分来撰写,分别讲述不同的漏洞类型及漏洞型态,亦发表于[HITCON CMT 2024](https://hitcon.org/2024/CMT/agenda/)中。
Start from MSKSSRV
------------------
> 对于一项漏洞研究来说,从历史的漏洞看起,是不可或缺的。
起初,我们为了挑战Pwn2Own Vancouver 2024 中Windows 11 的项目,开始从过去的Pwn2Own 以及近期in-the-wild 的漏洞中开始审视,寻找可能的攻击面。沿着历史轨迹可以得知,过去主要负责GDI 相关操作的Win32K 一直是个很热门的目标,从2018 年以来,CLFS (Common Log File System) 也渐渐成为了热门目标之一。这两个元件都非常复杂,并且直到现在仍然有不少新漏洞出现,但要熟悉这两个元件需要花不少时间,同时也有许多研究员在看这两个元件,所以最终我们没有先选择分析他们。
去年[Synacktiv](https://www.synacktiv.com/en)在Pwn2Own 2023 中,使用MSKSSRV 的[漏洞](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-29360)成功攻下Windows 11 后,便有不少人往这个元件开始看起,短时间内就又出现了[第二个漏洞CVE-2023-36802](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36802),这时[chompie](https://x.com/chompie1337)也发表了一篇[非常详细的文章](https://securityintelligence.com/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service/),讲述这个漏洞成因及其利用细节。由于这个元件非常的小,只看档案大小约略只有72 KB,可能认真看个几天就可以全部看完,因此我们便挑了MSKSSRV 来做历史漏洞分析,看看是否有机会抓出其他漏洞。
接下来我们会提一下这两个漏洞,但不会着墨过多。
### CVE-2023-29360 - logical vulnerability
第一个是Synacktiv 在Pwn2Own 2023 中所使用的漏洞 :
![](https://images.seebug.org/1728983394692-w331s)
这是一个逻辑上的漏洞。当MSKSSRV 使用[MmProbeAndLockPages](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmprobeandlockpages)锁定使用者给的记忆体位置作为FrameBuffer 时,并没有设置正确的AccessMode,导致没有检查使用者指定的位置是否属于User space。如果使用者给的是Kernel space 中的位置,它就会把指定的Kernel 位置映射到User space 给使用者用,最终导致使用者可以对Kernel 中的任意位置写入,利用上简单且非常稳定,成为了受欢迎的[漏洞之一](https://www.cisa.gov/news-events/alerts/2024/02/29/cisa-adds-one-known-exploited-vulnerability-catalog)。
更多细节可以参考Synacktiv 在HITB 2023 HKT 的[演讲](https://conference.hitb.org/hitbsecconf2023hkt/materials/D2T1%20-%20Windows%20Kernel%20Security%20-%20A%20Deep%20Dive%20into%20Two%20Exploits%20Demonstrated%20at%20Pwn2Own%20-%20Thomas%20Imbert.pdf)及[Nicolas Zilio(@Big5\_sec)](https://x.com/Big5_sec)的[部落格文章](https://big5-sec.github.io/posts/CVE-2023-29360-analysis/)
### CVE-2023-36802 - type confusion
这个漏洞则是在CVE-2023-29360 出来后没多久被许多人发现,并且在微软发布更新时,就已经侦测到利用,是个非常容易被发现的漏洞。 MSKSSRV 会先将内部使用的物件(FSContextReg、FSStreamReg)存放在[FILE\_OBJECT](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_object)的FsContext2 中,然而后续使用时并没有对FsContext2 的**型态**做检查,导致type confusion,详细内容可参考[IBM X-Force 的部落格](https://securityintelligence.com/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service/)。
至此之后,就很少有关于MSKSSRV 的相关漏洞了。
### But is that the end of it ?
然而是否这样就没洞了呢?
**而我要更准确地回答,No!**
实际上整个Kernel Streaming 就像下面这张图这样 :
![](https://images.seebug.org/1728983396729-w331s)
MSKSSRV 只是冰山一角而已,实际上还有不少潜在的元件,上图中所写的都是属于Kernel Streaming 的一部分。实际往这方向挖掘之后,最终也在这个攻击面上取得不少漏洞,就如同流水般的流出漏洞来。 ![](https://images.seebug.org/1728983399503-w331s)
顺带一提,我在写这篇部落格时,chompie 也发表了有关于他在今年Pwn2Own Vancouver 2024 中所使用的漏洞[CVE-2024-30089](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-30089)。这个漏洞也在MSKSSRV 中,该漏洞发生在Reference Count 的处理,其成因也很有趣,不过这边就不多谈,详细内容可参考她[发表的文章](https://securityintelligence.com/x-force/little-bug-that-could/)。
Brief overview of Kernel Streaming
----------------------------------
那么,什么是Kernel Streaming 呢? 事实上,我们正常使用电脑情况下就会用到 :
![](https://images.seebug.org/1728983402699-w331s)
在Windows 系统上,当我们打开镜头、开启音效以及麦克风等音讯设备时,系统需要从这些设备读取你的声音、影像等相关资料到RAM 中。为了更高效地完成这些资料的传输,微软提供了一个名为[Kernel Streaming](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/kernel-streaming)的框架,用来处理这些资料。**这个框架主要在Kernel mode 下运行**,具有低延迟、良好的扩充性和统一介面等特性,使你能更方便、更高效地处理串流(Stream)资料。
Kernel Streaming 中,提供了三种多媒体驱动模型:port class、AVStream 和stream class。这里将主要介绍port class 和AVStream,而stream class 因为较为罕见且过时,不会多加讨论。
### [Port Class](https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/introduction-to-port-class)
大多数用于PCI 和DMA 型音效装置的硬体驱动程式,它处理与音讯相关的数据传输,例如音量控制、麦克风输入等等,主要会使用到的元件函式库会是portcls.sys。
![](https://images.seebug.org/1728983405420-w331s)
### [AVStream](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/avstream-overview)
AVStream 则是由微软提供的多媒体类驱动程式,主要支援仅限影片的串流和整合音讯/影片串流,目前跟影像有关的处理多数都跟这类别有关,例如你的视讯镜头、撷取卡等等。
![](https://images.seebug.org/1728983407234-w331s)
实际上Kernel Streaming 的使用很复杂,因此这里只会简单的叙述一下,更多详细内容可以参考[微软官方文件](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/)。
Interact with device
--------------------
在我们想要与音讯设备或是视讯镜头等设备互动时该怎么做呢?其实就跟一般设备互动一样,可以透过CreateFile 函数来开启一个设备。那么这类型的设备,名称又会是什么呢?其实这边不太会像是`\DevcieamedPipe`这类型的名称,而是会像下面这样的路径:
\?\hdaudio#subfunc_01&ven_8086&dev_2812&nid_0001&subsys_00000000&rev_1000#6&2f1f346a&0&0002&0000001d#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\ehdmiouttopo
### Enumerate device
每台电脑都可能不一样,必须使用[SetupDiGetClassDevs](https://learn.microsoft.com/zh-tw/windows/win32/api/setupapi/nf-setupapi-setupdigetclassdevsw)等API 去列举设备,一般来说KS 系列的设备都会注册在`KSCATEGORY*`底下,像是音讯设备就会注册在[KSCATEGORY\_AUDIO](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/install/kscategory-audio)中。
你也可以使用KS 所提供的[KsOpenDefaultDevice](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ksproxy/nf-ksproxy-ksopendefaultdevice)获得该类别中第一个符合的PnP 装置的Handle,实际上来说也只是SetupDiGetClassDevs 和CreateFile 的封装而已。
hr = KsOpenDefaultDevice(KSCATEGORY_VIDEO_CAMERA,GENERIC_READ|GENERIC_WRITE, &g_hDevice)
### Kernel Streaming object
我们在开启这些设备之后,Kernel Streaming 会在Kernel 中建立一些相关的Instance,其中最为重要的就是[KS Filters](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/ks-filters)及[KS Pins](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/ks-pins)。在Kernel Streaming 的使用过程中,这些Instance 会被频繁使用,它们主要用来封装设备的硬体功能,方便开发者透过统一的介面进行串流的处理。
这边先以[Audio Filters](https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/audio-filters)作为例子,其他多数大同小异,我们也只会简单介绍,其他细节请自行参考微软官方文件。
#### KS filters
每个KS Filter 通常代表一个设备或设备的特定功能,在我们打开一个音讯设备后,大部分情况下会对应到一个Kernel Filter,当我们从音讯设备读取资料时,这些资料就会先通过这个KS Filter 进行处理。
概念上如下图所示,中间的大框表示一个代表音讯设备的KS filter。而我们想要从音讯设备中读取资料时,会从左边读入Filter,经过几个节点进行处理后,从右边输出。
![](https://images.seebug.org/1728983409159-w331s) (From: https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/audio-filters)
#### KS pins
上图中,读取及输出资料的点称为Pin,Kernel 也有相对应的KS Pin Object,用于描述这些Pin 的行为,例如Pin 是输入端还是输出端、支援的格式有哪些等。我们使用时必须在Filters 上,[开启一个Pin](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/ks/nf-ks-kscreatepin)来建立Instance,才能从设备读取或输出资料。
### KS Property
这些KS Object 都会有自己的Property,每个Property 都会有相对应的功能,前面所提到的Pin 中的资料格式、音量大小及设备的状态等等,这些都是一个Property,通常会对应到一组GUID,我们可以透过[IOCTL\_KS\_PROPERTY](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/ks/ni-ks-ioctl_ks_property)来读取或设定这些Property。
这大大简化了多媒体驱动程式的开发,并且确保了不同设备之间的一致性和可扩展性。
### Read streams from webcam
这边就用个简单的范例来介绍一下Application 如何从视讯镜头读取资料
其最简单的流程大概如这张图所示 :
![](https://images.seebug.org/1728983410936-w331s)
1. 开启设备后获得设备Handle
2. 使用这个Handle 在这个Filter 上建立Pin 的Instance 并获得Pin handle
3. 使用IOCTL\_KS\_PROPERTY 设置Pin 的状态到RUN
4. 最后就可以使用[IOCTL\_KS\_READ\_STREAM](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ks/ni-ks-ioctl_ks_read_stream)从这个Pin 中读资料进来
Kernel Streaming architecture
-----------------------------
> 对漏洞研究而言,我们必须先了解其架构,思考有哪些可能的攻击面
在初步了解Kernel Streaming 有哪些功能和操作后,为了找寻漏洞必须先了解一下架构,了解Windows 是怎么实作这些功能、分别有哪些元件等等,才知道应该要分析哪些sys,从哪边下手会比较好。
经过我们分析后,整个架构约略会像这张图所示 :
![](https://images.seebug.org/1728983413029-w331s)
在Kernel Stearming 元件中,最为核心的就是ksthunk.sys 及ks.sys,几乎所有功能都会与它们有关。
### ksthunk (Kernel Streaming WOW Thunk Service Driver)
Application 呼叫DeviceIoControl 后,在Kernel Streaming 中的**入口点**,但它功能很简单,负责将WoW64 process 中32-bit 的requests 转换成64-bit 的requests,使得下层的driver 就可以不必为32 位元的结构另外处理。
### ks (Kernel Connection and Streaming Architecture Library)
Kernel Streaming 的**核心元件**之一,它是Kernel Streaming 的函示库,负责及转发IOCTL\_KS\_PROPERTY 等requests 到对应设备的driver 中,同时也会负责处理AVStream 的相关功能。
### The work flow of IOCTL\_KS\_\*
而在呼叫DeviceIoControl 时,就会像下图一样,将使用者的requests 依序给相对应的driver 来处理
![](https://images.seebug.org/1728983414300-w331s)
而到第6 步时ks.sys 就会根据你requests 的[Property](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/ksproperty-structure)来决定要交给哪个driver 及handler 来处理你的request。
![](https://images.seebug.org/1728983416369-w331s)
最终再转发给相对应的Driver,如上图中最后转发给portcls 中的handler 来操作音讯设备。
到这边应该对Kernel Streaming 的架构及流程有初步概念了,接下来就是找洞的时刻。依照现有的元素来看,哪些是值得一看的攻击面呢?
### From attacker's view
> 在挖掘漏洞前,如果能仔细思考怎样的情况下容易有洞,可以达到事半功倍的效果
从一个漏洞研究员的角度来说,大概会有下列这几个点
1. 每个设备中的Property handler 每个设备中的KS Object 都有各自的Property,而且每个Property 都有各自的实作,有些Property 处理起来容易出问题。
2. ks 及ksthunk ks 及ksthunk 已经有很长一段时间没有漏洞,但却是个最容易接触到的入口点,也许是一个好目标,上一次出现的漏洞是在2020 年[@nghiadt1098](https://x.com/nghiadt1098)所找到的两个漏洞[CVE -2020-16889](https://msrc.microsoft.com/update-guide/en-us/vulnerability/CVE-2020-16889)及[CVE-2020-17045](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-17045)
3. 每个driver 都各自处理一部分的内容在Kernel Streaming 的部分功能中,有些driver 会各自先处理部分的内容,可能会造成一些不一致性的问题。
我们针对上面几个角度去对整个Kernel Streaming 做Code Review 后,很快的就发现了几个比较容易发现的漏洞
* portcls.sys
* CVE-2024-38055 (out-of-bounds read when set dataformat for Pin)
* CVE-2024-38056
* ksthunk
* CVE-2024-38054 (out-of-bounds write)
* CVE-2024-38057
不过我们这一篇不会一一讲解这些漏洞,这几个多数都是没有检查长度或是index 之类的越界存取等等明显的洞,也许会在后续的部分慢慢来讲解,[@Fr0st1706](https://x.com/Fr0st1706)也在前阵子写出了CVE-2024-38054 的[利用](https://github.com/Black-Frost/windows-learning/tree/main/CVE-2024-38054),这边就暂时留给读者研究了。
这篇要提的是,我们在Review 过程中发现了一些有趣的事情。
你觉得下面这段程式码是否安全呢?
__int64 __fastcall CKSThunkDevice::CheckIrpForStackAdjustmentNative(__int64 a1, struct _IRP *irp, __int64 a3, int *a4)
{
if ( irp->RequestorMode )
{
v14 = 0xC0000010;
}
else
{
UserBuffer = (unsigned int *)irp->UserBuffer;
...
v14 = (*(__int64 (__fastcall **)(_QWORD, _QWORD, __int64 *)) (Type3InputBuffer + 0x38))(// call Type3InputBuffer+0x38
*UserBuffer,
0LL,
v19);
}
}
看到这段程式码让我想起了[CVE-2024-21338](https://decoded.avast.io/janvojtesek/lazarus-and-the-fudmodule-rootkit-beyond-byovd-with-an-admin-to-kernel-zero-day/),该漏洞原先并没有任何检查,而在修补后则是新增了[ExGetPreviousMode](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/wdm/nf-wdm-exgetpreviousmode),但这边检查则是使用了[IRP](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp)中的RequestorMode 来做检查,不过一般情况下从使用者呼叫的IOCTL 的RequestorMode 都会是UserMode(1) 是不会有问题的。
此时我又想起来[James Forshaw](https://x.com/tiraniddo)的[Windows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager](https://googleprojectzero.blogspot.com/2019/03/windows-kernel-logic-bug-class-access.html)这篇文章。
The overlooked bug class
------------------------
这边我们必须先来提一下几个名词跟概念,不过如果你对PreviousMode 及RequestorMode 很熟悉,可以跳至[A logical bug class](https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1/#A-logical-bug-class)
### PreviousMode
第一个是PreviousMode,在Application 中如果使用者透过Nt\* 等System Service Call 来对设备或档案中操作时,进入Kernel 后就会在[\_ETHREAD](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/kernel/eprocess#ethread)中的PreviousMode 标注UserMode(1) 表示这个System Service Call 是来自User mode 的Application。如果你是从Kernel mode 中,例如设备driver 呼叫Zw\* System Service Call 的API 就会标记成KernelMode(0)。
![](https://images.seebug.org/1728983418299-w331s)
### RequestorMode
另外一个类似的则是IRP 中的RequestorMode 这边就是记录你原始的requests 是来自UserMode 还是KernelMode,在Kernel driver 中的程式码是非常常用到的栏位,通常会来自PreviousMode。
很常被用来决定是否要对来自使用者的requests 做额外检查,像是Memory Access Check 或是Security Access Check,例如下面这个例子中,如果requests 来自UserMode 就会检查使用者提供的位置,如果是从Kernel 来的,就不做额外检查增加效率。
![](https://images.seebug.org/1728983420019-w331s)
但实际上这也出现了一些问题…
### A logical bug class
在[James Forshaw](https://x.com/tiraniddo)的[Windows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager](https://googleprojectzero.blogspot.com/2019/03/windows-kernel-logic-bug-class-access.html)中,就提到了一种Bug Class
这边可以先想想看,使用者呼叫NtDeviceIoControlFile 之类的System Service Call 之后,如果处理的driver 又去用使用者可控的资料来作为ZwOpenFile 的参数,会发生什么事
![](https://images.seebug.org/1728983422104-w331s)
在driver 呼叫ZwOpenFile 之后, PreviousMode 会转换成为`KernelMode`,并且在NtOpenFile 处理时,就会因为PreviousMode 是`KernelMode`的关系少掉大部分的检查,而后续的`Irp->RequestorMode`也会因此变成`KernelMode`,从而绕过Security Access Check 及Memory Access Check。不过这边很看后续处理的driver 怎么去实作这些检查,如果只依赖RequestorMode 来决定要不要检查,就可能会有问题。这边省略了一些细节,实际上的状况会稍微再复杂一点点,也会跟CreateFile 的flag 有关,细节可参考下列几篇文章:
* [Windows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager](https://googleprojectzero.blogspot.com/2019/03/windows-kernel-logic-bug-class-access.html)
* [Hunting for Bugs in Windows Mini-Filter Drivers](https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in-windows-mini-filter.html)
* [Local privilege escalation via the Windows I/O Manager: a variant finding collaboration](https://msrc.microsoft.com/blog/2019/03/local-privilege-escalation-via-the-windows-i-o-manager-a-variant-finding-collaboration/)
这边有这样的概念就好,原先这些研究主要是在Zw\* 系列的System Service Call 上面,大家可以思考一下,有没有其他类似的情况,也可能造成这种逻辑漏洞呢?
#### The new bug pattern
事实上来说是有的,使用[IoBuildDeviceIoControlRequest](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iobuilddeviceiocontrolrequest)这个方法去创建一个DeviceIoControl 的IRP 时,万一没注意到也很容易有这样的问题。这个API 主要是Kernel driver 用来呼叫IOCTL 的其中一种方法,它会帮你建好IRP,而后续在去呼叫[IofCallDriver](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/wdm/nf-wdm-iofcalldriver),就可以在Kernel driver 中呼叫IOCTL。在[Microsoft Learn](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iobuilddeviceiocontrolrequest)中,有一段话特别值得注意:
![](https://images.seebug.org/1728983424188-w331s)
也就是预设情况下,如果你没有特别去设置RequestorMode 就会直接以KernelMode 形式去呼叫IOCTL。
![](https://images.seebug.org/1728983426298-w331s)
按照这个思路,我们重新回头审视一下我们的目标Kernel Streaming,我们发现了一个吸引我们的地方。
![](https://images.seebug.org/1728983428088-w331s)
在Kernel Streaming 中使用这个IoBuildDeviceIoControlRequest 地方是在`ks!KsSynchronousIoControlDevice`,而主要内容明显就是在用刚刚提到的方法,在Kernel 中呼叫DeviceIoControl,不过这边看似有好好的设置`Irp->RequestorMode`,且会根据KsSynchronousIoControlDevice 参数不同而去设置不同的数值,对于开发者来说会是一个方便的函式库。
然而…
ks!CKsPin::GetState ![](https://images.seebug.org/1728983430587-w331s)
ks!SerializePropertySet ![](https://images.seebug.org/1728983432708-w331s)
ks!UnserializePropertySet ![](https://images.seebug.org/1728983434758-w331s)
我们发现到在Kernel Streaming 中,全部有使用到`KsSynchronousIoControlDevice`的地方都是固定的使用KernelMode(0),到这边就可以仔细的检查看看,有用到的地方是否有安全上的问题了。因此我们将Kernel Streaming 中的bug pattern 转换成下列几点:
1. 有使用KsSynchronousIoControlDevice
2. 有可控的
* InputBuffer
* OutputBuffer
3. 第二次处理IOCTL 的地方有依赖RequestorMode 做安全检查,并且有可以作为提权利用的地方。 ![](https://images.seebug.org/1728983441903-w331s)
按照这个Pattern 我们很快地就找到了第一个洞
The vulnerability & exploitation
--------------------------------
### CVE-2024-35250
这个漏洞也是我们今年在[Pwn2Own Vancouver 2024 中所使用的漏洞](https://x.com/thezdi/status/1770517322203070674)。在Kernel Streaming 的IOCTL\_KS\_PROPERTY 功能中,为了让效率增加,提供了`KSPROPERTY_TYPE_SERIALIZESET`和`KSPROPERTY_TYPE_UNSERIALIZESET`功能允许使用者透过**单一呼叫**与多个Property 进行操作。当我们用这功能时,这些requests 将被KsPropertyHandler 函数分解成多个呼叫,详情可参考[这篇](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/ksproperty-structure#remarks)。
该功能实作在ks.sys 中
![](https://images.seebug.org/1728983443761-w331s)
上图中可以看到,在ks 处理Property 时,如果有给上述的flag 就会由UnserializePropertySet 来处理你的request
我们这边就先来看一下UnserializePropertySet
unsigned __int64 __fastcall UnserializePropertySet(
PIRP irp,
KSIDENTIFIER* UserProvideProperty,
KSPROPERTY_SET* propertyset_)
{
...
New_KsProperty_req = ExAllocatePoolWithTag(NonPagedPoolNx, InSize, 0x7070534Bu);
...
memmove(New_KsProperty_req, CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer, InSize); //------[1]
...
status = KsSynchronousIoControlDevice(
CurrentStackLocation->FileObject,
0,
CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode,
New_KsProperty_req,
InSize,
OutBuffer,
OutSize,
&BytesReturned); //-----------[2]
...
}
可看到在处理过程中会先将原始的request,复制到新分配出来的Buffer 中\[1\],而后续就会使用这个Buffer 来使用KsSynchronousIoControlDevice 呼叫新的IOCTL \[2\]。其中`New_KsProperty_req`及`OutBuffer`都是使用者所传入的内容。
而呼叫UnserializePropertySet 时的流程,大概如下图所示 :
![](https://images.seebug.org/1728983446006-w331s)
这边呼叫IOCTL 时可以看到图中第2 步I/O Manager 会将`Irp->RequestorMode`设成UserMode(1),直到第6 步时,ks 会去判断使用者requests 的Property 是否存在于该KS Object 中,如果该KS Object 的Property**存在**,并且有设置`KSPROPERTY_TYPE_UNSERIALIZESET`就会用`UnserializePropertySet`来处理指定的Property
![](https://images.seebug.org/1728983447971-w331s)
而接下来第7 步就会呼叫KsSynchronousIoControlDevice 重新做一次IOCTL,而此时新的`Irp->RequestorMode`就变成了KernelMode(0) 了,而后续的处理就如一般的IOCTL\_KS\_PROPERTY 相同,就不另外详述了,总之我们到这里已经有个可以任意做IOCTL\_KS\_PROPERTY 的primitive 了,接下来我们必须寻找看看是否有可以EoP 的地方。
### The EoP
最先看到的想必就是入口点ksthunk,我们这边可以直接来看`ksthunk!CKSThunkDevice::DispatchIoctl`
__int64 __fastcall CKSThunkDevice::DispatchIoctl(CKernelFilterDevice *a1, IRP *irp, unsigned int a3, NTSTATUS *a4)
{
...
if ( IoIs32bitProcess(irp) && irp->RequestorMode ) //------[3]
{
//Convert 32-bit requests to 64-bit requests
}
else if ( CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode == IOCTL_KS_PROPERTY )
{
return CKSThunkDevice::CheckIrpForStackAdjustmentNative((__int64)a1, irp, v11, a4) //-----[4];
}
}
ksthunk 会先判断是否是WoW64 的Process 的request,如果是就会将原本32-bit 的requests 转换成64-bit 的\[3\],如果原本就是64-bit 则会呼叫`CKSThunkDevice::CheckIrpForStackAdjustmentNative`\[4\] 往下传递
__int64 __fastcall CKSThunkDevice::CheckIrpForStackAdjustmentNative(__int64 a1, struct _IRP *irp, __int64 a3, int *a4)
{
...
if ( *(_OWORD *)&Type3InputBuffer->Set == *(_OWORD *)&KSPROPSETID_DrmAudioStream
&& !type3inputbuf.Id
&& (type3inputbuf.Flags & 2) != 0 ) //-----[5]
{
if ( irp->RequestorMode ) //-------[6]
{
v14 = 0xC0000010;
}
else
{
UserBuffer = (unsigned int *)irp->UserBuffer;
...
v14 = (*(__int64 (__fastcall **)(_QWORD, _QWORD, __int64 *))(Type3InputBuffer + 0x38))(// call Type3InputBuffer+0x38
*UserBuffer,
0LL,
v19); //------------[7]
}
}
}
我们在\[5\] 看到,如果我们给定的Property Set 是[KSPROPSETID\_DrmAudioStream](https://learn.microsoft.com/mt-mt/windows-hardware/drivers/audio/kspropsetid-drmaudiostream),就有特别的处理。而在\[6\] 时,会先去判断Irp->RequestorMode 是否为KernelMode(0),如果从UserMode(1) 呼叫的IOCTL 就会直接返回错误,但如果我们使用前面所说的`KSPROPERTY_TYPE_UNSERIALIZESET`来呼叫IOCTL,并指定`KSPROPSETID_DrmAudioStream`这个Property,那么这里\[6\] 就会是KerenlMode(0)。接下来就会在\[7\] 直接使用使用者所传入的内容做为function 呼叫,甚至第一个参数是可控的,实际写PoC 后,验证了我们的结果。
![](https://images.seebug.org/1728983450050-w331s)
这边可能会有人有疑惑,什么设备或是情况下会有`KSPROPSETID_DrmAudioStream`?实际上来说音讯设备大多情况下都会有,主要是用来设置DRM 相关内容用的。
### Exploitation
在有了任意呼叫之后,要达成EoP 就不是太大的问题,虽然会遇到kCFG、kASLR、SMEP 等等保护,但在Medium IL 下唯一比较需要处理的就只有kCFG。
* **kCFG**
* kASLR
* NtQuerySystemInformation
* SMEP
* Reuse Kernel Code
* …
#### Bypass kCFG
那我们目标很简单,就是从合法的function 做出任意写的primitive,而之后就可以利用常见的方法[用System token 取代当前的Process token](https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/how-kernel-exploits-abuse-tokens-for-privilege-escalation#id-1.-replacing-tokens-for-privilege-escalation)或是[Abuse token privilege](https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernal_Slides.pdf)去做到EoP。
直觉地会直接去找看看,kCFG 中合法的function 名称有set 的function,比较可能是可以写入的。我们这里是直接拿ntoskrnl.exe 中export fucntion 去寻找看看是否有合法的function,这些大多情况下都是合法的。
![](https://images.seebug.org/1728983452427-w331s)
而很快的我们就找到了[RtlSetAllBits](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlsetallbits)
![](https://images.seebug.org/1728983454809-w331s)
它是个非常好用的gadget 而且是kCFG 中合法的function,另外也只要控制一个参数`_RTL_BITMAP`
struct _RTL_BITMAP
{
ULONG SizeOfBitMap;
ULONG* Buffer;
};
我们可将Buffer 指定到任意位置并指定大小,就可以将一段范围的bits 全部设置起来,到这边就差不多结束了,只要将`Token->Privilege`全部设置起来,就可以利用Abuse Privilege 方法来做到EoP 了。
然而…在Pwn2Own 比赛前,我们在Hyper-V 上安装一个全新Windows 11 23H2 VM 测试Exploit,结果失败了。 而且是在开启设备阶段就失败。
![](https://images.seebug.org/1728983457031-w331s)
经过调查后发现到Hyper-V 在预设情况下并不会有音讯设备,造成exploit 会失败。
![](https://images.seebug.org/1728983458627-w331s)
在Hyper-V 中,预设情况下只会有MSKSSRV,然而MSKSSRV 也没有KSPROPSETID\_DrmAudioStream 这个Property,使得我们无法成功利用这个漏洞达成EoP,因此我们必须找其他方式触发或者找新的漏洞,此时我们决定重新Review 一遍整个流程,看看是否还有其他可能利用的地方。
### CVE-2024-30084
重新审视后,发现到IOCTL\_KS\_PROPERTY 是使用[Neither I/O](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/using-neither-buffered-nor-direct-i-o)来传递资料的,也就是说会直接拿使用者的Input buffer 来做资料上的处理,一般来说不太建议使用这个方法,很常出现Double Fetch 的问题。
![](https://images.seebug.org/1728983460585-w331s)
我们可从上图中KspPropertyHandler 看到,在使用者呼叫IOCTL 之后,会直接将Type3InputBuffer 复制到新分配出来的Buffer 中,其中会存有[KSPROPERTY](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/stream/ksproperty-structure)结构,接下来会用这结构中的GUID 来查询Property 是否有在该设备所支援的Property 中,若存在才会继续往下呼叫`UnserializePropertySet`。
这边我们再回头看一眼`UnserializePropertySet`。
![](https://images.seebug.org/1728983462825-w331s)
我们可以发现到,**它又再次从Type3InputBuffer 复制使用者所提供的资料**做为新的IOCTL 的输入,很明显的这边就存在了一个Double Fetch 的漏洞,因此我们将整个利用流程改成下图的样子
![](https://images.seebug.org/1728983470112-w331s)
我们一开始发送IOCTL\_KS\_PROPERTY 时,就会先以MSKSSRV 既有的Property`KSPROPSETID_Service`来做后续操作,而在图中第6 步时,会先复制一份Property 的GUID 到Kernel 中,而后再用这个Property GUID 去查询是否有在该KS Object 的支援清单中,而这边因为MSKSSRV 有支援,就会往下呼叫`UnserializePropertySet`。
![](https://images.seebug.org/1728983472088-w331s)
在呼叫UnserializePropertySet 后,因为有Double Fetch 的漏洞,让我们可以在检查后到使用之间,将`KSPROPSETID_Service`换成`KSPROPSETID_DrmAudioStream`,而接下来就可以让ks 使用`KSPROPSETID_DrmAudioStream`作为requests 来发送IOCTL,从而触发前述了CVE-2024-35250逻辑漏洞,使这个漏洞不论在什么环境下都可以使用。
最终我们成功在Pwn2Own Vancouver 2024 中,成功攻下Micorsoft Windows 11。
![](https://images.seebug.org/1728983474071-w331s)
在Pwn2Own 结束后,经过我们调查,发现到这个漏洞从Windows 7 就存在了,至少存在将近20 年,而且利用上非常稳定,有着百分之百的成功率,强烈建议大家尽速更新至最新版本。
To be continued
---------------
这篇主要着重在我们如何找到今年在Pwn2Own 中所使用的漏洞及Kernel Streaming 的攻击面分析。在找到这个洞之后,我们后续也持续朝这个方向继续研究,也发现了另外一个也是Exploitable 的漏洞以及其他更多有趣的漏洞,我们预计在今年十月发表,敬请期待Part II。
Reference
---------
* [Critically Close to Zero-Day: Exploiting Microsoft Kernel Streaming Service](https://securityintelligence.com/x-force/critically-close-to-zero-day-exploiting-microsoft-kernel-streaming-service/)
* [Windows Kernel Security - A Deep Dive into Two Exploits Demonstrated at Pwn2Own](https://conference.hitb.org/hitbsecconf2023hkt/materials/D2T1%20-%20Windows%20Kernel%20Security%20-%20A%20Deep%20Dive%20into%20Two%20Exploits%20Demonstrated%20at%20Pwn2Own%20-%20Thomas%20Imbert.pdf)
* [CVE-2023-29360 Analysis](https://big5-sec.github.io/posts/CVE-2023-29360-analysis/)
* [Racing Round and Round: The Little Bug That Could](https://securityintelligence.com/x-force/little-bug-that-could/)
* [Windows Kernel Logic Bug Class: Access Mode Mismatch in IO Manager](https://googleprojectzero.blogspot.com/2019/03/windows-kernel-logic-bug-class-access.html)
* [Hunting for Bugs in Windows Mini-Filter Drivers](https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in-windows-mini-filter.html)
* [Local Privilege Escalation via the Windows I/O Manager: A Variant Finding & Collaboration](https://msrc.microsoft.com/blog/2019/03/local-privilege-escalation-via-the-windows-i-o-manager-a-variant-finding-collaboration/)
暂无评论