Talos Vulnerability Report

TALOS-2024-1964

Microsoft CLIPSP.SYS License update signature check bypass vulnerability

August 13, 2024
CVE Number

CVE-2024-38184

SUMMARY

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.

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

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

CWE

CWE-349 - Acceptance of Extraneous Untrusted Data With Trusted Data

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 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.

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.