CVE-2024-38184
A signature check bypass 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 license tampering. 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
6.2 - CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
CWE-349 - Acceptance of Extraneous Untrusted Data With Trusted Data
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 snippet 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
When calling SPCallServerHandleUpdateLicense
the data will eventually be past to an obfuscated function inside clipsp
that is responsible for parsing the license blob, and does the following (see 0x1C00E8AC4):
while ( 1 )
{
v10 = (LicenseParsing_entry *)&LicenseData[v7];
entry_size = v10->entry_size;
if ( entry_size >= 0xFFFFF || v7 + entry_size > LicenseDataSize )
return (unsigned int)STATUS_INVALID_PARAMETER;
status = license_validate_entry((LicenseParsing_entry *)&LicenseData[v7], LicenseDataSize - v7);
if ( (status & 0x80000000) != 0 ) // bad
return status;
v12 = license_convert_type((unsigned __int16)v10->type);// 0xce -> type 14 -> pfn
if ( v12 < 25 )
{
v13 = v12;
[6] 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;
[0] if ( v12 == 24 ) // type 0xCC
break;
}
v7 += 8;
if ( v12 ) // not case 0 (aka 0x14 in license file)
goto LABEL_10;
LABEL_11:
if ( v7 >= (unsigned __int64)LicenseDataSize - 8 )
return status;
}
License->LicenseData_ = (__int64)LicenseData;
[1] License->entry_of_type_24 = (__int64)v10;
ExFreePoolWithTag_noTag(0i64);
status = 0;
v7 += 8;
LABEL_10:
v7 += v10->entry_size;
[5] goto LABEL_11;
}
We can see that at [0], if a type 0xCC (24 in the internal representation of ClipSp) is encountered, the loop is exited temporarily, and a pointer to the current entry is saved at [1] in a global structure. It is then used during signature verification, as such (See address 0x01C00EA22B):
sig_len = LicenseStruct_get_some_size_for_type24(License);
sig_data = (unsigned __int8 *)LicenseStruct_get_some_ptr_for_type24(License);
status_ = License_validate_signature(
context->alg_SHA256,
hKey,
bcryptFlags,
[2] (UCHAR *)License->LicenseData_,
[3] LODWORD(License->entry_of_type_24) - LODWORD(License->LicenseData_),
&extra_data_array,
UNUSED_ARG(),
sig_data,
sig_len);
Which does the following:
__int64 __fastcall License_validate_signature(
BCRYPT_ALG_HANDLE hAlgorithm,
BCRYPT_KEY_HANDLE hKey,
ULONG bcryptFlags,
UCHAR *data,
ULONG dataLen,
hash_extra_data *extra_data_array,
__unused unsigned int cbHashLen,
unsigned __int8 *sig_data,
unsigned int sig_len)
{
_BCRYPT_PKCS1_PADDING_INFO PaddingInfo; // rbx
int status; // edi
PUCHAR pbHash; // [rsp+40h] [rbp-18h] BYREF
const WCHAR *v15; // [rsp+48h] [rbp-10h] BYREF
PaddingInfo.pszAlgId = 0i64;
v15 = 0i64;
pbHash = 0i64;
cbHashLen = 0;
status = compute_hash_on_data_ex(hAlgorithm, data, dataLen, extra_data_array, 1u, &pbHash, &cbHashLen);
if ( status >= 0 )
{
if ( (bcryptFlags & BCRYPT_PAD_PKCS1) != 0 )
{
v15 = L"SHA256";
PaddingInfo.pszAlgId = (LPCWSTR)&v15;
}
[4] status = BCryptVerifySignature(
hKey,
(void *)PaddingInfo.pszAlgId,
pbHash,
cbHashLen,
sig_data,
sig_len,
bcryptFlags);
}
ExFreePoolWithTag_noTag(pbHash);
return (unsigned int)status;
}
The function computes the hash of the data provided at [2] assuming a length of [3] and then verify the associated signature at [4]. However, we can see at [5] that the processing of the license file can continue even though the signature blob (type 0xcc) has been reached. This means that any data added after the signature blob will be processed and never checked for authenticity during the signature check. Furthermore, because the way data is saved in the License structure at [6] repeating tags will overwrite entries already populated. As such it’s possible to tamper with an existing license file and edit its content without having to obtain the proper signature for it. This attack can be conducted with a low privilege user and/or from within a LPAC container thus allowing a sandboxed process to potentially tamper with more privileged files.
2024-04-08 - Vendor Disclosure
2024-08-13 - Vendor Patch Release
2024-08-13 - Public Release
Discovered by Philippe Laulheret of Cisco Talos.