原文链接:https://mp.weixin.qq.com/s/mkLjAyalo55PmdLbzMnX9g
1. 漏洞概述
项目 |
详情 |
名称 |
Windows Common Log File System Driver 提权漏洞 |
简介 |
CLFS 驱动中存在堆越界写漏洞,利用该漏洞可以实现本地提权 |
影响版本 |
该漏洞影响所有受支持的 Windows 操作系统 |
编号 |
CVE-2022-30220 |
2. CLFS基础
注:以下所有涉及的数据结构根据参考资料整理得到,由于没有官方文档,所以可能存在未知或同名字段,但不影响此次漏洞分析。
2.1 概念
CLFS(Common Log File System) 是一个日志框架,其他应用程序可以通过 CLFS 创建、保存和读取日志数据。“日志(log)”既可以表示一个抽象的概念,也可以表示一个物理上的文件,CLFS 对一条日志以及保存它的物理存储空间做了区分,因此物理空间的管理和日志的管理是分开的。CLFS 通过使用流(stream)和容器(container)在 NTFS 之上构建了一个虚拟的抽象层。
2.2 日志的存储
一个 CLFS 日志的存储由两部分组成:
包含元数据(metadata)的 base log file(BLF)
BLF文件大小通常为 64KB,但是可以根据需要增长,其中包含了日志存储所需的一些元信息,例如日志的起始位置、容器的大小和路径、日志名称、客户端信息等。考虑到日志恢复的需要,BLF 文件中包含了一份元数据的拷贝,使用 dump count 参数来识别哪份信息是最新的。
最多 1023 个包含真正数据的容器文件
容器是一个活动的日志流在空间上的基础分配单元,同一日志中的容器大小一致,是 512KB(一个扇区的大小)的倍数,最大为 4GB。CLFS 客户端通过增加和删除容器实现日志流的扩大和缩小。在实现时,CLFS 把容器当作 BLF 所在卷上的一个连续文件,通过在逻辑上将多个容器串在一起,形成包含一条日志的单个逻辑顺序磁盘区。初始化的时候一条日志至少要分配两个容器。
可以把整个日志存储看作是文件系统中一个卷的概念
2.2.1 Log Blocks
CLFS 使用日志块对记录进行组织管理,每个日志块由多个 512 字节的扇区组成,之后对日志数据的读取和写入操作都在日志块上进行。
每个日志块的开头有一个 _CLFS_LOG_BLOCK_HEADER
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
typedef struct _CLFS_LOG_BLOCK_HEADER
{
UCHAR MajorVersion;
UCHAR MinorVersion;
UCHAR Usn;
UCHAR StreamNum;
USHORT TotalSectorCount;
USHORT ValidSectorCount;
ULONG Padding;
ULONG Checksum;
ULONG Flags;
ULONG Unknown2;
CLFS_LSN CurrentLsn;
CLFS_LSN NextLsn;
ULONG RecordOffsets[
16
];
ULONG SignaturesOffset;
ULONG Unknown3;
} CLFS_LOG_BLOCK_HEADER;
|
当日志写入硬盘的时候,日志块会进行编码,编码状态的日志块中,每个扇区结尾处有一个 2 字节的扇区签名(sector signature):
1
|
[Sector Block
Type
][Usn]
|
由于在编码阶段每个扇区的最后两个字节被改写成了扇区签名,因此需要对原始数据进行备份。日志块的最后一个扇区结尾处,有一个签名数组,这个数组中就保存了原始数据,由 _CLFS_LOG_BLOCK_HEADER
中的 SignaturesOffset
表示。
在写入日志的时候,可以比较每个扇区结尾签名位置的数据是否和签名数组中的数据一致来判断写入是否成功。
2.2.2 BLF文件
BLF 由六种不同的元数据块组成,每种元数据块只包含对应类型的数据,其中 shadow block 中保存的是对应元数据块的备份,同样使用 dump count 参数说明数据块的新旧。
Control Block:包含了有关布局(layout)、扩展(extend)区域以及截断(truncate)区域的信息
Base Block:包含了符号表信息,其中包括该BLF有关的客户端、容器和安全上下文信息
Truncate Block:包含了因为截断操作而需要对扇区进行更改的客户端信息,以及具体更改的扇区字节
Control Block Shadow
Base Block Shadow
Truncate Block Shadow
这里主要关注其中的 Control Block,这个日志块中的记录遵循以下数据结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
typedef struct _CLFS_CONTROL_RECORD
{
CLFS_METADATA_RECORD_HEADER hdrControlRecord;
ULONGLONG ullMagicValue;
ULONG Version;
CLFS_EXTEND_STATE eExtendState;
USHORT iExtendBlock;
USHORT iFlushBlock;
ULONG cNewBlockSectors;
ULONG cExtendStartSectors;
ULONG cExtendSectors;
CLFS_TRUNCATE_CONTEXT cxTruncate;
ULONG cBlocks;
ULONG cReserved;
CLFS_METADATA_BLOCK rgBlocks[
6
];
} CLFS_CONTROL_RECORD;
|
其中的 cBlocks
表示整个文件中包含的日志块数量:
rgBlocks
中保存了每个日志块的大小信息:
1
2
3
4
5
6
7
8
|
typedef struct _CLFS_METADATA_BLOCK
{
ULONGLONG pbImage;
/
/
指向内存中数据的指针,在BLF文件中为
0
ULONG cbImage;
/
/
日志块大小
ULONG cbOffset;
/
/
偏移
CLFS_METADATA_BLOCK_TYPE eBlockType;
ULONG Padding;
} CLFS_METADATA_BLOCK;
|
3. 补丁对比
对比 clfy.sys 文件发现只有 CClfsBaseFilePersisted::ReadImage
函数存在不同,仔细检查代码,发现修改主要位于 cBlocks > 6
的情况:
修复之后,如果 BLF 文件中 cBlocks
数值大于 6,ReadImage
函数会直接出错返回。
4. 漏洞调试分析
4.1 漏洞原理
根据补丁分析结果,我们生成一个 BLF 文件,并手动将 cBlocks
修改成一个较大值 0x19(后期调试发现大于等于 0x20 无法通过检查),加载 BLF 文件后,系统崩溃(需要多次测试)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
kd> g
KDTARGET: Refreshing KD connection
KDTARGET: Refreshing KD connection
*
*
*
Fatal System Error:
0x00000050
(
0xFFFF800C3905118E
,
0x0000000000000000
,
0xFFFFF8006B2A8140
,
0x0000000000000000
)
Driver at fault:
*
*
*
CLFS.SYS
-
Address FFFFF8006B2A8140 base at FFFFF8006B2A0000, DateStamp
0c6e6b39
.
Break instruction exception
-
code
80000003
(first chance)
A fatal system error has occurred.
Debugger entered on first
try
; Bugcheck callbacks have
not
been invoked.
A fatal system error has occurred.
nt!DbgBreakPointWithStatus:
fffff800
*
6b804c70
cc
int
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
|
3
: kd> kb
00
fffff800
*
6b918032
: ffffa780
*
aec28ad0 fffff800
*
6b782480
fffff800
*
6b2a0000
00000000
*
00000000
: nt!DbgBreakPointWithStatus
01
fffff800
*
6b917616
: fffff800
*
00000003
ffffa780
*
aec28ad0 fffff800
*
6b811d10
ffffa780
*
aec29020 : nt!KiBugCheckDebugBreak
+
0x12
02
fffff800
*
6b7fced7
:
00000000
*
00000000
00000000
*
00000000
0000f288
*
00000000
ffff800c
*
3905118e
: nt!KeBugCheck2
+
0x946
03
fffff800
*
6b85352f
:
00000000
*
00000050
ffff800c
*
3905118e
00000000
*
00000000
ffffa780
*
aec293c0 : nt!KeBugCheckEx
+
0x107
04
fffff800
*
6b6ab960
: ffffa780
*
aec29300
00000000
*
00000000
ffffa780
*
aec29440
00000000
*
00000000
: nt!MiSystemFault
+
0x189b7f
05
fffff800
*
6b80af5e
:
00000000
*
00000000
00000000
*
00000000
00000000
*
00000000
00000000
*
00000001
: nt!MmAccessFault
+
0x400
06
fffff800
*
6b2a8140
:
00000000
*
00000000
00000000
*
00000000
00000000
*
00000000
ffffb90d
*
58a5b0f0
: nt!KiPageFault
+
0x35e
07
fffff800
*
6b2a802d
:
00000000
*
00004210
ffff800c
*
39050190
00000000
*
00000002
00000000
*
00848400
: CLFS!ClfsEncodeBlockPrivate
+
0xe0
08
fffff800
*
6b2eedff
:
00000000
*
00000000
00000000
*
00000200
00000000
*
00000200
ffff800c
*
373e5dd0
: CLFS!ClfsEncodeBlock
+
0x1d
09
fffff800
*
6b2fa139
: ffff800c
*
38ba02a0
ffffb90d
*
00ffffff
00000000
*
00000200
ffffa881
*
ebebcfb0 : CLFS!CClfsBaseFileSnapshot::CopyImage
+
0xc3
0a
fffff800
*
6b2f39a2
: ffffb90d
*
538672b0
00000000
*
00000200
ffffb90d
*
538672b0
ffffb90d
*
53867201
: CLFS!CClfsLogCcb::ReadArchiveMetadata
+
0x85
0b
fffff800
*
6b2d0e82
: ffffb90d
*
53df89e0
00000000
*
00000000
ffffb90d
*
53dfae50
ffffb90d
*
53dfad80
: CLFS!CClfsRequest::ReadArchiveMetadata
+
0xfe
0c
fffff800
*
6b2d0987
: ffffb90d
*
53df89e0
fffff800
*
6b616131
ffffb90d
*
53552330
00000058
*
a7c00fb0 : CLFS!CClfsRequest::Dispatch
+
0x35e
0d
fffff800
*
6b2d08d7
: ffffb90d
*
53dfad80
ffffb90d
*
53dfad80
00000000
*
00000000
ffffdd00
*
2c53dff8
: CLFS!ClfsDispatchIoRequest
+
0x87
0e
fffff800
*
6b653565
: ffffb90d
*
53dfad80
ffffb90d
*
5652ca10
00000000
*
00000021
ffffb90d
*
5253d080
: CLFS!CClfsDriver::LogIoDispatch
+
0x27
0f
fffff800
*
6ba12b18
: ffffb90d
*
53dfad80
00000000
*
00000000
00000000
*
00000000
00000000
*
0000d55b
: nt!IofCallDriver
+
0x55
10
fffff800
*
6ba13af7
:
00000000
*
00000002
00000000
*
00000000
00000000
*
00000000
ffffa780
*
aec29b80 : nt!IopSynchronousServiceTail
+
0x1a8
11
fffff800
*
6ba12e76
:
00007ffa
*
7b746308
00000000
*
00000000
00000000
*
00000000
00000000
*
00000000
: nt!IopXxxControlFile
+
0xc67
12
fffff800
*
6b80e7b5
:
00000000
*
00000000
00000000
*
00000000
00000000
*
00000000
00000000
*
00000000
: nt!NtDeviceIoControlFile
+
0x56
13
00007ffa
*
800ece24
:
00007ffa
*
7da4b0bb
00000058
*
a7bfed64
00000000
*
00000000
00000000
*
00000078
: nt!KiSystemServiceCopyEnd
+
0x25
14
00007ffa
*
7da4b0bb
:
00000058
*
a7bfed64
00000000
*
00000000
00000000
*
00000078
00000000
*
00000034
: ntdll!NtDeviceIoControlFile
+
0x14
15
00000058
*
a7bfed64 :
00000000
*
00000000
00000000
*
00000078
00000000
*
00000034
00000058
*
a7bfeda0 :
0x00007ffa
*
7da4b0bb
16
00000000
*
00000000
:
00000000
*
00000078
00000000
*
00000034
00000058
*
a7bfeda0
00000000
*
80076856
:
0x00000058
*
a7bfed64
|
可以看到异常发生在 CLFS!ClfsEncodeBlockPrivate
函数中,在 IDA 中查看该函数:
为验证该猜测,我们跟随函数调用栈到达函数 CClfsBaseFileSnapshot::CopyImage
,从代码看这里在对堆块进行循环编码:
1
2
3
4
5
6
7
|
while
( idx <
*
(this
+
20
) &&
*
a5 < size )
/
/
*
(this
+
20
)保存了cBlocks
{
blockHeader
=
*
(
*
(this
+
6
)
+
24
*
idx);
ClfsEncodeBlock(blockHeader, blockHeader
-
>TotalSectorCount <<
9
, blockHeader
-
>Usn,
0x10u
,
1u
);
...
idx
=
idx
+
1
;
}
|
在这个函数设置断点并重新开始调试,当代码执行到循环判断时:
1
2
3
4
5
6
7
8
9
10
11
|
1
: kd> r
rax
=
0000000000000001
rbx
=
0000000000000000
rcx
=
ffff92816d5e8180
rdx
=
0000000000000000
rsi
=
0000000000000200
rdi
=
0000000000000000
rip
=
fffff8047537edb7 rsp
=
ffff8381808b35c0 rbp
=
0000000000000002
r8
=
0000000000000000
r9
=
ffff92816f707500 r10
=
0000000000000000
r11
=
ffff8381808b3668 r12
=
0000000000000000
r13
=
ffffe60fcaf944a0
r14
=
0000000000ffffff
r15
=
ffff8381808b3738
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!CClfsBaseFileSnapshot::CopyImage
+
0x7b
:
fffff804
*
7537edb7
410fb74528
movzx eax,word ptr [r13
+
28h
] ds:
002b
:ffffe60f
*
caf944c8
=
0019
|
这里在获取保存的 cBlocks
数值,可以看到系统在通过 ReadImage
读取 BLF 文件中存储的 cBlocks
数值之后,该数值并没有发生过更改,并直接在 CopyImage
中被使用。
4.2 越界写的位置与内容
现在已知漏洞是由于没有对 cBlocks
进行验证,但对于发生堆越界写时,写入的位置和内容还不清楚。
如果继续向下调试,到达获取 blockHeader
地址的位置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
2
: kd> p
CLFS!CClfsBaseFileSnapshot::CopyImage
+
0x9f
:
fffff804
*
7537eddb
488b3cc8
mov rdi,qword ptr [rax
+
rcx
*
8
]
2
: kd> r
rax
=
ffffe60fc7415030 rbx
=
0000000000000000
rcx
=
0000000000000000
rdx
=
0000000000000000
rsi
=
0000000000000200
rdi
=
0000000000000000
rip
=
fffff8047537eddb rsp
=
ffff8381808b35c0 rbp
=
0000000000000002
r8
=
0000000000000000
r9
=
ffff92816f707500 r10
=
0000000000000000
r11
=
ffff8381808b3668 r12
=
0000000000000000
r13
=
ffffe60fcaf944a0
r14
=
0000000000ffffff
r15
=
ffff8381808b3738
iopl
=
0
nv up ei ng nz na po cy
cs
=
0010
ss
=
0018
ds
=
002b
es
=
002b
fs
=
0053
gs
=
002b
efl
=
00040287
CLFS!CClfsBaseFileSnapshot::CopyImage
+
0x9f
:
fffff804
*
7537eddb
488b3cc8
mov rdi,qword ptr [rax
+
rcx
*
8
] ds:
002b
:ffffe60f
*
c7415030
=
ffffe60fc86bbbb0
2
: kd> dq ffffe60fc7415030
ffffe60f
*
c7415030 ffffe60f
*
c86bbbb0
00000000
*
00000400
ffffe60f
*
c7415040
00000000
*
00000000
ffffe60f
*
c86bbbb0
ffffe60f
*
c7415050
00000400
*
00000400
00000000
*
00000001
ffffe60f
*
c7415060 ffffe60f
*
cc319000
00000800
*
00007a00
ffffe60f
*
c7415070
00000000
*
00000002
ffffe60f
*
cc319000
ffffe60f
*
c7415080
00008200
*
00007a00
00000000
*
00000003
ffffe60f
*
c7415090 ffffe60f
*
c8c43a10
0000fc00
*
00000200
ffffe60f
*
c74150a0
00000000
*
00000004
ffffe60f
*
c8c43a10
|
可以看到地址 0xffffe60fc7415030
的位置存储了一个列表,根据数据内容判断,保存的就是 rgBlocks
信息。
在 CClfsBaseFilePersisted::ReadImage
函数中,如果 cBlocks
大于6,修复之前的函数会继续向下执行:
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
|
if
( cBlocks >
6u
)
{
pool1
=
ExAllocatePoolWithTag(
512
,
24
*
cBlocks,
'sflC'
);
/
/
分配大小为
24
*
cBlocks的空间
pool2
=
ExAllocatePoolWithTag(
512
,
2
*
cBlocks,
'sflC'
);
/
/
分配大小为
2
*
cBlocks的空间
if
( pool1 )
{
if
( pool2 )
{
memmove(pool1,
*
(this
+
6
),
24i64
*
*
(this
+
20
));
memmove(pool2_,
*
(this
+
7
),
2i64
*
*
(this
+
20
));
ExFreePoolWithTag(
*
(this
+
6
),
0
);
ExFreePoolWithTag(
*
(this
+
7
),
0
);
*
(this
+
20
)
=
cBlocks;
/
/
注意保存的位置
*
(this
+
6
)
=
pool1;
*
(this
+
7
)
=
pool2;
}
}
}
for
( i
=
0
; i < cBlocks;
+
+
i )
{
controlRecord
=
*
pControlRecord;
pool1
=
*
((_QWORD
*
)this
+
6
);
*
(_OWORD
*
)(pool1_
+
24
*
i)
=
*
(_OWORD
*
)((char
*
)
*
pControlRecord
+
24
*
i
+
0x50
);
/
/
从BLF文件中复制数据,rgBlocks
*
(_QWORD
*
)(pool1_
+
24
*
i
+
16
)
=
*
((_QWORD
*
)controlRecord_
+
3
*
i
+
0xC
);
*
(_QWORD
*
)(
*
((_QWORD
*
)this
+
6
)
+
8
*
v15)
=
0i64
;
}
*
*
(this
+
6
)
=
v13;
/
/
第一个堆块的pbImage字段赋值
*
(
*
(this
+
6
)
+
24i64
)
=
v13;
/
/
第二个堆块的pbImage字段赋值
|
代码根据新的 cBlocks
数值分配了两个空间,大小为 24 x cBlocks
和 2 x cBlocks
,并分别放在了*(this+6)
和*(this+7)
的位置,除此之外还将 cBlocks
保存到了*(this+20)
。之后将 BLF 文件中的 rgBlocks
列表复制到 pool1 所在空间,此时 pbImage
字段为 0。
但是 pool1 的地址并不是崩溃发生时 rgBlocks
列表所在的地址 0xffffe60fc7415030
,因此在 ReadImage
中,this 指针指向的是一个 CClfsBaseFilePersisted
结构,而崩溃发生时,this 指针指向的是一个 CClfsBaseFileSnapshot
结构,*(this+6)
中存储的空间地址并不相同。
根据结构名,找到函数 CClfsBaseFileSnapshot::InitializeSnapshot
,这里的 this 指针指向的同样是 CClfsBaseFileSnapshot
结构,同时发现了相同结构的一段代码:
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
|
if
( cBlocks )
{
*
(this
+
6
)
=
ExAllocatePoolWithTag(PagedPool,
24i64
*
cBlocks,
'SflC'
);
pool2
=
ExAllocatePoolWithTag(PagedPool,
2i64
*
cBlocks,
'sflC'
);
*
(this
+
7
)
=
pool2;
pool1
=
*
(this
+
6
);
if
( pool1 && pool2 )
{
memmove(pool1,
*
a3,
24i64
*
cBlocks);
memset(pool2,
0
,
2i64
*
cBlocks);
for
( i
=
0
; i < cBlocks;
+
+
i )
/
/
清空所有pbImage
*
(
*
(this
+
6
)
+
24i64
*
i)
=
0i64
;
for
( j
=
0
; j < cBlocks; j
+
=
2
)
{
*
(
*
(this
+
6
)
+
24i64
*
j)
=
ExAllocatePoolWithTag(PagedPool,
*
(
*
(this
+
6
)
+
24i64
*
j
+
8
),
'sflC'
);
/
/
根据cbImage字段分配空间,并赋值给pbImage
pool1_
=
*
(
*
(this
+
6
)
+
24i64
*
j);
if
...
memmove(pool1_,
*
(
*
a3
+
24i64
*
j),
*
(
*
a3
+
24i64
*
j
+
8
));
*
(
*
(this
+
7
)
+
2i64
*
j)
=
1
;
if
( cBlocks >
1u
)
{
v18
=
j
+
1
;
if
( v18 < cBlocks )
*
(
*
(this
+
6
)
+
24
*
v18)
=
*
(
*
(this
+
6
)
+
24i64
*
j);
/
/
shadow block
}
}
....
}
....
}
|
这里根据对应日志块的 cbImage
循环分配了 cBlocks
个空间,由于 cBlocks
比实际值大,且 BLF 文件除 cBlocks
值之外没有做任何修改,因此系统认为后续 19 个日志块的大小为 0,ExAllocatePoolWithTag
分配了大小为 0 的空间,memmove
复制了大小为 0 的数据,最终保存到 pbImage
字段的地址是堆中一个未初始化空间的地址。
因此在系统执行到 ClfsEncodeBlockPrivate
时,blockHeader 指向的实际上是一块未初始化且大小为 0 的空间。
通过调试获取到分配的13 个日志块地址(剩余的 12 个日志块地址重复):
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ffffd30c417903c0
/
/
control block
ffffd30c43f83000
/
/
base block
ffffd30c3fc134b0
/
/
truncate block
ffffd30c42cdebd0
ffffd30c42cdec30
ffffd30c42cdecf0
ffffd30c42cdfbd0
ffffd30c42cdfbf0
ffffd30c42cdf950
ffffd30c42cdf9d0
ffffd30c42cdf9f0
ffffd30c42cdfa10
ffffd30c42cdfa30
|
其中前三个是正常的日志块,我们主要看后 10 个日志块的地址,使用 !pool
命令查看堆块情况,截取部分输出:
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
|
2
: kd> !pool ffffd30c42cdfbd0
1
Pool page ffffd30c42cdfbd0 region
is
Paged pool
...
ffffd30c42cdf940 size:
20
previous size:
0
(Allocated) Clfs
ffffd30c42cdf950
00000000
00000000
26e5c558
00007ff8
...
ffffd30c42cdf9c0 size:
20
previous size:
0
(Allocated) Clfs
ffffd30c42cdf9d0
00000101
10000000
00002000
00007ff8
ffffd30c42cdf9e0 size:
20
previous size:
0
(Allocated) Clfs
ffffd30c42cdf9f0
0000001d
00000000
80000000
00000000
ffffd30c42cdfa00 size:
20
previous size:
0
(Allocated) Clfs
ffffd30c42cdfa10
00000101
10000000
00002000
00007ff8
ffffd30c42cdfa20 size:
20
previous size:
0
(Allocated) Clfs
ffffd30c42cdfa30
001e001c
00000000
26e5c558
00007ff8
...
*
ffffd30c42cdfbc0 size:
20
previous size:
0
(Allocated)
*
Clfs
Pooltag Clfs : CLFS General
buffer
,
or
owner page lookaside
list
, Binary : clfs.sys
ffffd30c42cdfbd0
00000101
10000000
00002000
00007ff8
ffffd30c42cdfbe0 size:
20
previous size:
0
(Allocated) Clfs
ffffd30c42cdfbf0
00000000
10000000
00002000
00007ff8
|
可以看到,虽然调用时请求分配的空间大小是 0,但实际上分配的是大小为 32 字节(包含头部)的堆块,且堆块都位于连续的一块空间中,且堆块头部数据相对固定:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
2
: kd> dt _POOL_HEADER ffffd30c42cdfbc0
nt!_POOL_HEADER
+
0x000
PreviousSize :
0y00000000
(
0
)
+
0x000
PoolIndex :
0y00000000
(
0
)
+
0x002
BlockSize :
0y00000010
(
0x2
)
+
0x002
PoolType :
0y00000011
(
0x3
)
+
0x000
Ulong1 :
0x3020000
+
0x004
PoolTag :
0x73666c43
+
0x008
ProcessBilled : (null)
+
0x008
AllocatorBackTraceIndex :
0
+
0x00a
PoolTagHash :
0
2
: kd> db ffffd30c42cdfbc0 l10
ffffd30c
*
42cdfbc0
00
00
02
03
43
6c
66
73
-
00
00
00
00
00
00
00
00
....Clfs........
|
也就是说,当相对于 blockHeader
进行取值时,如果偏移范围在 0x10-0x1F、0x30-0x3F、0x50-0x5F...,那么取到的数据就来自相对固定的头部数据,否则就来自未初始化的数据空间。
再次回到发生崩溃的函数,看一下涉及到的数据来源:
注:以下代码删除了一些细节,主要关注相对于blockHeader
的偏移量
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
|
__int64 __fastcall ClfsEncodeBlockPrivate(struct _CLFS_LOG_BLOCK_HEADER
*
blockHeader, unsigned
int
allSectorSize, char usn, unsigned __int8 a4)
{
nSector
=
*
(blockHeader
+
4
);
/
/
TotalSectorCount
if
( !nSector ||
*
(blockHeader
+
6
) < nSector || nSector <<
9
> allSectorSize )
/
/
ValidSectorCount
return
0xC01A000Ai64
;
if
( (
*
(blockHeader
+
0x10
) &
1
) !
=
0
)
/
/
Flags
return
0xC01A000Ai64
;
signatureOffset
=
*
(blockHeader
+
0x68
);
/
/
SignaturesOffset
*
(blockHeader
+
2
)
=
usn;
/
/
Usn
signatureArray
=
blockHeader
+
signatureOffset;
if
( ULongAdd(signatureOffset,
2
*
(allSectorSize >>
9
), v21) <
0
||
0
> allSectorSize )
{
return
0xC01A000Ai64
;
}
idx
=
0
;
if
( nSector )
{
do
{
signatureArray
+
=
2i64
;
flag1
=
0x20
; flag2
=
0x40
; flag
=
flag2 | flag1;
curSectorSize
=
idx <<
9
;
LOBYTE(v22)
=
a4 | flag;
+
+
idx;
*
(signatureArray
-
2
)
=
*
(curSectorSize
+
blockHeader
+
0x1FE
);
/
/
异常发生位置
/
/
获取当前sector结尾处的signature并写入signature array
*
(curSectorSize
+
blockHeader
+
0x1FE
)
=
v22;
}
while
( idx < nSector);
}
return
0i64
;
}
|
越界写的位置是 signatureArray - 2
,主要受到偏移 0x68 位置的 signatureOffset
的影响;越界写的数据是 *(curSectorSize + blockHeader+ 0x1FE)
,主要受到偏移 0x4 位置的 nSector
的影响,就是因为这里的数值太大才导致了崩溃的发生。
还有一些偏移位置的数据会影响程序流程,这里暂时不关注。
总结:
根据上面对于堆块结构的分析,如果 blockHeader+【offset】
中 offset % 16
的结果是奇数,那么数据来源就是相对固定的堆块头部,否则数据来源就是未初始化的数据空间。
所以如果不考虑超出这块内存空间(即内部全部是0x10堆块的空间)的情况:写入数据所在的位置正位于堆块头部偏移 0x0E 的位置,该位置的数据绝大部分情况下均为 0x0000 ;写入位置可以通过类似堆喷射的方式进行控制。
5. 总结
通过以上分析可知,CVE-2022-30220 漏洞是由于对保存在堆块头部中的 cBlocks
字段的检查不够充分造成的,通过修改 BLF 文件中该字段数值,可以导致堆越界写的发生。由于堆空间结构的限制,越界写的位置与写入数据受到限制,如果想要进行漏洞利用,可能需要对堆空间结构更详细的分析,并通过修改BLF文件内容以及类似堆喷射的方式对数据进行控制。
6. 参考资料
-
https://github.com/libyal/libfsclfs/blob/main/documenation/Common%20Log%20File%20System%20(CLFS).asciidoc
Windows Internal editon 6th