首页
社区
课程
招聘
[原创]CLFS信息泄露漏洞CVE-2023-28266分析
发表于: 2023-8-6 15:27 14332

[原创]CLFS信息泄露漏洞CVE-2023-28266分析

王cb 活跃值
11
2023-8-6 15:27
14332

这篇文章的目的是介绍今年4月发布的CLFS信息泄露漏洞CVE-2023-28266分析.

文章结合了逆向代码和调试结果分析了CVE-2023-28266漏洞利用过程和漏洞成因.

CVE-2023-28266是笔者今年1月提交的一个关于CLFS文件系统驱动程序的信息泄露漏洞,漏洞公告发布于今年4月.信息泄露类型的漏洞属于间接利用类型并不能直接导致权限提升的结果,以普通用户触发漏洞可以实现内核态内存的越界读取,最终将泄露的数据写入CLFS容器文件(BLF格式)中,并在控制台输出中提供展示泄露的数据内容.
关于CLFS模块的基础知识读者可以查看相关引用节中的文章获取里面有详细介绍,请读者自行研究,本文只讨论CVE-2023-28266的相关利用细节.
CVE-2023-28266与其他CLFS漏洞中的符号表相关的关联数据关系不大,只存在于一个截断日志相关的过程调用中,下面我们来具体分析一下.
漏洞存在于一个名为的CClfsLogFcbPhysical::TruncateLogModifyStreams函数中,触发该函数需要增加一个Truncate Record的元数据块(Metadata Block),大小0x2800在最后一个元数据块后.从字面量分析来看这个元数据块的作用应该是用来截断clfs文件指定扇区数据到容器文件的作用.从逆向分析来看我们得到了一个结构体_CLFS_SECTOR_CHANGE用于表示需要截断的扇区的数据结构,截断数据存在于rgbSector字段大小为一个默认扇区大小也就是0x200.具体逆向结果如下:

触发截断日志扇区功能函数需首先要设置控制元数据块(Control Record)PCLFS_CONTROL_RECORD->cxTruncate.eTruncateState = ClfsTruncateStateModifyingStream,在进入这个函数前存在一个_CLFS_TRUNCATE_CLIENT_CHANGE结构体用于表示需要截断扇区个数chg->cLength,CLFS会根据这个指定的个数分配非分页缓冲区大小chg->cLength<<9也就是chg->cLength*0x200用于存放将要截断的扇区数据,从当前元数据块加上coffClientChange偏移量得到拷贝的基址写入缓冲区中,这里并没有对实际的Truncate Record的元数块据中指定扇区数据数量进行校验,导致可以越界读取到最后一个扇区的下一个扇区数据到缓冲区中,也就是说泄露下个内核池中的0x200大小的数据.一般情况下非分页池相邻的数据块都是有数据的,所以越界读取并不会导致BSOD.所有缓冲区的数据最后都会在CClfsLogFcbPhysical::WriteOneRawSectorSync写入CLFS容器文件,所以读取泄露数据对于调用者来说是可行的.

漏洞触发的整个调用过程首先根据Client符号表的cidClient获取TruncateContext上下文,所在元数据块大小0x2800,其实在这个函数之后确实存在对当前元数据块的CClfsLogFcbPhysical::IsTruncatedRecordOffsetValid进行第一步校验,判断CLFS_TRUNCATE_CONTEXT->eTruncateState是不是ClfsTruncateStateModifyingStream,然后进入CClfsLogFcbPhysical::TruncateLogModifyStreams对TruncateContext进行第二步校验,在CClfsLogFcbPhysical::ValidateTruncateRecord函数中.这里笔者总结出了一个绕过方式方法如下:逆向代码显示IsTruncatedRecordOffsetValid的参数offset来自与元数据块的SignaturesOffset+RecordOffsets[0]=27d0值,可以理解为这个值接近于整个元数据库块的结尾,因为一般情况下Signature会从最后一个扇区直到写入位于每个扇区的结尾,第一步校验经过排除法计算得出chk1 = offOwnerPage < offClientChange;
和 chk2 = offOwnerPage == offClientChange这2个都不能让它成立,参数offset位于整个元数据块长度减去0x30字节处,为了让offset - offOwnerPage < 0x1000且offset - offClientChange < 0x230不成立,只能让offOwnerPage - offClientChange >= 0x230成立,得出结论offset减去offOwnerPage和offClientChange都只能大于0x1000,而且它们的差值需要大于0x230,那么offClientChange就必须小于offset-1000-230值,采用0x1750- 0x238=1518可以是个合适的值.在第二步校验中0x208 * chg_cLength > offOwnerPage所以offOwnerPage必须大于0x2089=1248采用0x1750也符合要求;为什么采用TruncateContext元数据块大小0x2800原因是offClientChange=1518,0x2800-1518-70=1278,转换成扇区个数1278/208=9正好是9个,通过加减8个字节的微调让这些符号的边界都和扇区对齐,让最后一个PCLFS_SECTOR_CHANGE扇区的边界(90x208=1248)正好落在整个TruncateContext元数据块的结尾,1248+70+1518+sizeof(CLFS_TRUNCATE_CLIENT_CHANGE)=27f8(约等于2800).而越界读取没有对PCLFS_TRUNCATE_CLIENT_CHANGE->cLength进行校验导致读取数据越过了元数据块的边界,这里SignaturesOffset需要被clfs写入签名值,所以必须让他落在拷贝扇区数据的内存区域要不然会污染符号表的数据,在ClfsDecodeBlockPrivate中对这个值和CLFS_LOG_BLOCK_HEADER->TotalSectorCount组合也有校验需要绕过,笔者用的合适的SignaturesOffset值是元数据块大小TruncateContextlength-0x30.完整的绕过方法构造合适的值如下.

下面我们来看调试过程

设置第一个断点bp CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x127 ".if(rax==9){.echo found}.else{gc}";
当迭代到拷贝第9个扇区数据时候,可以看到rsi指向当前TruncateContext指向元数据块内存区域,大小2800也就是拷贝的起始地址,再次下第二个断点bp CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x14e发现rax=ffffde86f13e4800减去TruncateContext基址正好是0x2800,说明这次拷贝的起始地址已经到了当前pool的末尾,也就是说会越界拷贝一个扇区0x200大小的数据内容.
下第三个断点bp CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x3d3,调用CLFS!CClfsLogFcbPhysical::WriteOneRawSectorSync函数r9指向要写入容器文件的全部缓冲区地址正好是9*0x200=0x1200;查看db ffff8b03d81ab000+1200数据内容得出的结论是与上一步越界读取rax指向的泄露信息内容是相同的.
接下来调用者就可以通过读取容器通过CLFS_TRUNCATE_CLIENT_CHANGE->lsn获取容器的符号表中对应容器默认第0个是CLFSCON01文件,在偏移量1200数据内容展示泄露信息,读取方法与读取普通文件相同,这里不再赘述.至此完成整个漏洞的利用过程.

出于安全原因笔者不能提供完整的poc代码,下图是笔者在打了1月补丁的Windows1021h2虚拟机上成功复现了CVE-2023-28266

查看大图

clfs逆向工程文档

CVE-2023-28252红雨滴分析

CVE-2023-28252安全客分析

CVE-2023-28252谷歌P0分析

CVE-2023-28252分析

CVE-2023-28252看雪分析2

CVE-2023-28252看雪分析2

CVE-2023-28252的poc

CVE-2022-24521分析第一篇

CVE-2022-24521分析第二篇

CVE-2022-24521分析第三篇

CVE-2022-24521分析第三篇原文

CVE-2022-3022分析

CVE-2022-24521分析谷p0

CVE-2022-37969分析第一部分

CVE-2022-37969分析第二部分

CVE-2022-37969分析中文第一篇

CVE-2023-28266漏洞致谢

作者来自ZheJiang Guoli Security Technology,邮箱cbwang505@hotmail.com

typedef struct __declspec(align(2)) _CLFS_SECTOR_CHANGE
{
    ULONG iSector;
    ULONG ulUnused;
    BYTE rgbSector[0x200];
}CLFS_SECTOR_CHANGE,*PCLFS_SECTOR_CHANGE;
typedef struct __declspec(align(8)) _CLFS_TRUNCATE_CLIENT_CHANGE
{
    CLFS_CLIENT_ID cidClient;
    CLFS_LSN lsn;
    CLFS_LSN lsnClient;
    CLFS_LSN lsnRestart;
    USHORT cLength;
    USHORT cOldLength;
    ULONG cSectors;
    CLFS_SECTOR_CHANGE rgSectors[0xb];
}CLFS_TRUNCATE_CLIENT_CHANGE, * PCLFS_TRUNCATE_CLIENT_CHANGE;
__int64 __fastcall CClfsLogFcbPhysical::TruncateLogModifyStreams(CClfsLogFcbPhysical *this, _CLFS_LOG_BLOCK_HEADER *loghdr, struct _CLFS_TRUNCATE_CONTEXT *trunctx, struct _CLFS_TRUNCATE_RECORD_HEADER *trunhdr)
{
//vulnerability code
struct _CLFS_LOG_BLOCK_HEADER * hdr = ExAllocatePoolWithTag(PagedPool, (unsigned __int64)chg->cLength << 9, 'sflC');
(_CLFS_TRUNCATE_RECORD_HEADER *) crdhdr =(&that->field_rgBlocks_30[4].pbImage->MajorVersion
                                                          + crawlast->RecordOffsets[0]);                                                      
CLFS_TRUNCATE_CLIENT_CHANGE * chg = (char *)crdhdr + crdhdr->coffClientChange_8);
while ( 1 )
  {
    nowlsn = idx;
    if ( idx >= chg->cLength )
      break;
    trunctx = (struct _CLFS_TRUNCATE_CONTEXT *)(0x208i64 * idx);
    if ( idx != *(ULONG *)((char *)&chg->rgSectors[0].iSector + (_QWORD)trunctx) )
      goto error;
    loghdr = &hdr[idx];
    rgbSector = (CLFS_SECTOR_CHANGE *)((char *)chg->rgSectors + (_QWORD)trunctx + 8);
    val4 = 4i64;
    do
    {
      *(_OWORD *)&loghdr->MajorVersion = *(_OWORD *)&rgbSector->iSector;
      *(_OWORD *)&loghdr->Flags = *(_OWORD *)&rgbSector->rgbSector[8];
      *(_OWORD *)&loghdr->NextLsn.offset.idxRecord = *(_OWORD *)&rgbSector->rgbSector[24];
      *(_OWORD *)&loghdr->RecordOffsets[2] = *(_OWORD *)&rgbSector->rgbSector[40];
      *(_OWORD *)&loghdr->RecordOffsets[6] = *(_OWORD *)&rgbSector->rgbSector[56];
      *(_OWORD *)&loghdr->RecordOffsets[10] = *(_OWORD *)&rgbSector->rgbSector[72];
      *(_OWORD *)&loghdr->RecordOffsets[14] = *(_OWORD *)&rgbSector->rgbSector[88];
      loghdr = (_CLFS_LOG_BLOCK_HEADER *)((char *)loghdr + 0x80);
      *(_OWORD *)&loghdr[-1].glag[384] = *(_OWORD *)&rgbSector->rgbSector[0x68];
      rgbSector = (_CLFS_SECTOR_CHANGE *)((char *)rgbSector + 0x80);
      --val4;
    }
    while ( val4 );
    ++idx;
  }
 //泄露的数据最终会通过CClfsLogFcbPhysical::WriteOneRawSectorSync写入容器文件
 Hresult  hr = CClfsLogFcbPhysical::WriteOneRawSectorSync(that, &v44, &v42, &hdr[lsnidx].MajorVersion, &chg->lsn, 1, v5);
}
typedef struct __declspec(align(2)) _CLFS_SECTOR_CHANGE
{
    ULONG iSector;
    ULONG ulUnused;
    BYTE rgbSector[0x200];
}CLFS_SECTOR_CHANGE,*PCLFS_SECTOR_CHANGE;
typedef struct __declspec(align(8)) _CLFS_TRUNCATE_CLIENT_CHANGE
{
    CLFS_CLIENT_ID cidClient;
    CLFS_LSN lsn;
    CLFS_LSN lsnClient;
    CLFS_LSN lsnRestart;
    USHORT cLength;
    USHORT cOldLength;
    ULONG cSectors;
    CLFS_SECTOR_CHANGE rgSectors[0xb];
}CLFS_TRUNCATE_CLIENT_CHANGE, * PCLFS_TRUNCATE_CLIENT_CHANGE;
__int64 __fastcall CClfsLogFcbPhysical::TruncateLogModifyStreams(CClfsLogFcbPhysical *this, _CLFS_LOG_BLOCK_HEADER *loghdr, struct _CLFS_TRUNCATE_CONTEXT *trunctx, struct _CLFS_TRUNCATE_RECORD_HEADER *trunhdr)
{
//vulnerability code
struct _CLFS_LOG_BLOCK_HEADER * hdr = ExAllocatePoolWithTag(PagedPool, (unsigned __int64)chg->cLength << 9, 'sflC');
(_CLFS_TRUNCATE_RECORD_HEADER *) crdhdr =(&that->field_rgBlocks_30[4].pbImage->MajorVersion
                                                          + crawlast->RecordOffsets[0]);                                                      
CLFS_TRUNCATE_CLIENT_CHANGE * chg = (char *)crdhdr + crdhdr->coffClientChange_8);
while ( 1 )
  {
    nowlsn = idx;
    if ( idx >= chg->cLength )
      break;
    trunctx = (struct _CLFS_TRUNCATE_CONTEXT *)(0x208i64 * idx);
    if ( idx != *(ULONG *)((char *)&chg->rgSectors[0].iSector + (_QWORD)trunctx) )
      goto error;
    loghdr = &hdr[idx];
    rgbSector = (CLFS_SECTOR_CHANGE *)((char *)chg->rgSectors + (_QWORD)trunctx + 8);
    val4 = 4i64;
    do
    {
      *(_OWORD *)&loghdr->MajorVersion = *(_OWORD *)&rgbSector->iSector;
      *(_OWORD *)&loghdr->Flags = *(_OWORD *)&rgbSector->rgbSector[8];
      *(_OWORD *)&loghdr->NextLsn.offset.idxRecord = *(_OWORD *)&rgbSector->rgbSector[24];
      *(_OWORD *)&loghdr->RecordOffsets[2] = *(_OWORD *)&rgbSector->rgbSector[40];
      *(_OWORD *)&loghdr->RecordOffsets[6] = *(_OWORD *)&rgbSector->rgbSector[56];
      *(_OWORD *)&loghdr->RecordOffsets[10] = *(_OWORD *)&rgbSector->rgbSector[72];
      *(_OWORD *)&loghdr->RecordOffsets[14] = *(_OWORD *)&rgbSector->rgbSector[88];
      loghdr = (_CLFS_LOG_BLOCK_HEADER *)((char *)loghdr + 0x80);
      *(_OWORD *)&loghdr[-1].glag[384] = *(_OWORD *)&rgbSector->rgbSector[0x68];
      rgbSector = (_CLFS_SECTOR_CHANGE *)((char *)rgbSector + 0x80);
      --val4;
    }
    while ( val4 );
    ++idx;
  }
 //泄露的数据最终会通过CClfsLogFcbPhysical::WriteOneRawSectorSync写入容器文件
 Hresult  hr = CClfsLogFcbPhysical::WriteOneRawSectorSync(that, &v44, &v42, &hdr[lsnidx].MajorVersion, &chg->lsn, 1, v5);
}
typedef struct _CLFS_TRUNCATE_RECORD_HEADER
{
    CLFS_METADATA_RECORD_HEADER hdrBaseRecord;
    ULONG coffClientChange;
    ULONG coffOwnerPage;
} CLFS_TRUNCATE_RECORD_HEADER, * PCLFS_TRUNCATE_RECORD_HEADER;
__int64 __fastcall CClfsBaseFilePersisted::AcquireTruncateContext(CClfsBaseFilePersisted *this, unsigned int *val1, struct _CLFS_TRUNCATE_CONTEXT **trunctxret, _CLFS_TRUNCATE_RECORD_HEADER **offset, unsigned int *deleta)
{
     hr = CClfsBaseFile::AcquireMetadataBlock(this, 4i64, (__int64)v14, v15);
     hr = RtlULongSub(
             this->field_rgBlocks_30[4].pbImage->SignaturesOffset,
             this->field_rgBlocks_30[4].pbImage->RecordOffsets[0],
             deleta);        
      *offset = (_CLFS_TRUNCATE_RECORD_HEADER *)(&this->field_rgBlocks_30[4].pbImage->MajorVersion
                                               + crawlast->RecordOffsets[0]);
      ctrlrcdref = ctrlrcd;
      //判断eTruncateState是不是ClfsTruncateStateModifyingStream,然后进入CClfsLogFcbPhysical::TruncateLogModifyStreams
      *trunctxret = &ctrlrcd->cxTruncate;
}
//offset=CClfsBaseFilePersisted::AcquireTruncateContext参数deleta
unsigned __int8 __fastcall CClfsLogFcbPhysical::IsTruncatedRecordOffsetValid(CClfsLogFcbPhysical *this, _CLFS_TRUNCATE_RECORD_HEADER *trunhdr, unsigned int offset)
{
  unsigned int offOwnerPage; // ecx
  unsigned int offClientChange; // edx
  bool chk1; // cf
  bool chk2; // zf
 
  offOwnerPage = trunhdr->coffOwnerPage_c;
  if ( offOwnerPage < 0x10 )
    return 0;
  offClientChange = trunhdr->coffClientChange_8;
  if ( offClientChange < 0x10
    || offOwnerPage > offset
    || offClientChange > offset
    || offClientChange == offOwnerPage
    || offset - offOwnerPage < 0x1000
    || offset - offClientChange < 0x230 )
  {
    return 0;
  }
  chk1 = offOwnerPage < offClientChange;
  chk2 = offOwnerPage == offClientChange;
  if ( offOwnerPage < offClientChange )
  {
    if ( offClientChange - offOwnerPage < 0x1000 )
      return 0;
    chk1 = offOwnerPage < offClientChange;
    chk2 = offOwnerPage == offClientChange;
  }
  if ( chk1 || chk2 || offOwnerPage - offClientChange >= 0x230 )
    return 1;
  return 0;
}   
__int64 __fastcall CClfsLogFcbPhysical::ValidateTruncateRecord(CClfsLogFcbPhysical *this, struct _CLFS_TRUNCATE_RECORD_HEADER *hdr)
{
  __int64 offClientChange; // rax
  unsigned __int64 offOwnerPage; // r9
  CLFS_TRUNCATE_CLIENT_CHANGE *chg; // r8
  __int64 chg_cLength; // rax
  __int64 result; // rax
 
  offClientChange = hdr->coffClientChange_8;
  if ( offClientChange & 7
    || (offOwnerPage = hdr->coffOwnerPage_c, offOwnerPage & 7)
    || (chg = (CLFS_TRUNCATE_CLIENT_CHANGE *)((char *)hdr + offClientChange),
        chg_cLength = *(unsigned __int16 *)((char *)&hdr[2]+ offClientChange).cLength,
        chg->cSectors != (_DWORD)chg_cLength)
    || chg->cOldLength < (unsigned __int16)chg_cLength
    || chg->rgSectors[0].iSector
    || 0x208 * chg_cLength > offOwnerPage
    || chg->cidClient >= 0x7Cu )
  {
    result = 3222929421i64;
  }
  else
  {
    result = 0i64;
  }
  return result;
}
__int64 __fastcall ClfsDecodeBlockPrivate(_CLFS_LOG_BLOCK_HEADER *a1, unsigned int SectorCount, char sig, unsigned __int8 a4, unsigned int *a5)
{
    if ( (int)ULongAdd(SignaturesOffset, 2 * SectorCount, &plus) < 0 || (v11 & 7) != 0 || plus > blocksize )
    return 0xC01A000Ai64;
}
typedef struct _CLFS_TRUNCATE_RECORD_HEADER
{
    CLFS_METADATA_RECORD_HEADER hdrBaseRecord;
    ULONG coffClientChange;
    ULONG coffOwnerPage;
} CLFS_TRUNCATE_RECORD_HEADER, * PCLFS_TRUNCATE_RECORD_HEADER;
__int64 __fastcall CClfsBaseFilePersisted::AcquireTruncateContext(CClfsBaseFilePersisted *this, unsigned int *val1, struct _CLFS_TRUNCATE_CONTEXT **trunctxret, _CLFS_TRUNCATE_RECORD_HEADER **offset, unsigned int *deleta)
{
     hr = CClfsBaseFile::AcquireMetadataBlock(this, 4i64, (__int64)v14, v15);
     hr = RtlULongSub(
             this->field_rgBlocks_30[4].pbImage->SignaturesOffset,
             this->field_rgBlocks_30[4].pbImage->RecordOffsets[0],
             deleta);        
      *offset = (_CLFS_TRUNCATE_RECORD_HEADER *)(&this->field_rgBlocks_30[4].pbImage->MajorVersion
                                               + crawlast->RecordOffsets[0]);
      ctrlrcdref = ctrlrcd;
      //判断eTruncateState是不是ClfsTruncateStateModifyingStream,然后进入CClfsLogFcbPhysical::TruncateLogModifyStreams
      *trunctxret = &ctrlrcd->cxTruncate;
}
//offset=CClfsBaseFilePersisted::AcquireTruncateContext参数deleta
unsigned __int8 __fastcall CClfsLogFcbPhysical::IsTruncatedRecordOffsetValid(CClfsLogFcbPhysical *this, _CLFS_TRUNCATE_RECORD_HEADER *trunhdr, unsigned int offset)
{
  unsigned int offOwnerPage; // ecx
  unsigned int offClientChange; // edx
  bool chk1; // cf
  bool chk2; // zf
 
  offOwnerPage = trunhdr->coffOwnerPage_c;
  if ( offOwnerPage < 0x10 )
    return 0;
  offClientChange = trunhdr->coffClientChange_8;
  if ( offClientChange < 0x10
    || offOwnerPage > offset
    || offClientChange > offset
    || offClientChange == offOwnerPage
    || offset - offOwnerPage < 0x1000
    || offset - offClientChange < 0x230 )
  {
    return 0;
  }
  chk1 = offOwnerPage < offClientChange;
  chk2 = offOwnerPage == offClientChange;
  if ( offOwnerPage < offClientChange )
  {
    if ( offClientChange - offOwnerPage < 0x1000 )
      return 0;
    chk1 = offOwnerPage < offClientChange;
    chk2 = offOwnerPage == offClientChange;
  }
  if ( chk1 || chk2 || offOwnerPage - offClientChange >= 0x230 )
    return 1;
  return 0;
}   
__int64 __fastcall CClfsLogFcbPhysical::ValidateTruncateRecord(CClfsLogFcbPhysical *this, struct _CLFS_TRUNCATE_RECORD_HEADER *hdr)
{
  __int64 offClientChange; // rax
  unsigned __int64 offOwnerPage; // r9
  CLFS_TRUNCATE_CLIENT_CHANGE *chg; // r8
  __int64 chg_cLength; // rax
  __int64 result; // rax
 
  offClientChange = hdr->coffClientChange_8;
  if ( offClientChange & 7
    || (offOwnerPage = hdr->coffOwnerPage_c, offOwnerPage & 7)
    || (chg = (CLFS_TRUNCATE_CLIENT_CHANGE *)((char *)hdr + offClientChange),
        chg_cLength = *(unsigned __int16 *)((char *)&hdr[2]+ offClientChange).cLength,
        chg->cSectors != (_DWORD)chg_cLength)
    || chg->cOldLength < (unsigned __int16)chg_cLength
    || chg->rgSectors[0].iSector
    || 0x208 * chg_cLength > offOwnerPage
    || chg->cidClient >= 0x7Cu )

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 2
支持
分享
最新回复 (4)
雪    币: 2575
活跃值: (502)
能力值: ( LV2,RANK:85 )
在线值:
发帖
回帖
粉丝
2
能成功复现还是不太容易,学习
2023-8-6 17:12
0
雪    币: 3059
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-8-6 20:57
1
雪    币: 220
活跃值: (721)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
现在已经看不见兼容性好的EXP发布了,,
2023-8-6 22:24
0
游客
登录 | 注册 方可回帖
返回
//