Talos Vulnerability Report

TALOS-2024-1965

Microsoft CLIPSP.SYS License update out-of-bounds read vulnerability

August 13, 2024
CVE Number

CVE-2024-38185

SUMMARY

Multiple out-of-bounds read vulnerabilities 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 information disclosure. An attacker can use the NtQuerySystemInformation function call to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

CLIPSP.SYS - https://www.microsoft.com/en-us/windows/windows-11

CVSSv3 SCORE

8.4 - CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-125 - Out-of-bounds Read

DETAILS

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.

Context

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 vulnerability

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 0x1. This vulnerability can be triggered by feeding to the SPCallServerHandleUpdateLicense function a license filed that has been tampered with to include a malformed type 0x1 blob.

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 0x1 (internal value representation is 17) is handled throughout the code especially during the DeviceLicenseInstall function (see 0x001C00EDB0C). The data is expected to be a blob of 4100 bytes with a 4 bytes header and 4096 bytes of data used for generating key material / authentication data in the case of a device-bound license. However the size of the data is never verified and as such, out-of-bound reads can occur in multiple location of the code when provided with a type 0x1 blob whose data has been truncated. Be advised that in normal circumstances, a signature check would prevent from tampering with a license file, but that check can also be bypassed and was filed in TALOS-2024-1964 report.

First out-of-bound read occurs when dereferencing the first two bytes of the data provided as no size check is ever performed:

 __int64 __fastcall __spoils<rdx,rax> License_get_version_or_size_for_type17_via_deref_clepstuff(LicenseStruct *License) // at 00001C00E7658 
{
  unsigned __int16 *entry_ptr; // rdx
  __int64 result; // rax

  entry_ptr = (unsigned __int16 *)License->field_0[17].entry_ptr;
  result = 0i64;
  if ( entry_ptr )
    return *entry_ptr; //2 bytes out of bound read 
  return result;
}

A second and third out-of-bound reads occur reading the next two bytes in the payload used to determine which version of the code path to use: when going through the DeviceLicenseInstall code path (at 001C00EDBF2) a pointer to the data is obtained, and passed to ReleaseKeyFromEncState_internal which calls ClepReleaseKeyFromEncState then reads the first two bytes of that payload: Indeed, consider the following code:

Get the pointer:

    //01C00E763C 
    __int64 __fastcall __spoils<rax,rcx> License_get_ptr_for_clep_type17(LicenseStruct *License)
{
  return (License->field_0[17].entry_ptr + 2) & -(__int64)(License->field_0[17].entry_ptr != 0);
}

Use the data provided to release the key:

   //In DeviceInstall at 001C00EDBF2  

if ( (License->license_status_bitmask & 4) == 0 )
{
  version_or_size_for_type17_via_deref_clepstuff = License_get_version_or_size_for_type17_via_deref_clepstuff(License);
  ptr_for_clep_type17 = (int *)License_get_ptr_for_clep_type17(License);
  status = ReleaseKeyFromEncState_internal(
             UNUSED_ARG(),
  [0]               ptr_for_clep_type17,
             version_or_size_for_type17_via_deref_clepstuff,
             &hKey);
  if ( status < 0 )     
    goto DONE;
    
    ...
 }

The key release process:

_int64 __fastcall ReleaseKeyFromEncState_internal(
    __unused __int64 a1,
    int *ClepStuff,
    int size,
    BCRYPT_KEY_HANDLE *keyHandle)
{
  int v6; // ebx
  BCRYPT_ALG_HANDLE hAlgorithm; // [rsp+30h] [rbp+8h] BYREF
  int v9; // [rsp+40h] [rbp+18h] BYREF

  hAlgorithm = 0i64;
  if ( size )
  {
    v6 = CLIP_open_alg(aAes, (PUCHAR)aChainingmodecb, &hAlgorithm, &v9);
    if ( v6 >= 0 )
      v6 = ((__int64 (__fastcall *)(int *, BCRYPT_ALG_HANDLE, BCRYPT_KEY_HANDLE *))ClepReleaseKeyFromEncState)(
             ClepStuff,
             hAlgorithm,
             keyHandle);
    if ( hAlgorithm )
      BCryptCloseAlgorithmProvider(hAlgorithm, 0);
  }

And the problematic derefencing at [1] and [2]:

   //0001C00F0248   
__int64 __fastcall ClepReleaseKeyFromEncState(int *ClepStuff, void *hAlgorithm, BCRYPT_KEY_HANDLE *keyHandle)
{
  int v7; // [rsp+40h] [rbp+8h] BYREF

  if ( (unsigned int)g_log_context > 5 )
  {
    v7 = *ClepStuff;
    CLIP_log_stuff3(UNUSED_ARG(), a2, UNUSED_ARG(), UNUSED_ARG(), (ULONGLONG)&v7);
  }
 [1]  if ( *ClepStuff == 2 )
    return ClepTpmReleaseKeyFromEncState_version2((__int64)ClepStuff, hAlgorithm, keyHandle);
 [2] if ( *ClepStuff == 4 )
    return ClepTpmReleaseKeyFromEncState_version4_((__int64)ClepStuff, hAlgorithm, keyHandle);
  return 0xC0000001i64;

The functions ClepTpmReleaseKeyFromEncState_version2 and ClepTpmReleaseKeyFromEncState_version4_ also process the data out of bound (the ClepStuff variable is the same data we obtained at [0]).

Another series of out-of-bound reads occurs a little later in the DeviceLicenseInstall function assuming previous steps where successful (which may depend on the data being read out-of-bound):

The same data is obtained in the clep_stuff pointer and passed to the generate_license_auth_data function that will then dereference the data to determine its type and how to handle it, and then copy part of its content into the context->license_auth_data buffer:

//00001C00EDD5A
clep_stuff = (_DWORD *)License_get_ptr_for_clep_type17(License);
status = generate_license_auth_data(clep_stuff, deviceId, deviceIdSize, &context->license_auth_data);

//001C00F0218 
__int64 __fastcall generate_license_auth_data(_DWORD *clep_stuff, char *deviceId, __int64 deviceIdSize, __int64 *a4)
{
  if ( *clep_stuff == 2 ) //oob-read here
    return generate_license_auth_data_v2(clep_stuff, deviceId, deviceIdSize, a4);
  if ( *clep_stuff == 4 ) //oob-read here
    return generate_license_auth_data_v4((__int64)clep_stuff, (__int64)deviceId, deviceIdSize, a4);
  return 3221225473i64;

For example, in the case the data is of type 4, we can see:

  //001C00F1CB0    
  __int64 __fastcall generate_license_auth_data_v4(
        __int64 clep_stuff,
        __int64 deviceId,
        __int64 deviceIdSize,
        __int64 *pDest)
{
  int status; // r8d
  __int64 pDest_1; // rdx

  status = CLIP_allocate_buffer(148i64, pDest);
  if ( buffer >= 0 )
  {
    pDest_1 = *pDest;
    *(_OWORD *)pDest_1 = *(_OWORD *)(clep_stuff + 1722);
    *(_OWORD *)(pDest_1 + 16) = *(_OWORD *)(clep_stuff + 1738);
    *(_OWORD *)(pDest_1 + 32) = *(_OWORD *)(clep_stuff + 1754);
    *(_OWORD *)(pDest_1 + 48) = *(_OWORD *)(clep_stuff + 1770);
    *(_OWORD *)(pDest_1 + 64) = *(_OWORD *)(clep_stuff + 1786);
    *(_OWORD *)(pDest_1 + 80) = *(_OWORD *)(clep_stuff + 1802);
    *(_OWORD *)(pDest_1 + 96) = *(_OWORD *)(clep_stuff + 1818);
    *(_OWORD *)(pDest_1 + 112) = *(_OWORD *)(clep_stuff + 1834);
    *(_OWORD *)(pDest_1 + 128) = *(_OWORD *)(clep_stuff + 1850);
    *(_DWORD *)(pDest_1 + 144) = *(_DWORD *)(clep_stuff + 1866);
  }
  return (unsigned int)status;
}

The case of type 2 (generate_license_auth_data_v2) also reads data out of bound but is left out for brevity of this report.

It’s important to note that the data read out-of-bound into the context->license_auth_data is also used in the ClipSpGetLicenseChallange function (at 00001C00B7E70) to generate a challenge when the appropriate call is made (NtQuerySystemInformation with the SystemPolicyInformation and command_id=105) thus potentially leaking back to userland part of the the data that was read out of bound.

TIMELINE

2024-04-08 - Vendor Disclosure
2024-08-13 - Vendor Patch Release
2024-08-13 - Public Release

Credit

Discovered by Philippe Laulheret of Cisco Talos.