Streaming vulnerabilities from Windows Kernel - Proxying to Kernel - Part II
============================================================================
[](https://devco.re/en/blog/author/angelboy)[Angelboy](https://devco.re/en/blog/author/angelboy)2024-10-05
![](https://images.seebug.org/1729850503826-w331s)
***
[English Version](https://devco.re/blog/2024/10/05/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part2-en/), [中文版本](https://devco.re/blog/2024/10/05/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part2/)
This is a series of research related to Kernel Streaming attack surface. It is recommended to read the following articles first.
* [Streaming vulnerabilities from Windows Kernel - Proxying to Kernel - Part I](https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1-en/)
In the previous research on `Proxying to Kernel`, we discovered multiple vulnerabilities in Kernel Stearming as well as an overlooked bug Class. We successfully exploited vulnerabilities CVE-2024-35250 and CVE-2024-30084 to compromise Windows 11 at Pwn2Own Vancouver 2024.
In this article, we will continue to explore this attack surface and bug Class, revealing another vulnerability and exploitation technique, which was also presented at [HEXACON 2024](https://www.hexacon.fr/).
After Pwn2Own Vancouver 2024, we continued to investigate the `ks!KsSynchronousIoControlDevice` bug pattern to see if there were any other security issues. However, after some time, we did not find any other exploitable points in the property operations of KS object. Therefore, we shifted our focus to another feature, KS Event.
[KS Event](https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/ks-events)
---------------------------------------------------------------------------------------
Similar to the [KS Property](https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1-en/#ks-property) mentioned in the previous article, the KS object not only has its own property set but also provides the functionality to set KS Event. For instance, you can set an event to trigger when the device status changes or at regular intervals, which is convenient for developers of playback software to define subsequent behaviors. Each KS Event, like a property, requires the KS object to support it to be used. We can register or disable these Events through [IOCTL\_KS\_ENABLE\_EVENT](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/ks/ni-ks-ioctl_ks_enable_event) and [IOCTL\_KS\_DISABLE\_EVENT](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/ks/ni-ks-ioctl_ks_disable_event).
### KSEVENTDATA
When registering a KS Event, you can register the desired event by providing [KSEVENTDATA](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ks/ns-ks-kseventdata). You can include handles such as EVENT\_HANDLE and SEMAPHORE\_HANDLE in the registration. When KS triggers this event, it will notify you using the provided handle.
### The work flow of IOCTL\_KS\_ENABLE\_EVENT
The entire work flow is similar to IOCTL\_KS\_PROPERTY. When calling DeviceIoControl, as shown in the figure below, the user’s requests are sequentially passed to the corresponding driver for processing.
![](https://images.seebug.org/1729850508826-w331s)
Similarly, in step 3, 32-bit requests will be converted into 64-bit requests. By step 6, ks.sys will determine which driver and [addhandler](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ks/nc-ks-pfnksaddevent) to handle your request based on the event of your requests.
![](https://images.seebug.org/1729850511870-w331s)
Finally, forward it to the corresponding driver. As shown in the figure above, it is finally forwarded to `KsiDefaultClockAddMarkEvent` in ks to set the [timer](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/wdm/nf-wdm-kesettimerex).
After grasping the KS Event functionality and process, we swiftly identified another exploitable vulnerability, [CVE-2024-30090](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2024-30090), based on the previous [bug pattern](https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1/#the-new-bug-pattern).
Proxying to kernel again !
--------------------------
This time, the issue occurs when `ksthunk` converts a 32-bit request into a 64-bit one.
As shown in the figure below, when `ksthunk` receives an `IOCTL_KS_ENABLE_EVENT` request and the request is from a WoW64 Process, it will perform the conversion from a 32-bit structure to a 64-bit structure.
![](https://images.seebug.org/1729850515103-w331s)
The conversion would call `ksthunk!CKSAutomationThunk::ThunkEnableEventIrp` to handle it.
__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 ioctlcode_d, PIRP irp, __int64 a3, int *a4)
{
...
if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLE
|| (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ONESHOT
|| (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLEBUFFERED )
{
// Convert 32-bit requests and pass down directly
}
else if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_QUERYBUFFER )
{
...
newinputbuf = (KSEVENT *)ExAllocatePoolWithTag((POOL_TYPE)0x600, (unsigned int)(inputbuflen + 8), 'bqSK');
...
memcpy(newinputbuf,Type3InputBuffer,0x28); //------------------------[1]
...
v18 = KsSynchronousIoControlDevice(
v25->FileObject,
0,
IOCTL_KS_ENABLE_EVENT,
newinputbuf,
inputbuflen + 8,
OutBuffer,
outbuflen,
&BytesReturned); //-----------------[2]
...
}
...
}
In `CKSAutomationThunk::ThunkEnableEventIrp`, a similar bug pattern is clearly visible. You can see that during the processing, the original request is first copied into a newly allocated buffer at \[1\]. Subsequently, this buffer is used to call the new IOCTL using `KsSynchronousIoControlDevice` at \[2\]. Both `newinputbuf` and `OutBuffer` are controlled by the user.
The flow when calling `CKSAutomationThunk::ThunkEnableEventIrp` is illustrated as follows:
![](https://images.seebug.org/1729850518393-w331s)
When calling IOCTL in a WoW64 process, you can see in step 2 of the diagram that the I/O Manager sets `Irp->RequestorMode` to UserMode(1). In step 3, ksthunk converts the user’s request from 32-bit to 64-bit, handled by `CKSAutomationThunk::ThunkEnableEventIrp`.
![](https://images.seebug.org/1729850521734-w331s)
Afterward, in step 5, `KsSynchronousIoControlDevice` will be called to issue the IOCTL, and at this point, the new `Irp->RequestorMode` has become **KernelMode(0)**. The subsequent processing is the same as a typical `IOCTL_KS_ENABLE_EVENT`, so it won’t be detailed further. In summary, we now have a primitive that allows us to perform arbitrary `IOCTL_KS_ENABLE_EVENT` with KernelMode. Next, we need to look for places where we can achieve EoP.
The Exploitation
----------------
Following the [previous approach](https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1-en/#the-eop), we first analyzed the entry point `ksthunk`. However, after searching for a while, we found no potential privilege escalation points. In `ksthunk`, most instances where `Irp->RequestMode` is `KernelMode(0)` are directly passed down without additional processing. Therefore, we shifted our eyes to the next layer, `ks`, to see if there are any opportunities for privilege escalation during the event handling process.
![](https://images.seebug.org/1729850524861-w331s)
Quickly, we found a place that caught our attention.
![](https://images.seebug.org/1729850527982-w331s)
In the `KspEnableEvent` handler, a code snippet first checks the `NotificationType` in the `KSEVENTDATA` you passed in to determine how to register and handle your event. In general, it usually provides an [EVENT\_HANDLE](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa) or a [SEMAPHORE\_HANDLE](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsemaphorea). However, in `ks`, if called from `KernelMode`, we can provide an [Event Object](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/event-objects) or even a [DPC Object](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-dpc-objects) to register your event, making the overall handling more efficient.
This means we can use this `DeviceIoControl` with `KernelMode` primitive to provide a **kernel object** for subsequent processing. If constructed well, it might achieve `EoP`, but it depends on how this `Object` is used later.
However, after trying for a while, we discovered that …
__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 ioctlcode_d, PIRP irp, __int64 a3, int *a4)
{
...
if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLE
|| (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ONESHOT
|| (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_ENABLEBUFFERED ) //-------[3]
{
// Convert 32-bit requests and pass down directly
}
else if ( (v25->Parameters.DeviceIoControl.Type3InputBuffer->Flags & 0xEFFFFFFF) == KSEVENT_TYPE_QUERYBUFFER ) //-------[4]
{
...
newinputbuf = (KSEVENT *)ExAllocatePoolWithTag((POOL_TYPE)0x600, (unsigned int)(inputbuflen + 8), 'bqSK');
...
memcpy(newinputbuf,Type3InputBuffer,0x28); //------[5]
...
v18 = KsSynchronousIoControlDevice(
v25->FileObject,
0,
IOCTL_KS_ENABLE_EVENT,
newinputbuf,
inputbuflen + 8,
OutBuffer,
outbuflen,
&BytesReturned);
...
}
...
}
If you want to provide a kernel object to register an event, then the flag given in the IOCTL for [KSEVENT](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/stream/ksevent-structure) must be `KSEVENT_TYPE_ENABLE` at \[3\]. However, at \[4\], where the vulnerability is triggered, it must be `KSEVENT_TYPE_QUERYBUFFER`, and it is impossible to directly provide a kernel object as we might have expected.
![](https://images.seebug.org/1729850531628-w331s)
Fortunately, `IOCTL_KS_ENABLE_EVENT` also uses `Neither I/O` to transmit data. It also presents a `Double Fetch` issue **again**.
![](https://images.seebug.org/1729850537331-w331s)
As shown in the figure above, we can set the flag to `KSEVENT_TYPE_QUERYBUFFER` before calling IOCTL. When checking, it will handle it with `KSEVENT_TYPE_QUERYBUFFER`. Before the second `KsSynchronousIoControlDevice` call, we can change the flag to `KSEVENT_TYPE_ENABLE`.
This way, we can successfully trigger the vulnerability and construct a specific kernel object to register the event.
### Trigger the event
When would it use the kernel object that you constructed? When an event is triggered, ks will call `ks!ksGenerateEvent` through DPC. At this point, it will determine how to handle your event based on the `NotificationType` you specified.
Let’s take a look at KsGenerateEvent
NTSTATUS __stdcall KsGenerateEvent(PKSEVENT_ENTRY EventEntry)
{
switch ( EventEntry->NotificationType )
{
case KSEVENTF_DPC:
...
if ( !KeInsertQueueDpc(EventEntry->EventData->Dpc.Dpc, EventEntry->EventData, 0LL) )
_InterlockedAdd(&EventEntry->EventData->EventObject.Increment, 0xFFFFFFFF); //--------[6]
...
case KSEVENTF_KSWORKITEM:
...
KsIncrementCountedWorker(eventdata->KsWorkItem.KsWorkerObject); //-----------[7]
}
}
At this point, there are multiple ways to exploit this. The most straightforward method is to directly construct a DPC structure and queue a DPC to achieve arbitrary kernel code execution, which corresponds to the code snippet at \[6\]. However, the IRQL when calling KsGenerateEvent is `DISPATCH_LEVEL`, making it very difficult to construct a DPC object in User space, and the exploitation process will encounter many issues.
Therefore, we opt for an alternative route using `KSEVENTF_KSWORKITEM` at \[7\]. This method involves passing in a kernel address and manipulating it to be recognized as a pointer to [KSWORKITEM](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ks/ns-ks-kseventdata).
![](https://images.seebug.org/1729850540776-w331s)
It can achieve an arbitrary kernel address increment by one. The entire process is illustrated in the diagram below.
![](https://images.seebug.org/1729850545849-w331s)
When calling `IOCTL_KS_ENABLE_EVENT`, after constructing `KSEVENTDATA` to point to a kernel memory address, ks will handle it as a kernel object and register the specified event.
![](https://images.seebug.org/1729850549028-w331s)
When triggered, ks will increment the content at our provided memory address. Therefore, we have a kernel arbitrary increment primitive here.
### Arbitrary increment primitive to EoP
From arbitrary increment primitive to EoP, there are many methods that can be exploited, among which the most well-known are [`abuse token privilege`](https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernal_Slides.pdf) and [`IoRing`](https://windows-internals.com/one-i-o-ring-to-rule-them-all-a-full-read-write-exploit-primitive-on-windows-11/). Initially, it seemed like this would be the end of it.
However, both of these methods have certain limitations in this situation:
#### Abuse token Privilege
If we use the method of abusing token privilege for EoP, the key lies of the technique in overwriting `Privileges.Enable` and `Privileges.Present`. Since our vulnerability can only be incremented by one at a time, both fields need to be written to obtain `SeDebugPrivilege`. The default values for these two fields are `0x602880000` and `0x800000`, which need to be changed to 0x602**9**80000 and 0x**9**00000\. This means each field needs to be written 0x10 times, totaling 0x20 writes. Each write requires a race condition, which takes times and significantly reduces stability.
#### IoRing
Using IoRing to achieve arbitrary writing might be a simpler method. To achieve arbitrary write, you just need to overwrite `IoRing->RegBuffersCount` and `IoRing->RegBuffers`. However, a problem arises.
![](https://images.seebug.org/1729850552153-w331s)
When triggering the arbitrary increment, if the original value is 0, it will call `KsQueueWorkItem`, where some corresponding complex processing will occur, leading to BSoD. The exploitation method of IoRing happens to encounter this situation…
![](https://images.seebug.org/1729850555550-w331s)
Is it really impossible to exploit it stably?
#### Let’s find a new way !
> When traditional exploitation methods hit a roadblock, it might be worthwhile to dive deeper into the core mechanics of the technique. You may unexpectedly discover new approaches along the way.
After several days of contemplation, we decided to seek a new approach. However, starting from scratch might take considerable time and may not yield results. Therefore, we chose to derive new inspiration from two existing methods. First, let’s look at `abusing token privilege`. The key aspect here is exploiting a vulnerability to obtain `SeDebugPrivilege`, allowing us to open high-privilege processes such as `winlogon`.
The question arises: why does having `SeDebugPrivilege` allow you to open high-privilege processes?
We need to take a look at `nt!PsOpenProcess` first.
![](https://images.seebug.org/1729850559328-w331s)
From this code snippet, we can see that when we open the process, the kernel will use [SeSinglePrivilegeCheck](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-sesingleprivilegecheck) to check if you have SeDebugPrivilege. If you have it, you will be granted `PROCESS_ALL_ACCESS` permission, allowing you to perform any action on any process except PPL. As the name implies, it is intended for debugging purposes. However, it is worth noting that `nt!SeDebugPrivilege` is a global variable in `ntoskrnl.exe`.
![](https://images.seebug.org/1729850562564-w331s)
It’s a [LUID](https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-luid) structure that was initialized at system startup. The actual value is 0x14, indicating which bit in the `Privileges.Enable` and `Privileges.Present` fields represent `SeDebugPrivilege`. Therefore, when we use NtOpenProcess, the system reads the value in this global variable
![](https://images.seebug.org/1729850565853-w331s)
Once the value of `nt!SeDebugPrivilege` is obtained, it will be used to inspect the `Privileges` field in the Token to see if the `Enable` and `Present` fields are set. For `SeDebugPrivilege`, it will check the 0x14 bit.
![](https://images.seebug.org/1729850568993-w331s)
However, there is an interesting thing…
![](https://images.seebug.org/1729850572117-w331s)
The global variable `nt!SeDebugPrivilege` is located in a writable section!
A new idea was born.
#### Make abusing token privilege great again !
By default, a normal user will have only a limited number of `Privileges`, as shown in this diagram.
![](https://images.seebug.org/1729850575277-w331s)
We can notice that in most cases, `SeChangeNotifyPrivilege` is enabled. At this point, we can look at the initialization part and find that `SeChangeNotifyPrivilege` represents the value 0x17.
![](https://images.seebug.org/1729850578646-w331s)
What would happen if we use the vulnerability to change `nt!SeDebugPrivilege` from 0x14 to 0x17?
![](https://images.seebug.org/1729850582226-w331s)
As shown in the figure, in the `NtOpenProcess` flow, it will first get the value of `nt!SeDebugPrivilege`, and at this time the obtained value is 0x17 (SeChangeNotifyPrivilege)
![](https://images.seebug.org/1729850585276-w331s)
The next check will verify the current process token using 0x17 to see if it has this `Privilege`. However, normal users generally have `SeChangeNotifyPrivilege`, so even if you don’t have `SeDebugPrivilege`, you will still pass the check and obtain `PROCESS_ALL_ACCESS`. In other words, anyone with `SeChangeNotifyPrivilege` can open a high-privilege process except PPL.
Furthermore, by using the vulnerability mentioned above, we can change `nt!SeDebugPrivilege` from **0x14 to 0x17**. Since the original value is not 0, it will not be affected by `KsQueueWorkItem`, making it highly suitable for our purposes.
![](https://images.seebug.org/1729850588304-w331s)
Once we can open a high-privilege process, the privilege escalation method is the same as the `abuse token privilege` approach so that we won’t elaborate on that here. Ultimately, we successfully achieved EoP on Windows 11 23H2 by again utilizing Proxying to kernel.
#### Remark
Actually, this technique also applies to other `Privilege`.
* SeTcbPrivilege = 0x7
* SeTakeOwnershipPrivilege = 0x9
* SeLoadDriverPrivilege = 0xa
* …
The Next & Summary
------------------
The focus of these two articles is primarily on how we analyze past vulnerabilities to discover new ones, how we gain new ideas from previous research, find new exploitation methods, new vulnerabilities, and new attack surfaces.
There may still be many security issues of this bug class, and they might not be limited to Kernel Streaming and [IoBuildDeviceIoControlRequest](https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/wdm/nf-wdm-iobuilddeviceiocontrolrequest). I believe this is a design flaw in Windows, and if we search carefully, we might find more vulnerabilities.
For this type of vulnerability, you need to pay attention to the timing of setting `Irp->RequestorMode`. If it is set to `KernelMode` and then user input is used, issues may arise. Moreover, this type of vulnerability is often very exploitable.
In Kernel Streaming, I believe there are quite a few potential security vulnerabilities. There are also many components like `Hdaudio.sys` or `Usbvideo.sys` that might be worth examining and are suitable places for fuzzing. If you are a kernel driver developer, it is best not to only check `Irp->RequestorMode` . There might still be issues within the Windows architecture. Finally, I strongly recommend everyone to update Windows to the latest version as soon as possible.
Is that the end of it ?
-----------------------
Apart from proxy-based vulnerabilities, we have also identified many other bug classes, allowing us to discover over 20 vulnerabilities in Kernel Streaming. Some of these vulnerabilities are quite unique, so stay tuned for Part III.
Reference
---------
* [Easy Local Windows Kernel Exploitation](https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernal_Slides.pdf)
* [One I/O Ring to Rule Them All: A Full Read/Write Exploit Primitive on Windows 11](https://windows-internals.com/one-i-o-ring-to-rule-them-all-a-full-read-write-exploit-primitive-on-windows-11/)
Unavailable Comments