Skip to content

Commit 4ee4cde

Browse files
committed
1 parent cf9f43e commit 4ee4cde

File tree

8 files changed

+288
-0
lines changed

8 files changed

+288
-0
lines changed
68 KB
Loading
11.5 KB
Loading
58.6 KB
Loading
110 KB
Loading
152 KB
Loading
143 KB
Loading
64.7 KB
Loading
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
---
2+
title: "CVE-2025-55680 - Windows Cloud Files Mini Filter Driver Elevation of Privilege Vulnerability"
3+
pubDate: 2025-04-28
4+
author: "Ghostbyt3"
5+
tags: ["1day", "cldflt.sys", "windows", "kernel"]
6+
description: "A vulnerability in Windows Cloud Files Mini Filter Driver arises from mapping user-controlled buffers into kernel space and relying on them for both path validation and file creation. By racing a single-byte change in the shared buffer between these steps, an attacker can bypass validation and create arbitrary files in System32 via a junction, enabling SYSTEM-level privilege escalation through DLL hijacking."
7+
---
8+
9+
The vulnerability exists in `HsmpOpCreatePlaceholders()`, which uses `MmMapLockedPagesSpecifyCache()` to map userspace buffers into kernel space, creating shared physical memory. During placeholder file creation, the driver validates filenames for path traversal characters (`\`, `:`) by reading from this shared memory, then uses the same memory for `FltCreateFileEx2()` file creation. An attacker exploits the race window by rapidly flipping a single character (e.g., `'D'``\'`) in the shared buffer between validation ("JUSTASTRINGDfile.dll" passes) and file creation ("JUSTASTRING\file.dll" enables traversal). Combined with a pre-created junction (JUSTASTRING → C:\Windows\System32), this allows creating arbitrary files in System32 with SYSTEM privileges, leading to privilege escalation via DLL hijacking.
10+
11+
**CVE-2025-55680:** https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-55680
12+
**Vulnerability Type:** Time-of-check Time-of-use (TOCTOU) Race Condition
13+
**Driver Version:** cldlft.sys - 10.0.26100.6725
14+
15+
## Vulnerability analysis
16+
17+
The function `HsmpOpCreatePlaceholders()` was identified as the vulnerable routine addressed in the latest CVE. By reviewing the call graph, the shortest path to this function originates from `HsmFltPreFILE_SYSTEM_CONTROL()`.
18+
19+
![image.png](/img/cve-2025-55680/image.png)
20+
21+
#### **HsmFltPreFILE_SYSTEM_CONTROL():**
22+
23+
`HsmFltPreFILE_SYSTEM_CONTROL()` is a pre-operation callback invoked by the Filter Manager before the actual I/O operation occurs (“Pre” indicates this; a corresponding “Post” callback also exists).
24+
25+
This function effectively acts as an IOCTL handler. If the IOCTL code equals `0x903BC`, it calls `HsmFltProcessHSMControl()`, which is one step closer to the vulnerable function.
26+
27+
This IOCTL is triggered when user space calls [`CfCreatePlaceholders()`](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfcreateplaceholders), which creates one or more placeholder files or directories under a registered sync root. The API resides in `cldapi.dll`, which issues the IOCTL. The I/O manager (ntoskrnl.exe) processes the request, and then the Filter Manager (fltmgr.sys) invokes the `HsmFltPreFILE_SYSTEM_CONTROL` callback.
28+
29+
![image.png](/img/cve-2025-55680/image%201.png)
30+
31+
The CallbackData passed to the `HsmFltProcessHSMControl()` function is a `_FLT_CALLBACK_DATA` structure. Where `Iopb` is an important member, because it contains all the user-provided parameters.
32+
33+
```cpp
34+
typedef struct _FLT_CALLBACK_DATA {
35+
FLT_CALLBACK_DATA_FLAGS Flags;
36+
PETHREAD Thread;
37+
PFLT_IO_PARAMETER_BLOCK Iopb;
38+
IO_STATUS_BLOCK IoStatus;
39+
struct _FLT_TAG_DATA_BUFFER *TagData;
40+
union {
41+
struct {
42+
LIST_ENTRY QueueLinks;
43+
PVOID QueueContext[2];
44+
};
45+
PVOID FilterContext[4];
46+
};
47+
KPROCESSOR_MODE RequestorMode;
48+
} FLT_CALLBACK_DATA, *PFLT_CALLBACK_DATA;
49+
```
50+
51+
The `CfCreatePlaceholders()` function accepts the following user input, where `BaseDirectoryPath` represents the registered sync root directory, `PlaceholderArray` is the pointer to an array of `CF_PLACEHOLDER_CREATE_INFO`.
52+
53+
```cpp
54+
HRESULT CfCreatePlaceholders(
55+
[in] LPCWSTR BaseDirectoryPath,
56+
[in, out] CF_PLACEHOLDER_CREATE_INFO *PlaceholderArray,
57+
[in] DWORD PlaceholderCount,
58+
[in] CF_CREATE_FLAGS CreateFlags,
59+
[out] PDWORD EntriesProcessed
60+
);
61+
```
62+
63+
Each placeholder describes either a file or a directory:
64+
```cpp
65+
typedef struct CF_PLACEHOLDER_CREATE_INFO {
66+
LPCWSTR RelativeFileName;
67+
CF_FS_METADATA FsMetadata;
68+
LPCVOID FileIdentity;
69+
DWORD FileIdentityLength;
70+
CF_PLACEHOLDER_CREATE_FLAGS Flags;
71+
HRESULT Result;
72+
USN CreateUsn;
73+
} CF_PLACEHOLDER_CREATE_INFO;
74+
```
75+
76+
When creating placeholders, each file or subfolder under the sync root receives one `CF_PLACEHOLDER_CREATE_INFO`. It is also valid to create a placeholder even when the file does not yet exist because placeholders represent metadata.
77+
78+
```cpp
79+
BaseDirectoryPath (the sync root)
80+
81+
├─── Placeholder 1 (file or subfolder)
82+
├─── Placeholder 2 (file or subfolder)
83+
├─── Placeholder 3 (file or subfolder)
84+
└─── ...
85+
```
86+
87+
After the IOCTL is issued, the input buffer is converted into an undocumented structure referred to here as `CREATE_PLACEHOLDER_STRUCT`.
88+
89+
```cpp
90+
Offset Length Field Description
91+
------- ------- ------------------------- ---------------------------------------------
92+
0x00 4 Tag Set to 0x9000001A.
93+
0x04 4 OpType Set to 0xC0000001.
94+
...
95+
0x0C 4 size Size of the 'CREATE_PLACEHOLDER_STRUCT'.
96+
...
97+
0x10 8 placeholder_payload Pointer to the 'CREATE_PLACEHOLDER_STRUCT' data structure
98+
```
99+
100+
The `CREATE_PLACEHOLDER_STRUCT` (`placeholder_payload`) is similar to `CF_PLACEHOLDER_CREATE_INFO` structure with few changes.
101+
102+
```cpp
103+
Offset Length Field Description
104+
------- ------- ------------------------- ---------------------------------------------
105+
0x08 2 relativeName_offset The offset to the 'RelativeFileName' start.
106+
0x0A 2 relativeName_len The 'RelativeFileName' size.
107+
0x0C 2 fileidentity_offset The offset to the 'FileIdentity' start.
108+
0x0e 2 fileidentity_len The 'FileIdentityLength' size.
109+
...
110+
0x2e 4 fileAttributes The file attributes to apply to the created file.
111+
...
112+
0x50 VAR relName The relative file name content.
113+
VAR VAR fileid The file identity content.
114+
```
115+
116+
Basically the following structure is sent as input buffer to 0x903BC IOCTL call.
117+
118+
```cpp
119+
typedef struct ioctl_0x903BC {
120+
DWORD Tag; // 0x9000001A
121+
DWORD OpType; // 0xC0000001
122+
DWORD unknown1;
123+
DWORD size;
124+
DWORD unknown2;
125+
PLACEHOLDER_CREATE_KERNEL* placeholder_payload; // ← USERSPACE POINTER!
126+
// ...
127+
} ioctl_0x903BC;
128+
```
129+
130+
From the IOCTL code, we can determine the method being used, because the `Iopb` contains the user input and we need to know whether its Buffered or Direct or Neither.
131+
132+
![image.png](/img/cve-2025-55680/image%202.png)
133+
134+
#### **HsmFltProcessHSMControl():**
135+
136+
Before calling `HsmFltProcessCreatePlaceholders()` function, it checks if the input buffer length is not lesser than 0x98 bytes. Then it checks if `OpType` is `0xC0000001`, this is set by `cldapi.dll` when it makes the IOCTL call.
137+
138+
![image.png](/img/cve-2025-55680/image%203.png)
139+
140+
#### **HsmFltProcessCreatePlaceholders()**
141+
142+
![image.png](/img/cve-2025-55680/image%204.png)
143+
144+
- It checks the user input buffer size (1️⃣) and moving on it calls `HsmpRelativeStreamOpen()` function (2️⃣) which verifies the directory provided in `BaseDirectoryPath` (member of **`CfCreatePlaceholders()`** function) is a registered sync root and check permissions and returns a handle.
145+
- Finally, it calls (3️⃣) the vulnerable function `HsmpOpCreatePlaceholders()` with the user buffer, also with the sync root directory handle.
146+
147+
**HsmpOpCreatePlaceholders()**
148+
149+
The `CREATE_PLACEHOLDER_STRUCT` structure is `Payload` here and first it calls `IoAllocateMdl()` to allocates a memory descriptor list (MDL) large enough to map a buffer, this is to map the user space buffer to kernel space.
150+
151+
Following that, **`ProbeForRead()`** is called which validates that the buffer (`Payload`) is in userspace and readable and calls **`MmProbeAndLockPages()`** which locks the physical pages in memory and prevents pages from being paged out to disk, the parameter `1` = `UserMode` (indicates this is a userspace buffer).
152+
153+
Finally, `MmMapLockedPagesSpecifyCache()` maps those locked physical pages that are described by the MDL (`v9`) into kernel virtual address space (`MappedSystemVa_Payload`). This returns a kernel virtual address (KVA) that the driver can use to directly access the same physical memory as the user buffer. No copying occurs. Because both the original user-mode pointer and the new kernel virtual address refer to the same underlying physical memory, any changes made through the user buffer will be visible through the kernel mapping—and any changes made through the kernel mapping will be visible to user space.
154+
155+
A while loop begins where it allocates some space in stack and copies the first placeholder values from the virtual kernel address (`v17`) to the stack (`Payload_in_Stack`) but this does not copy everything, the relative file name (`relName`) is not copied yet.
156+
157+
Another nested while loop, where it checks all the wide characters of the relative file name to see if any of the characters is equal to the`\`or the`:`character. This check is implemented to avoid any path traversal or [junction](https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions#junctions) in the file name (`..\..\Windows\System32\file.dll` or `C:\Windows\file.dll`), this is the TIME-OF-CHECK (TOC). It as a vulnerability fix implemented in CVE-2020-17136 and it’s writeup [here](https://project-zero.issues.chromium.org/issues/42451188).
158+
159+
Finally it calls, `FltCreateFileEx2()` function to create a new file or open an existing file, where `ObjectAttributes` (`_OBJECT_ATTRIBUTES`) structure contains `ObjectName` member which is pointer to aUnicode stringthat contains the name of the object for which a handle is to be opened.Here you see the relative file name is passed to it, but it takes it directly from the kernel virtual address (v68 → v54 → v17 → MappedSystemVa_Payload). This is the TIME-OF-USE (TOU).
160+
161+
Because the relative file name is taken from the KVA directly, if it’s changed in user space address, it will be changed here as well. So after the path traversal or junction check there is a time window available before it calls `FltCreateFileEx2()`. If we tamper the name after the check then with race condition it is possible to exploit this. Also, junction does not requires any elevation privilege.
162+
163+
```c++
164+
__int64 __fastcall HsmpOpCreatePlaceholders(
165+
PFLT_INSTANCE *a1,
166+
__m128i *a2,
167+
int a3,
168+
void *Payload,
169+
ULONG Length,
170+
_DWORD *a6)
171+
{
172+
173+
[::]
174+
175+
v9 = IoAllocateMdl(Payload, Length, 0, 0, 0);
176+
Mdl = v9;
177+
if ( !v9 )
178+
{
179+
180+
[::]
181+
182+
ProbeForRead(Payload, Length, 4u);
183+
v13 = Feature_2594491707__private_IsEnabledDeviceUsageNoInline() != 0;
184+
MmProbeAndLockPages(v9, 1, v13);
185+
if ( (v9->MdlFlags & 5) != 0 )
186+
MappedSystemVa_Payload = (char *)v9->MappedSystemVa;
187+
else
188+
MappedSystemVa_Payload = (char *)MmMapLockedPagesSpecifyCache(v9, 0, MmCached, 0, 0, 0x40000010u);
189+
v48 = MappedSystemVa_Payload;
190+
if ( !MappedSystemVa_Payload )
191+
{
192+
193+
[::]
194+
195+
196+
while ( 1 )
197+
{
198+
memset(Payload_in_Stack, 0, sizeof(Payload_in_Stack));
199+
v17 = (__m128i *)&MappedSystemVa_Payload[v56];
200+
v54 = v17;
201+
v68 = 0;
202+
memset(&ObjectAttributes, 0, 44);
203+
IoStatusBlock = 0;
204+
205+
[::]
206+
207+
Payload_in_Stack[0] = *v17;
208+
Payload_in_Stack[1] = v17[1];
209+
Payload_in_Stack[2] = v17[2];
210+
Payload_in_Stack[3] = v17[3];
211+
Payload_in_Stack[4] = v17[4];
212+
213+
[::]
214+
215+
while ( 1 )
216+
{
217+
v24 = *(__int16 *)((char *)&v54->m128i_i16[v23] + epi16);
218+
if ( v24 == '\\' || v24 == ':' )
219+
break;
220+
if ( ++v23 >= (unsigned __int16)(WORD1(v19) >> 1) )
221+
goto LABEL_52;
222+
}
223+
224+
[::]
225+
226+
[<------- RACE WINDOW -------->]
227+
228+
[::]
229+
230+
*((_QWORD *)&v68 + 1) = (char *)v54 + Payload_in_Stack[0].m128i_u16[4];
231+
LOWORD(v68) = Payload_in_Stack[0].m128i_i16[5];
232+
WORD1(v68) = Payload_in_Stack[0].m128i_i16[5];
233+
ObjectAttributes.Length = 48;
234+
ObjectAttributes.RootDirectory = v57;
235+
ObjectAttributes.Attributes = 576;
236+
ObjectAttributes.ObjectName = (PUNICODE_STRING)&v68; // Relative Name of the File in Memory Mapped Region
237+
*(_OWORD *)&ObjectAttributes.SecurityDescriptor = 0;
238+
CreateOptions = (v34 != 0) | 0x208020;
239+
LODWORD(v25) = FltCreateFileEx2(
240+
Filter,
241+
Instance,
242+
&FileHandle,
243+
&FileObject,
244+
0x100180u,
245+
&ObjectAttributes,
246+
&IoStatusBlock,
247+
&AllocationSize,
248+
FileAttributes,
249+
0,
250+
2u,
251+
CreateOptions,
252+
0,
253+
0,
254+
0x800u,
255+
&DriverContext);
256+
257+
258+
[::]
259+
260+
} [ WHILE LOOP CONTINUES TO NEXT PLACEHOLDER ]
261+
262+
[::]
263+
```
264+
265+
## Triggering the Vulnerability
266+
267+
To exploit this vulnerability,
268+
269+
**Step 1:** Register a directory as sync root directory, which can be done by using the `CfRegisterSyncRoot()` API.
270+
271+
**Step 2:** Create a new directory inside this sync root directory, for e.g, JUSTASTRING.
272+
273+
**Step 3:** Create a junction for this JUSTASTRING to `C:\Windows\System32\` directory (which is non-writable by normal user but junction can be created).
274+
275+
**Step 4:** Create 3 threads
276+
277+
- First thread will keep checking if `C:\Windows\System32\newfile.dll` is created.
278+
- Second thread will keep modifying the filename string which is `"JUSTASTRINGDnewfile.dll"` in the shared memory buffer, changing the character at position 11 from `'D'` to `'\'` and back repeatedly. This causes the filename to oscillate between `"JUSTASTRINGDnewfile.dll"` (safe) and `"JUSTASTRING\newfile.dll"` (exploits junction).
279+
- Third thread will keep making IOCTL call to 0x903BC, inorder to invoke the `HsmpOpCreatePlaceholders()` vulnerable function.
280+
281+
The function will create `JUSTASTRINGDnewfile.dll` file but once the validation for the `relName` field is success, i.e. check that the `relName` does not contain any `\` character, reaching the `FltCreateFileEx2()` function with `relName` set to `JUSTASTRING\newfile.dll`. Once this is set, the junction will create the file in `C:\Windows\System32\newfile.dll`.
282+
283+
**Step 5:** Once we have written a DLL in `C:\Windows\System32\`, then we can directly change the content of the DLL, so if a privileged service load this DLL we can escalate the privilege.
284+
285+
**References:**
286+
287+
- https://ssd-disclosure.com/cloud-filter-arbitrary-file-creation-eop-patch-bypass-lpe/
288+
- https://blog.exodusintel.com/2025/10/20/microsoft-windows-cloud-files-minifilter-toctou-privilege-escalation/

0 commit comments

Comments
 (0)