概述
在这篇博文中,我们研究了cldflt.sys2024 年 3 月在 Microsoft Windows Cloud Minifilter(即)中发现的竞争条件。此漏洞于 2025 年 10 月修补并分配CVE-2025-55680。
该漏洞发生在函数内,当用户发出HsmpOpCreatePlaceholders()该函数以在同步根目录树下创建一个或多个新的占位符文件或目录时,该函数会被调用。CfCreatePlaceholders()
在创建占位符文件之前,该HsmpOpCreatePlaceholders()函数会通过检查保存文件名的用户空间缓冲区是否包含\或:字符来验证文件名。此验证是在 CVE-2020-17136 漏洞修补时引入的。在检查用户提供的文件名后,它会调用 函数FltCreateFileEx2()来创建文件。由于此检查的实现方式,文件名验证和文件创建之间存在一个时间窗口,用户可以在此期间更改文件名,从而允许用户在系统的任何位置创建文件或目录,从而导致权限提升。
背景
微软云 API
同步引擎是一种在本地主机和远程主机(例如)之间同步文件和目录的服务OneDrive。它使用Cloud API 中的实现cldapi.dll。[1]同步根文件夹是注册为已同步文件夹的根文件夹,其中所有嵌套的文件和目录均已同步。同步根目录中包含的所有文件都处于以下状态之一:
固定完整文件– 文件已水化,即内容由用户明确从云端下载(例如从资源管理器上下文菜单)。
完整文件– 文件持续更新,即持续从云端下载内容,以保持文件更新。如果空间不足,可以停止下载。
占位符– 当用户访问文件时,同步提供程序会自动对文件进行数据融合。文件状态受同步根文件夹上设置的融合策略影响。
同步根文件夹及其所有子文件夹根据其上设置的填充策略遵循不同的行为。同步提供程序负责使用CfRegisterSyncRoot()中的 API 实现将目录初始化为同步根文件夹cldapi.dll。
HRESULT CfRegisterSyncRoot(
[in] LPCWSTR SyncRootPath,
[in] constCF_SYNC_REGISTRATION *Registration,
[in] constCF_SYNC_POLICIES *Policies,
[in] CF_REGISTER_FLAGS RegisterFlags
);
提供程序的信息(例如名称和版本)在参数中提供Registration,而Policies参数指定应应用于文件夹及其嵌套文件的策略。同步行为很大程度上取决于设置的策略。
部分政策如下:
CF_HYDRATION_POLICY:水合策略定义文件何时水合,即用云数据填充。例如,如果策略设置为CF_HYDRATION_POLICY_FULL在请求时完全下载文件或目录(即使只请求了一个字节)。如果发生CF_HYDRATION_POLICY_ALWAYS_FULL脱水,即文件内容被丢弃,操作将失败。
CF_POPULATION_POLICY:填充策略定义了目录的填充方式,即何时下载目录中的文件。例如,如果设置为 ,CF_POPULATION_POLICY_ALWAYS_FULL则目录会持续更新云目录内容。如果设置为 ,则CF_POPULATION_POLICY_FULL目录会在用户导航到该目录时完全填充。最后,如果CF_POPULATION_POLICY_PARTIAL设置为 ,则目录会仅下载用户在导航过程中请求的信息进行填充。
cldapi.dll由云文件微过滤器驱动程序支持,即,cldflt.sys其中所有功能都是通过文件系统过滤器功能实现的。
云文件微过滤器驱动程序
云文件微过滤驱动 (Cloud Files Minifilter driver cldflt.sys) 由微软提供,用于向云应用程序(例如OneDrive)公开文件系统功能。它是一个文件系统过滤驱动,默认注册到过滤管理器。它通过一些 I/O 控制或主要函数类型来使用。它注册了以下MajorFunction代码的回调:
IRP_MJ_CREATE– 创建/打开文件或目录。
IRP_MJ_CLEANUP– 文件或目录已关闭,其引用计数器已达到零。
IRP_MJ_DIRECTORY_CONTROL– 主要函数在用户空间应用程序执行类似 API 时使用ReadDirectoryChangesW()。
IRP_MJ_FILE_SYSTEM_CONTROL– 当用户空间应用程序调用时使用NtDeviceIoControlFile()。
IRP_MJ_LOCK_CONTROL– 由 I/O 管理器发出,用于管理独占访问设置。
IRP_MJ_READ– 请求读取文件或目录。
IRP_MJ_WRITE– 请求写入文件或目录。
IRP_MJ_QUERY_INFORMATION– 请求获取文件或目录信息。
IRP_MJ_SET_INFORMATION– 请求设置文件或目录信息。
云文件微过滤驱动 – IOCTLs
对于NtDeviceIoControlFile()和NtFsControlFile()用户空间 API,云文件微筛选器驱动程序定义了两个回调:
HsmFltPreFILE_SYSTEM_CONTROL():实际操作之前执行的回调。
HsmFltPostFILE_SYSTEM_CONTROL():实际操作后执行的回调。
在回调执行操作之前处理和请求,NtDeviceIoControlFile()之后执行操作。NtFsControlFile()HsmFltPreFILE_SYSTEM_CONTROL()HsmFltPostFILE_SYSTEM_CONTROL()
回调HsmFltPreFILE_SYSTEM_CONTROL()会过滤多个 I/O 控制代码请求。其中一些如下:
FSCTL_DELETE_REPARSE_POINT– 默认情况下,此 I/O 控制代码请求被拒绝。
FSCTL_GET_REPARSE_POINT– 此 I/O 控制代码用于获取文件或目录的重新解析点设置。
FSCTL_SET_REPARSE_POINT– 此 I/O 控制代码用于设置文件或目录的重新解析点。
FSCTL_SET_REPARSE_POINT_EX– 此 I/O 控制代码用于设置文件或目录的重新解析点。
0x903BCcldapi.dll– 这是用于执行以下操作的自定义 I / O 控制代码:
CfpRegisterSyncRoot(),,CfConvertToPlaceholder()。CfCreatePlaceholders()
云文件微过滤器驱动程序 – IOCTL 0x903bc 操作 0xC0000001
当 I/O 控制码为、0x903bc和时,表示已发出请求。同步提供程序使用该API创建一个或多个占位符文件和目录。[3]Tag0x9000001AOpType0xC0000001CfCreatePlaceholdersCfCreatePlaceholders()
原型如下图所示:
HRESULT CfCreatePlaceholders(
[in] LPCWSTR BaseDirectoryPath,
[in, out] CF_PLACEHOLDER_CREATE_INFO *PlaceholderArray,
[in] DWORD PlaceholderCount,
[in] CF_CREATE_FLAGS CreateFlags,
[out] PDWORD EntriesProcessed
);
以下清单显示了CF_PLACEHOLDER_CREATE_INFO数据结构。
typedefstructCF_PLACEHOLDER_CREATE_INFO {
LPCWSTR RelativeFileName;
CF_FS_METADATA FsMetadata;
LPCVOID FileIdentity;
DWORD FileIdentityLength;
CF_PLACEHOLDER_CREATE_FLAGS Flags;
HRESULT Result;
USN CreateUsn;
} CF_PLACEHOLDER_CREATE_INFO;
typedefstructCF_FS_METADATA {
FILE_BASIC_INFO BasicInfo;
LARGE_INTEGER FileSize;
} CF_FS_METADATA;
APICfCreatePlaceholders()打开BaseDirectoryPath文件并向其发送包含代码的 I/O 控制请求0x903BC。它会发送一个缓冲区作为输入,其中包含作为参数传递给 API 的所有信息CfCreatePlaceholders()。输入缓冲区遵循ioctl_0x903BC数据结构。作为输入缓冲区发送到文件的完整数据结构BaseDirectoryPath如下所示。输入BaseDirectoryPath缓冲区必须是同步根文件夹中的目录;否则请求将失败。
Offset Length Field Description
------- ------- ------------------------- ---------------------------------------------
0x00 4 Tag Setto0x9000001A.
0x04 4 OpType Setto0xC0000001.
...
0x0C 4 size Sizeof the 'create_placeholder_t'.
...
0x10 8 placeholder_payload Pointer to the 'create_placeholder_t'
data structure
...
CfCreatePlaceholders()由实现的API构建的输入缓冲区包含指向字段中数据结构cldapi.dll的指针。
create_placeholder_tplaceholder_payload
数据结构create_placeholder_t如下所示。
Offset Length Field Description
------- ------- ------------------------- ---------------------------------------------
0x08 2 relativeName_offset The offset to the 'relName' start.
0x0A 2 relativeName_len The 'relName' size.
0x0C 2 fileidentity_offset The offsetto the 'fileid' start.
0x0e 2 fileidentity_len The 'fileid' size.
...
0x2e 4 fileAttributes The fileattributestoapply
to the created file.
...
0x50 VAR relName The relativefilename content.
VAR VAR fileid The fileidentity content.
当PlaceholderCount大于一时,则placeholder_payload是结构数组create_placeholder_t,PlaceholderCount信息不包含在ioctl_0x903BC数据结构中,因为内核空间中的处理通过继续读取placeholder_payload缓冲区直到完成来进行。
漏洞
该漏洞存在于驱动程序HsmpOpCreatePlaceholders()中实现的函数中cldflt.sys,该函数在处理具有以下特征的 IOCTL 时存在:
I/O控制代码是0x903BC。
输入缓冲区具有ioctl_0x903BC格式。
标签设置为IO_REPARSE_TAG_CLOUD。
大小必须大于或等于0x98。
操作等同于0xC0000001,即创建一个占位符。
在创建占位符时,驱动程序会检查同步根目录的可访问性和调用该HsmiOpPrepareOperation()函数的权限,从而导致调用该HsmFltProcessCreatePlaceholders()函数。
// cldflt.sys
__int64 __fastcall HsmFltProcessCreatePlaceholders(
__int64 a1,
PFLT_INSTANCE *a2,
PFILE_OBJECT baseDirObj,
__int64 a4,
__int64 a5,
PVOID Object,
__int128 *a7,
PFLT_CALLBACK_DATA CallbackData,
ioctl_0x903BC *inputBuffer,
unsigned int inputBufferSize,
char a11)
{
[Truncated]
[1]
if ( inputBufferSize < 0x20 || (v14 = inputBuffer, inputBuffer->size < 0x50) )
{
[Truncated]
}
[2]
SyncPolicy = HsmpRelativeStreamOpen(
(__int64)a2,
baseDirObj,
0i64,
0x100000u,
1u,
32,
v26,
&a7,
(PFILE_OBJECT *)&Object);
HsmDbgBreakOnStatus((unsigned int)SyncPolicy);
if ( SyncPolicy < 0 )
{
[Truncated]
}
[Truncated]
[3]
SyncPolicy = HsmpOpCreatePlaceholders(a2, a7, syncPolicy, inputBuffer->placeholder_payload, inputBuffer->size, &v27);
[Truncated]
}
在 [1] 处,它检查输入缓冲区大小(即函数nInBufferSize中的参数DeviceIoControl())是否大于0x20。它还检查 是否ioctl_0x903BC.size大于或等于0x50,这意味着placeholder_payload缓冲区的长度大于0x4f字节。如果其中一个约束不满足,则会抛出错误。
然后在 [2] 处,它调用HsmpRelativeStreamOpen()函数打开TargetFileObject存储在结构体中的文件,该文件是接收 I/O 控制请求(即)PFLT_CALLBACK_DATA的文件。文件句柄存储在变量中。句柄通过内部调用函数打开。如果返回任何错误,则程序将退出。0x903BCBaseDirectoryPatha7FltCreateFileEx()
在 [3] 处,它调用HsmpOpCreatePlaceholders()最终处理请求的函数。该函数通过将placeholder_payload指针、指针大小以及接收 I/O 控制请求的目录句柄(即BaseDirectoryPath)作为参数来调用。
// cldflt.sys
__int64 __fastcall HsmpOpCreatePlaceholders(
PFLT_INSTANCE *a1,
__int128 *DirHandle,
int syncPolicy,
create_placeholder_t *placeholderPayload,
ULONG placeholderPayload_size,
int *out)
{
syncPolicy_cp = syncPolicy;
dirHandle_cp = DirHandle;
P = a1;
v51 = DirHandle;
placeholderPayload_size_cp = placeholderPayload_size;
v66 = out;
[Truncated]
v53 = 0;
[Truncated]
[4]
MemoryDescriptorList = IoAllocateMdl(placeholderPayload, placeholderPayload_size, 0, 0, 0i64);
if ( !MemoryDescriptorList )
{
[Truncated]
}
[5]
ProbeForRead(placeholderPayload, placeholderPayload_size, 4u);
v10 = MemoryDescriptorList;
MmProbeAndLockPages(MemoryDescriptorList, 1, IoReadAccess);
if ( (MemoryDescriptorList->MdlFlags & 5) != 0 ) // non paged pool | SYSTEM_VA_MAPPED
MappedSystemVa = (create_placeholder_t *)MemoryDescriptorList->MappedSystemVa;
else
MappedSystemVa = (create_placeholder_t *)MmMapLockedPagesSpecifyCache(
MemoryDescriptorList,
0,
MmCached,
0i64,
0,
0x40000010u);
mmapped_userspace_region = MappedSystemVa;
if ( !MappedSystemVa )
{
[Truncated]
}
[Truncated]
[6]
while ( 1 )
{
memset(&placeholderPayload_stack, 0, sizeof(placeholderPayload_stack));
v16 = (create_placeholder_t *)((char *)mmapped_userspace_region + v53);
v51 = v16;
v65 = 0i64;
memset&ObjectAttributes, 0, 44);
[Truncated]
[7]
*(_OWORD *)&placeholderPayload_stack.unknown_0 = *(_OWORD *)&v16->unknown_0;
*(_OWORD *)placeholderPayload_stack.unknown_1 = *(_OWORD *)v16->unknown_1;
*(_OWORD *)&placeholderPayload_stack.unknown_1[16] = *(_OWORD *)&v16->unknown_1[16];
*(_OWORD *)&placeholderPayload_stack.fileAttributes = *(_OWORD *)&v16->fileAttributes;
*(_OWORD *)&placeholderPayload_stack.minus_1 = *(_OWORD *)&v16->minus_1;
[Truncated]
[8]
v21 = (int)v51;
v22 = 0;
if ( HIWORD(relName_sz) >> 1 )
{
while ( 1 )
{
v23 = *(_WORD *)((char *)v51 + 2 * v22 + relName_offset);
if ( v23 == '\\' || v23 == ':' )
break;
if ( ++v22 >= (unsigned __int16)(HIWORD(relName_sz) >> 1) )
goto LABEL_51;
}
LODWORD(v24) = 0xC000CF0B;
HsmDbgBreakOnStatus(0xC000CF0Bi64);
v27 = 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_162;
}
v28 = 107i64;
gotocontinue;
[Truncated]
}
[Truncated]
[9]
*((_QWORD *)&v65 + 1) = (char *)v51 + placeholderPayload_stack.relativeName_offset;
LOWORD(v65) = placeholderPayload_stack.relativeName_len;
WORD1(v65) = placeholderPayload_stack.relativeName_len;
ObjectAttributes.Length = 48;
ObjectAttributes.RootDirectory = dirHandle_cp;
ObjectAttributes.Attributes = 576;
ObjectAttributes.ObjectName = (PUNICODE_STRING)&v65;
*(_OWORD *)&ObjectAttributes.SecurityDescriptor = 0i64;
CreateOptions = (v33 != 0) | 0x208020;
[10]
LODWORD(v24) = FltCreateFileEx2(
Filter,
Instance,
&FileHandle,
&FileObject,
0x100180u,
&ObjectAttributes,
&IoStatusBlock,
&AllocationSize,
FileAttributes,
0,
2u,
CreateOptions,
0i64,
0,
0x800u,
&DriverContext);
[Truncated]
continue:
v29 = (PFILE_OBJECT)dirHandle_cp;
[Truncated]
if ( (int)v24 >= 0 || (syncPolicy_cp & 1) == 0 )
{
v38 = LODWORD(placeholderPayload_stack.unknown_0) ? LODWORD(placeholderPayload_stack.unknown_0) + v53 : 0;
v53 = v38;
if ( v38 )
continue;
}
[Truncated]
在 [4] 处,HsmpOpCreatePlaceholders()函数调用IoAllocateMdl()分配内存描述符列表来描述用户空间缓冲区(即placeholderPayload),并在 [5] 处验证该缓冲区是否为用户空间缓冲区。由于该地址不属于非分页池,并且尚未映射到内核虚拟地址空间,因此通过调用该函数进行映射MmMapLockedPagesSpecifyCache()。
该MmMapLockedPagesSpecifyCache()函数将用户空间缓冲区映射到内核虚拟地址空间,因此用户空间缓冲区和MappedSystemVa都由相同的物理页面支持,这意味着用户空间缓冲区中的修改会反映到MappedSystemVa内存本身。
在 [6] 处,函数开始解析placeholderPayload缓冲区,并创建所有预期的占位符文件和目录。首先在 [7] 处,它将 中包含的所有信息设置placeholderPayload为堆栈变量,即placeholderPayload_stack。所有信息(relName和除外fileid)都被复制到该placeholderPayload_stack变量中。
relName在 [8] 处,它验证字段中包含的文件名placeholderPayload,并检查所有宽字符。如果任何 字符等于\或:字符,它将停止当前relName处理并继续更新v53变量,该变量表示要处理的 下一个字节的偏移量placeholderPayload。最后在 [9] 处,如果relName被认为是有效的,则ObjectAttributes填充结构,将RootDirectory句柄设置为 I/O 控制请求所针对的目录0x903BC(即BaseDirectoryPath),并将 设置ObjectName为mmapped_userspace_region.relName地址。
由于mmapped_userspace_region是从用户空间缓冲区映射的内存,因此用户有可能在执行mmapped_userspace_region.relName之前更改存储在 中的文件名FltCreateFileEx2()[15]。FltCreateFileEx2()通过将IO_IGNORE_SHARE_ACCESS_CHECK值作为Flags参数传递来调用 ,并将OBJECT_ATTRIBUTES.Attributes设置为OBJ_KERNEL_HANDLE | OBJ_INHERIT掩码。
在 [8] 和 [10] 之间存在一个时间窗口,恶意攻击者可以placeholderPayload.relName通过插入\字符来更改字符串,从而获得任意文件/目录的创建权限。由于该FltCreateFileEx2()函数并非通过指定任何标志来调用,如果遇到符号链接/连接点,该标志会使其返回错误,因此攻击者可以通过将 设置为 来请求创建占位符relName。JUSTASTRINGDnewfile.dll如果攻击者获胜,relName内容将从 更改为JUSTASTRINGDnewfile.dll。JUSTASTRING\newfile.dll接下来,如果JUSTASTRING目录存在并且它是用户不可写的目录的连接点,FltCreateFileEx2()则将按照连接点newfile.dll在不可写目录中创建 。
开发
为了利用此漏洞,必须执行以下步骤:
设置环境——注册同步根目录并创建连接点。
通过生成多个线程来触发漏洞,其中一些线程执行,一些线程尝试在验证和调用之间CfCreatePlaceholders()更改。relNamerelNameFltCreateFileEx2()
使用 DLL 侧加载来执行权限提升。
清理后期开发。
这些步骤的详细说明如下:
利用步骤
步骤 1 – 设置环境
该漏洞利用必须将一个目录注册为同步根目录,这可以通过调用CfRegisterSyncRoot()API 来完成。然后,它必须在同步根目录中创建一个新目录(例如JUSTASTRING),使其成为与不可写目录(例如)的连接点C:\Windows\System32。
步骤 2 – 触发漏洞
该漏洞可以通过并行运行多个线程来利用,其中一个线程负责监视文件是否被创建,而其他一些线程则在无限循环中运行以创建占位符操作,其余线程则更改创建占位符操作中发送的用户空间缓冲区中的字节(即relName),如下所示:
监视线程——负责监视文件是否已创建的线程。
创建占位符线程 ——执行创建占位符操作的线程。
文件名更改器线程 ——负责更改文件名以利用函数中的检查时间到使用时间漏洞的线程HsmpOpCreatePlaceholders()。
假设漏洞利用者想要创建DLL,那么一旦文件被创建,C:\Windows\System32\newfile.dll监视线程就会停止竞争。C:\Windows\System32\newfile.dll
创建占位符线程将通过打开同步根目录并向0x903BC其发出 I/O 控制代码来持续发出创建占位符操作。所有这些线程都发送与ioctl_0x903BC数据结构格式相同的输入缓冲区。占位符文件名ioctl_0x903BC.placeholder_payload.relName设置为JUSTASTRINGDnewfile.dll,ioctl_0x903BC.placeholder_payload.fileAttributes字段设置为FILE_ATTRIBUTE_NORMAL。
fileName Changer 线程将 连续将D字符(即宽字符串的八个字节JUSTASTRINGDnewfile.dll)从D更改为\,反之亦然,两次更改之间会有短暂的延迟。
这样,就可以绕过relName字段的验证,即检查relName不包含任何\字符,从而到达设置FltCreateFileEx2()为 的函数。如果验证后立即发生更改,则该函数将遵循在目录上创建的连接,并在 中创建。relNameJUSTASTRING\newfile.dllrelNamerelNameFltCreateFileEx2()JUSTASTRINGnewfile.dllC:\Windows\System32
一旦监视线程 检测到C:\Windows\System32\newfile.dll创建,它就会将恶意文件内容写入其中并开始权限提升阶段。
步骤 3 – 权限提升
一旦创建了恶意文件,就可以通过利用 DLL 侧加载来提升系统权限。
关于Exodus Intelligence
我们世界一流的漏洞研究人员团队发现了数百个独家零日漏洞,在攻击者发现之前为客户提供专有知识。我们还开展 N 日漏洞研究,我们会筛选关键的 N 日漏洞,并进行深入研究,以证明这些漏洞是否真的可以在野外利用。
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里

