Introduction
When looking for vulnerabilities of interest, it’s always a good option to look for vulnerabilities used at pwn2own. Indeed, these vulnerabilities are exploited during the competition, meaning they indeed have a practical impact. This year, I got my interest piqued during my freetime by a LPE (CVE-2023-29360) reported by Thomas Imbert(@masthoon) for the following reasons:
- Thomas is recognized as one of the several highly skilled researchers in the French exploit scene, and is certainly knowledgeable
- The vulnerability, teased in the HITB upcoming conference, is described as: “a logical bug that defeats most mitigations by allowing direct read and write access to kernel virtual memory”
In fact, I was not disappointed at all and learnt a lot from the analysis I made, and thought I would share the root cause analysis.
disclaimer: In this post I will describe the root cause of the vulnerability while omitting some details for full reproduction for obvious reasons. As I was not really acquainted with the underlying stuff revolving around that vulnerability before starting my analysis, I may have made some mistakes.
Finding the root cause
Starting with the ZDI’s advisory of the vulnerability, it is possible to get enough details to look for the root cause. Typically, the important information is:
- The vulnerability is present in the
mskssrv
driver - The issue results from the lack of proper validation of a user-supplied value prior to dereferencing it as a pointer
The next step is to patch-diff the driver for the update correcting the vulnerability. Only one function—named FsAllocAndLockMdl
—was modified. Within that function, the AccessMode
parameter of the call to MmProbeAndLockPages
was changed from KernelMode
to UserMode
, as shown in the following screenshot:
With the root cause found, let’s analyze the vulnerability.
Understanding the vulnerability
MDL, what is that?
An I/O buffer that occupies a contiguous virtual memory range can be non-contiguously distributed over several physical pages in memory. The Windows OS utilizes memory descriptor list
(MDL) structures at the kernel level to describe a single virtual memory buffer’s physical page layout. MDLs vary in size, are semi-opaque, and are composed of a header that describes the MDL’s properties and a variable-size array of pointers—called the page frame number
(PFN) array—describing the physical addresses used by the MDL. Inside the header, the virtual address (VA) of the memory buffer that is physically described by the MDL is present, as well as its length.
The following schematics illustrates the concept of MDL:
MDL creation and interaction is reserved to the components operating in kernel mode like drivers, even though a user land process may be able to do so via a communication channel with a driver.
It should be noted that an address that is already mapped and potentially in use by the OS can have its physical page layout described by a new MDL. In this case, the component accessing this MDL will be able (overly simplified) to directly access the physical memory pointed to by this buffer VA. This creates a communication channel between this component and the OS component that is already interacting with the VA.
As a consequence, MDLs can allow drivers for the Windows OS to implement Direct Memory Access (DMA) operations and permit to avoid memory-copy operations between the user land and the kernel land (Direct I/O).
Inner working of IoAllocateMdl and MmProbeAndLockPages
In the vulnerable function FsAllocAndLockMdl
, two APIs permitting to interact with MDLs are used: IoAllocateMdl
and MmProbeAndLockPages
.
The first API—IoAllocateMdl
—allocates the MDL structure’s storage to the virtual memory, sets the buffer VA that the MDL describes in its header, but does not initialize the PFN array describing the physical memory that will be used for the buffer. In fact, it should be coupled with a second API call that is responsible for establishing this array, thus acquiring the correct physical memory to describe it.
The second API—MmProbeAndLockPages
— first probes the buffer VA described by the MDL—i.e. it will check if this buffer VA can be accessed—in case the the AccessMode
parameter is set to UserMode
. Next, this function locks the physical pages, making them unable to be paged, reallocated, and freeable; while setting the access operation (read and/or write).
Let’s describe in what consists the probing: as already described, new MDLs can be created to get the physical description of a given virtual address’s buffer already in use by the OS, and potentially to directly interact with the physical memory associated with this buffer. In particular, it can be used against various data already in use at the kernel level. This is a problem when the MDL parameters come from the user-land (for instance because that user-land process aims to perform a DMA operation), for example through a DeviceIO
control message made to a driver. Indeed, if the user-land process passes in kernel pointers for the creation of the MDL, and is then able to interact with it, that means this user-land process would be able to interact with kernel data. As a consequence, the user/kernel barrier is broken. To avoid this problem, the probing simply checks that the buffer VA in the MDL is not in the kernel-land, by checking the address is not superior to 0x7FFFFFFF0000
.
Explaining the root cause
The FsAllocAndLockMdl
function is reachable through a DeviceIO
control message with code 0x2f0408
. In particular, the parameters for the MDL creation are directly taken from the user-supplied SystemBuffer
. As the AccessMode
parameter of MmProbeAndLockPages
was not correctly set to UserMode
, no probing of the MDL occurs. As a consequence, the user can create a MDL pointing to critical kernel data.
As CVE-2023-29360 was exploited, it means there is a way for the user to interact later with the arbitrarily created MDL, especially to directly modify the kernel data pointed by it. In particular, it appears that a second DeviceIO
control message with code 0x2f0410
, permits to map the previously created MDL’s physical memory directly in the user-land process’s memory, inside a variable with read and write access. This mapping is realized through the MmMapLockedPagesSpecifyCache
API. As a consequence, accessing this variable as a pointer allows the physical pages used in the MDL to be accessed and modified directly.
Exploiting the vulnerability
One approach to exploit CVE-2023-29360 is to first obtain a MDL describing the kernel VA where the current process privileges are defined (the kernel VA being simply obtained through a NtQuery
leak), using the first DeviceIO
control message. Subsequently, this MDL is mapped through the second DeviceIO
control message. As a consequence, the values located at the kernel VA where the current process privileges are defined are now directly accessible and modifiable in the current process’s virtual memory. The process can now freely modify its own privileges and achieve privilege escalation, for example by getting the SeDebugPrivilege
.
The following steps highlight how the identified exploit approach works:
-
The exploit process is launched. After launching, the memory layout is as follows:
-
The exploit process sends the first
DeviceIO
control message tomskssrv
, inducing the creation of a MDL pointing to its own privileges in the kernel address space, thanks to the absence of check. The memory layout is now the following: -
The exploit process sends the second
DeviceIO
control message tomskssrv
, mapping the physical memory pointed by the MDL in its own virtual address space. The memory layout becomes: -
The exploit process can now directly modify the physical memory values tied to its own privileges, to make itself highly privileged.
Conclusion
This logical vulnerability is really powerful as it may allow for direct kernel read/write. As stated by Thomas, no mitigation currently halts a similar exploit for it. This might change soon with the modification of the NtQuery leaks in-the-works, as such exploits will necessitate a first vulnerability to leak the kernel address of interest.
Finally, I would like to thanks Thomas Imbert one more time for having found it as I learnt a lot while analyzing it.
By the way, this attack surface might have been underlooked. Setting up the following bad yara rule for variants leads to a few results, eheh:
1
2
3
4
5
6
7
8
9
10
11
12
13
rule search_cve_2023_29360_variant
{
meta:
version = "102947593"
strings:
$api1 = "MmProbeAndLockPages"
$api2 = "MmMapLockedPagesSpecifyCache"
$s2 = { 33 D2 44 8D 42 01 } //xor edx, edx, lea r8d, [rdx+1]
condition:
uint16(0) == 0x5a4d and all of them
}