CVE-2024-38186
A privilege escalation vulnerability exists in the License update functionality of Microsoft CLIPSP.SYS 10.0.22621 Build 22621, 10.0.26080.1 and 10.0.26085.1. A specially crafted license blob can lead to privilege escalation. An attacker can use the NtQuerySystemInformation function call to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Microsoft CLIPSP.SYS 10.0.22621 Build 22621
Microsoft CLIPSP.SYS 10.0.26080.1
Microsoft CLIPSP.SYS 10.0.26085.1
CLIPSP.SYS - https://www.microsoft.com/en-us/windows/windows-11
7.4 - CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-130 - Improper Handling of Length Parameter Inconsistency
CLIPSP.SYS is a driver used to implement Client License System Policy on Windows 10 and 11. It provides the functions used when handling most of the requests involving licensing, notably the implementation of many use cases involved with the SystemPolicyInformation class used in conjunction with NtQuerySystemInformation.
When calling NtQuerySystemInformation
with the SystemPolicyInformation
class, ntoskrnl
will call ExHandleSPCall2
that will process the data provided. The format is mostly undocumented and encrypted using Microsoft’s Warbird. Upon decryption of the data provided, a call handler is invoked based on the command_id
provided and dispatches the payload to the relevant function (e.g. SPCallServerHandleClepKdf
, SPCallServerHandleUpdateLicense
, etc.). A substential amount of these functions are wrapers around clipsp
functions that are stored as function pointers in the nt!g_kernelCallbacks
globlal array.
The SPCallServerHandleUpdateLicense
(command_id
:100) will accept a License blob whose format is also undocumented. Once installed, these license files are stored in the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\{7746D80F-97E0-4E26-9543-26B41FC22F79}\{A25AE4F2-1B96-4CED-8007-AA30E9B1A218}
key, only accessible to the SYSTEM user.
The format of this license file is TLV (Tag-length-value) following this format:
struct __unaligned __declspec(align(1)) LicenseParsing_entry
{
__int16 type;
__int16 reserved;
int entry_size;
char value[ANYSIZE_ARRAY]; //expected to be of size entry_size
};
The code snippets below are decompiled output and variables names were assumed from context or retrieved from public symbol servers, sdk, etc.
The addresses provided are for the canary build 26085.1.amd64fre.ge_release.240315-1352
There exists multiple out-of-bound reads vulnerabilities when handling license data of type 0x2 that can be turned into an out-of-bound write. This vulnerability can be triggered by feeding to the SPCallServerHandleUpdateLicense
function a license filed that has been tampered with to include a malformed type-0x2 blob. The purpose of the type 0x2 is to set the device_id
associated with a license bound to hardware.
When calling SPCallServerHandleUpdateLicense
the data will eventually be past to an obfuscated function inside clipsp
and stored in an array (see 0x0001C00E8BA3):
License->field_0[v13].entry_size = v10->entry_size;
License->field_0[v13].entry_field2 = v10->field_2;
License->field_0[v13].entry_ptr = (__int64)&v10->first_byte;
Where v13
is the internal type associated with an entry type in the license file.
The problem is with how type 0x2
(internal value representation is 18
) is handled throughout the code especially during the DeviceLicenseInstall
function (see 0x001C00EDB0C). The data is expected to be an n+2
buffer where the first two bytes are the value n
as a little endian short, followed by n
bytes that are the device_id
value. However the entry_size
value is not used to ensures it equals n+2
. As such, it is possible to provide a truncated type-0x2
license entry that will lead to multiple out-of-bound reads. Furthermore, because the n
field itself can be read out-of-bound, this creates a situation where a race-condition between mutliple reads of this value can be exploited to create a mismatch between the allocation and the copy of the device_id
. Let’s see how:
First we can see how the size of the device_id
is being returned:
//0001C00E761C
__int64 __fastcall __spoils<rax,rcx> License_get_DeviceIDSize(LicenseStruct *a1)
{
__int64 result; // rax
unsigned __int16 *entry_ptr; // rcx
result = (unsigned __int16)a1->field_0[5].entry_size;
[0] if ( !a1->field_0[5].entry_size )
{
entry_ptr = (unsigned __int16 *)a1->field_0[18].entry_ptr;
if ( entry_ptr )
[1] return *entry_ptr; //first out-of-bound read
}
return result;
}
At [0] If no field of internal type 5
is contained in the license blob, then the internal field 18
is retrieved and the first two bytes of the entry are being dereferenced at [1], potentially causing an OOB-read if a1->field_0[18].entry_size < 2
Then we can see at [2] that, once again, if there was no internal type 5
in the license blob, then the device_id is return from the internal type 18
starting at offset 2 (see [3]) (assuming it was provided in the license blob). Which means that any subsequent access to the pointer returned by License_get_DeviceID
could lead in an out-of-bound read as the expected size of that device_id
is coming from the result of License_get_DeviceIDSize
whose result is attacker controlled and not matching the actual value that was computed from the license blob into the a1->field_0[18].entry_size
field.
//00000001C00E75F8
__int64 __fastcall __spoils<rax,rcx> License_get_DeviceID(LicenseStruct *a1)
{
__int64 result; // rax
__int64 entry_ptr; // rcx
result = a1->field_0[5].entry_ptr;
[2] if ( !result )
{
entry_ptr = a1->field_0[18].entry_ptr;
if ( entry_ptr )
[3] return entry_ptr + 2;
}
return result;
}
This vulnerability comes to roost in the DeviceLicenseInstall
function when executing the code path responsible for installing a new device-bound license:
//00001C00EDC1D
[4] DeviceIDSize = License_get_DeviceIDSize(License);
[5] status = CLIP_allocate_buffer(DeviceIDSize, &deviceIDcopy);
if ( status < 0 )
{
v3 = v28;
goto DONE;
}
v8 = License_get_DeviceIDSize(License);
[6] v13 = (unsigned int)License_get_DeviceIDSize(License);
DeviceID = License_get_DeviceID(License);
v3 = deviceIDcopy;
[7] memcpy_(deviceIDcopy, DeviceID, v13);
}
The device_id_size
is retrieved twice at [4] and [6] (double-fetch), the first time being used to do a memory allocation at [5] and then as the size variable for the memcpy
at [7]. Because the device_id_size
is potentially read out-of-bound (see [1]), it’s possible to create a license file where the first byte outside of the license file is the the device_id_size value, and tailor the the license blob is such a way that it will land at the end of a memory page. Then, it creates a race condition where usual heap shaping methodologies can be used to make so that the value read out of bound for the size field changes in-between the reads, leading to an out-of-bound write, if size value read the second time is bigger than the first.
The NtQuerySystemInformation
with the SystemPolicyInformation
with command_id=100
involved in this vulnerability is reachable from a low privilege user and potentially running in an LPAC container, as such, could be leveraged in an sandbox escape scenario.
2024-04-08 - Vendor Disclosure
2024-08-13 - Vendor Patch Release
2024-08-13 - Public Release
Discovered by Philippe Laulheret of Cisco Talos.