【二进制漏洞-CVE-2023-36036 Windows Cloud Files Mini Filter Driver 权限提升漏洞分析】此文章归类为:二进制漏洞。
Windows Cloud Files Mini Filter 驱动中存在越界写入漏洞,在解析Reparse point数据时,由于memcpy函数的长度参数用户可控,源内存可控,导致攻击者可以构造恶意结构并传递给Windows Cloud Files Mini Filter 驱动,造成越界写入,并在内核执行任意代码。
略
cldflt.sys驱动中实现了云文件的各项功能,diff该驱动,修改函数如下:
在HsmpRpiDecompressBuffer
函数中有如下修改,对*(_WORD *)(a1 + 10)
添加了一个判断,是否>0x4000
,
如果大于则抛出错误 0xC000CF02
对应 STATUS_CLOUD_FILE_METADATA_CORRUPT
AI输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
HsmpRpiDecompressBuffer函数的作用是解压压缩后的Reparse Point数据。
主要功能包括:
1. 校验传入数据的完整性和魔数是否正确
2. 如果数据被压缩,则根据原长度分配解压缓冲区
3. 调用RtlDecompressBuffer进行实际解压
4. 检查解压后数据长度是否匹配
5. 如果解压成功,返回解压后的数据
6. 否则返回错误码
所以它是一个典型的压缩数据解压函数,接收原始压缩数据,校验->分配缓冲区->解压->返回解压后数据的过程。
通过解压让后续代码可以处理未压缩的Reparse Point数据,一般在需要提交/更新数据时会解压。
主要作用就是将压缩后的Reparse Point还原为可读的未压缩数据。
|
HsmpRpiDecompressBuffer
由 HsmpRpReadBuffer
调用
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
|
__int64
__fastcall HsmpRpReadBuffer(PFLT_INSTANCE Instance, PFILE_OBJECT FileObject, unsigned
__int16
**a3)
{
...
*a3 = 0i64;
v6 = 1024;
OutputBuffer = (unsigned
__int16
*)ExAllocatePoolWithTag(PagedPool, 0x400ui64, 0x70527348u);
v8 = OutputBuffer;
......
}
LODWORD(v9) = HsmpRpiDecompressBuffer((
__int64
)v8, v6, a3);
HsmDbgBreakOnStatus((unsigned
int
)v9);
if
( (
int
)v9 < 0 )
{
v16 = WPP_GLOBAL_Control;
if
( WPP_GLOBAL_Control == (PDEVICE_OBJECT)&WPP_GLOBAL_Control
|| (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) == 0
|| BYTE1(WPP_GLOBAL_Control->Timer) < 2u )
{
goto
LABEL_8;
}
v17 = 20i64;
goto
LABEL_30;
}
if
( *a3 != v8 )
LABEL_8:
ExFreePoolWithTag(v8, 0x70527348u);
return
(unsigned
int
)v9;
}
|
AI解释
1
2
3
4
5
6
7
8
9
10
11
12
13
|
这段代码实现了解析和解压Reparse Point数据的功能:
1.
分配OutputBuffer内存用于读取Reparse数据。
2.
调用FltFsControlFile读取Reparse数据到OutputBuffer。
3.
如果读取失败,并且错误码表示
buffer
太小,则重新分配更大的
buffer
并重试读取。
4.
调用HsmpRpiDecompressBuffer对读取到的数据进行解压。
5.
如果解压成功,返回解压后的数据,否则返回错误码。
6.
释放原始数据内存。
7.
其中包含了详细的错误和日志记录逻辑。
所以总的来说,这段代码用于从文件系统读取Reparse Point数据,然后对数据进行解压和解析,最后返回解析后的数据。
主要功能聚焦在读取压缩数据并解压这块。
|
HsmpRpReadBuffer
由 HsmpRpCommitNoLock
和 HsmpSetupContexts
调用。
HsmpRpCommitNoLock
解释如下
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
|
这个C函数实现了HsmpRpCommitNoLock,它似乎是用于在没有锁的情况下提交页面重播(Page Replay)数据。
主要的逻辑如下:
1. 校验和准备输入数据
2. 分配内存池来存储提交的数据
3. 构建数据结构,填充各种元数据
4. 尝试压缩数据
5. 将数据写入文件
6. 清理临时数据结构和内存
具体来说,这个函数做了以下工作:
1. 验证输入参数的有效性
2. 为输出缓冲区分配内存
3. 构建输出缓冲区的数据结构
4. 填充输出缓冲区的头部
5. 将输入缓冲区的数据复制到输出缓冲区
6. 计算校验和
7. 尝试压缩输出缓冲区
8. 标记文件属性
9. 将输出缓冲区的数据写入文件
10. 重置文件属性
11. 释放临时缓冲区和内存
所以总的来说,这个函数的主要目的是准备并提交页面重播数据,同时处理必要的校验、压缩和清理工作。
|
在 HsmpRpCommitNoLock
中有如下代码,可以看到在前面diff中出现的0x4000和0x3FFC,可以猜测漏洞产生于该函数中
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
|
LABEL_156:
PoolWithTag = (unsigned
int
*)ExAllocatePoolWithTag(PagedPool, 0x4000ui64, 0x70527348u);
v142 = PoolWithTag;
v11 = (
char
*)PoolWithTag;
if
( PoolWithTag )
{
memset
(PoolWithTag, 0, 0x4000ui64);
v57 = InputBuffer;
v58 = v11 + 4;
if
( v8 && *((_WORD *)v8 + 7) > 0xAu )
v57 = *((_WORD *)v8 + 7);
v59 = (unsigned
int
*)(v58 + 8);
*((_WORD *)v58 + 6) = 0;
v9 = (unsigned
__int64
)(v58 + 16);
*((_WORD *)v58 + 7) = v57;
*((_DWORD *)v58 + 2) = 8 * v57 + 16;
*(_DWORD *)v58 =
'pReF'
;
memset
(v58 + 16, 0, 8i64 * v57);
if
( *((_WORD *)v58 + 7) )
{
v60 = *v59;
if
( ((v60 + 3) & 0xFFFFFFFFFFFFFFFCui64) + 1 <= 0x3FFC )
// 12 偏移
{
*v59 = (v60 + 3) & 0xFFFFFFFC;
if
( *(_WORD *)v9 )
*((_WORD *)v58 + 6) |= 1u;
*(_WORD *)v9 = 7;
LODWORD(v9) = 0;
*((_WORD *)v58 + 9) = 1;
v61 = *v59;
*((_DWORD *)v58 + 5) = v61;
v58[v61] = 1;
|
继续审查代码,发现在HsmpRpCommitNoLock
中有如下代码,在do while循环中调用memmove函数时,传入的src来源于 HsmpRpReadBuffer
解压后的element[10]
数据,dst为ExAllocatePoolWithTag
分配的大小为0x4000的内存。长度参数来源于ElementInfos[10].Length
,不难看出由此可以造成越界写入,且用户可控。
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
|
v32 = 0i64;
if
( (v9 & 0x80000000) == 0i64 )
{
v8 = (
char
*)P + 12;
...
{
if
( (_DWORD)v54 && (_WORD)v55 )
v167 = &v8[v54];
else
v167 = v32;
.....
PoolWithTag = (unsigned
int
*)ExAllocatePoolWithTag(PagedPool, 0x4000ui64, 0x70527348u);
v142 = PoolWithTag;
v11 = (
char
*)PoolWithTag;
if
( PoolWithTag )
{
memset
(PoolWithTag, 0, 0x4000ui64);
v57 = InputBuffer;
v58 = v11 + 4;
if
( v8 && *((_WORD *)v8 + 7) > 0xAu )
v57 = *((_WORD *)v8 + 7);
v59 = (unsigned
int
*)(v58 + 8);
*((_WORD *)v58 + 6) = 0;
v9 = (unsigned
__int64
)(v58 + 16);
*((_WORD *)v58 + 7) = v57;
*((_DWORD *)v58 + 2) = 8 * v57 + 16;
*(_DWORD *)v58 =
'pReF'
;
memset
(v58 + 16, 0, 8i64 * v57);
.....
}
*v59 += v109;
.....
if
( *((_WORD *)v58 + 28) )
*((_WORD *)v58 + 6) |= 1u;
*v59 = (v113 + 3) & 0xFFFFFFFC;
....
*v59 += v114;
.....
v117 = (
char
*)v167;
v107 = (
char
*)Src;
*v59 = (v118 + 3) & 0xFFFFFFFC;
*((_WORD *)v58 + 32) = 17;
*((_WORD *)v58 + 33) = v119;
v121 = *v59;
*((_DWORD *)v58 + 17) = v121;
if
( &v58[v121] != v117 )
{
memmove
(&v58[v121], v117, v120);
......
v125 = 10;
do
{
v126 = v125;
*(HSM_ELEMENT_INFO *)&v58[8 * v125 + 16] = v124->ElementInfos[v125];
memmove
(&v58[*v59], (
char
*)v124 + v124->ElementInfos[v125].Offset, v124->ElementInfos[v125].Length);
++v125;
*(_DWORD *)&v58[8 * v126 + 20] = *v59;
*v59 += *(unsigned
__int16
*)&v58[8 * v126 + 18];
}
while
( v125 < v124->NumberOfElements );
...
if
( v14 )
ExFreePoolWithTag(v14, 0x70527348u);
if
( v11 )
ExFreePoolWithTag(v11, 0x70527348u);
return
(unsigned
int
)v9;
}
|
搜索Reparse point RtlCompressBuffer
,找到文章,根据 _REPARSE_DATA_BUFFER
定义如下,可以知道传入 HsmpRpiDecompressBuffer
的是 REPARSE_DATA_BUFFER
,其中 ReparseTag
为IO_REPARSE_TAG_CLOUD_3
值 0x9000301A
并且在结构体 HsmReparseBufferRaw
的RawData
成员中存储了由 RtlCompressBuffer
压缩的数据HsmReparseBufferRaw
1
2
3
4
5
6
|
// Handled by cldflt.sys!HsmpRpReadBuffer
struct
{
USHORT
Flags;
// Flags (0x8000 = not compressed)
USHORT
Length;
// Length of the data (uncompressed)
BYTE
RawData[1];
// To be RtlDecompressBuffer-ed
} HsmReparseBufferRaw;
|
_REPARSE_DATA_BUFFER
定义
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
|
typedef
struct
_REPARSE_DATA_BUFFER {
ULONG
ReparseTag;
// Reparse tag type
USHORT
ReparseDataLength;
// Length of the reparse data
USHORT
Reserved;
// Used internally by NTFS to store remaining length
union
{
// Structure for IO_REPARSE_TAG_SYMLINK
// Handled by nt!IoCompleteRequest
struct
{
USHORT
SubstituteNameOffset;
USHORT
SubstituteNameLength;
USHORT
PrintNameOffset;
USHORT
PrintNameLength;
ULONG
Flags;
WCHAR
PathBuffer[1];
/* Example of distinction between substitute and print names:
// mklink /d ldrive c:\
// SubstituteName: c:\\??\
// PrintName: c:\
*/
} SymbolicLinkReparseBuffer;
// Structure for IO_REPARSE_TAG_MOUNT_POINT
// Handled by nt!IoCompleteRequest
struct
{
USHORT
SubstituteNameOffset;
USHORT
SubstituteNameLength;
USHORT
PrintNameOffset;
USHORT
PrintNameLength;
WCHAR
PathBuffer[1];
} MountPointReparseBuffer;
// Structure for IO_REPARSE_TAG_WIM
// Handled by wimmount!FPOpenReparseTarget->wimserv.dll
// (wimsrv!ImageExtract)
struct
{
GUID ImageGuid;
// GUID of the mounted VIM image
BYTE
ImagePathHash[0x14];
// Hash of the path to the file within the
// image
} WimImageReparseBuffer;
// Structure for IO_REPARSE_TAG_WOF
// Handled by FSCTL_GET_EXTERNAL_BACKING, FSCTL_SET_EXTERNAL_BACKING in
// NTFS (Windows 10+)
struct
{
//-- WOF_EXTERNAL_INFO --------------------
ULONG
Wof_Version;
// Should be 1 (WOF_CURRENT_VERSION)
ULONG
Wof_Provider;
// Should be 2 (WOF_PROVIDER_FILE)
//-- FILE_PROVIDER_EXTERNAL_INFO_V1 --------------------
ULONG
FileInfo_Version;
// Should be 1 (FILE_PROVIDER_CURRENT_VERSION)
ULONG
FileInfo_Algorithm;
// Usually 0 (FILE_PROVIDER_COMPRESSION_XPRESS4K)
} WofReparseBuffer;
// Structure for IO_REPARSE_TAG_APPEXECLINK
struct
{
ULONG
StringCount;
// Number of the strings in the StringList, separated
// by '\0'
WCHAR
StringList[1];
// Multistring (strings separated by '\0',
// terminated by '\0\0')
} AppExecLinkReparseBuffer;
// Structure for IO_REPARSE_TAG_WCI (0x80000018)
struct
{
ULONG
Version;
// Expected to be 1 by wcifs.sys
ULONG
Reserved;
GUID LookupGuid;
// GUID used for lookup in wcifs!WcLookupLayer
USHORT
WciNameLength;
// Length of the WCI subname, in bytes
WCHAR
WciName[1];
// The WCI subname (not zero terminated)
} WcifsReparseBuffer;
// Handled by cldflt.sys!HsmpRpReadBuffer
struct
{
USHORT
Flags;
// Flags (0x8000 = not compressed)
USHORT
Length;
// Length of the data (uncompressed)
BYTE
RawData[1];
// To be RtlDecompressBuffer-ed
} HsmReparseBufferRaw;
// Dummy structure
struct
{
UCHAR
DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
|
在中实现了对Reparse point的解析,其中定义了HSM_REPARSE_DATA
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
|
typedef
struct
_HSM_ELEMENT_INFO
{
USHORT
Type;
// Type of the element (?). One of HSM_ELEMENT_TYPE_XXX
USHORT
Length;
// Length of the element data in bytes
ULONG
Offset;
// Offset of the element data, relative to begin of HSM_DATA. Aligned to 4 bytes
} HSM_ELEMENT_INFO, *PHSM_ELEMENT_INFO;
typedef
struct
_HSM_DATA
{
ULONG
Magic;
// 0x70527442 ('pRtB') for bitmap data, 0x70526546 ('FeRp') for file data
ULONG
Crc32;
// CRC32 of the following data (calculated by RtlComputeCrc32)
ULONG
Length;
// Length of the entire HSM_DATA in bytes
USHORT
Flags;
// HSM_DATA_XXXX
USHORT
NumberOfElements;
// Number of elements
HSM_ELEMENT_INFO ElementInfos[1];
// Array of element infos. There is fixed maximal items for bitmap and reparse data
} HSM_DATA, *PHSM_DATA;
typedef
struct
_HSM_REPARSE_DATA
{
USHORT
Flags;
// Lower 8 bits is revision (must be 1 as of Windows 10 16299)
// Flags: 0x8000 = Data needs to be decompressed by RtlCompressBuffer
USHORT
Length;
// Length of the HSM_REPARSE_DATA structure (including "Flags" and "Length")
HSM_DATA FileData;
// HSM data
} HSM_REPARSE_DATA, *PHSM_REPARSE_DATA;
|
对应在 REPARSE_DATA_BUFFER
的偏移如下
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
|
0
:
000
> dt pa
Local var @
0xa8444fec08
Type
_REPARSE_DATA_BUFFER
*
0x000001e0
`ef867690
+
0x000
ReparseTag :
0x9000301a
+
0x004
ReparseDataLength :
0x4008
+
0x006
Reserved :
0
+
0x008
SymbolicLinkReparseBuffer : _REPARSE_DATA_BUFFER::<unnamed
-
tag>::<unnamed
-
type
-
SymbolicLinkReparseBuffer>
+
0x008
MountPointReparseBuffer : _REPARSE_DATA_BUFFER::<unnamed
-
tag>::<unnamed
-
type
-
MountPointReparseBuffer>
+
0x008
WimImageReparseBuffer : _REPARSE_DATA_BUFFER::<unnamed
-
tag>::<unnamed
-
type
-
WimImageReparseBuffer>
+
0x008
WofReparseBuffer : _REPARSE_DATA_BUFFER::<unnamed
-
tag>::<unnamed
-
type
-
WofReparseBuffer>
+
0x008
AppExecLinkReparseBuffer : _REPARSE_DATA_BUFFER::<unnamed
-
tag>::<unnamed
-
type
-
AppExecLinkReparseBuffer>
+
0x008
WcifsReparseBuffer : _REPARSE_DATA_BUFFER::<unnamed
-
tag>::<unnamed
-
type
-
WcifsReparseBuffer>
+
0x008
hsm_reparse_data : _HSM_REPARSE_DATA
+
0x008
GenericReparseBuffer : _REPARSE_DATA_BUFFER::<unnamed
-
tag>::<unnamed
-
type
-
GenericReparseBuffer>
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_REPARSE_DATA
*
)
0x1e0ef867698
))
(
*
((poc3!_HSM_REPARSE_DATA
*
)
0x1e0ef867698
)) [
Type
: _HSM_REPARSE_DATA]
[
+
0x000
] Flags :
0x8001
[
Type
: unsigned short]
/
/
8
[
+
0x002
] Length :
0x4008
[
Type
: unsigned short]
/
/
10
[
+
0x004
] FileData [
Type
: _HSM_DATA]
/
/
12
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_DATA
*
)
0x1e0ef86769c
))
(
*
((poc3!_HSM_DATA
*
)
0x1e0ef86769c
)) [
Type
: _HSM_DATA]
[
+
0x000
] Magic :
0x70526546
[
Type
: unsigned
long
]
/
/
12
[
+
0x004
] Crc32 :
0x31e13b17
[
Type
: unsigned
long
]
/
/
16
[
+
0x008
] Length :
0x4004
[
Type
: unsigned
long
]
/
/
20
[
+
0x00c
] Flags :
0x2
[
Type
: unsigned short]
/
/
24
[
+
0x00e
] NumberOfElements :
0xb
[
Type
: unsigned short]
/
/
26
[
+
0x010
] ElementInfos [
Type
: _HSM_ELEMENT_INFO [
10
]]
/
/
28
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x1e0ef8676ac
))
(
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x1e0ef8676ac
)) [
Type
: _HSM_ELEMENT_INFO]
[
+
0x000
]
Type
:
0x7
[
Type
: unsigned short]
/
/
28
[
+
0x002
] Length :
0x1
[
Type
: unsigned short]
/
/
30
[
+
0x004
] Offset :
0x68
[
Type
: unsigned
long
]
/
/
32
-
35
/
/
36
-
43
|
结构体_HSM_ELEMENT_INFO
内存信息
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
|
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO (
*
)[
11
])
0x23d15ae6a9c
))
(
*
((poc3!_HSM_ELEMENT_INFO (
*
)[
11
])
0x23d15ae6a9c
)) [
Type
: _HSM_ELEMENT_INFO [
11
]]
[
0
] [
Type
: _HSM_ELEMENT_INFO]
[
1
] [
Type
: _HSM_ELEMENT_INFO]
[
2
] [
Type
: _HSM_ELEMENT_INFO]
[
3
] [
Type
: _HSM_ELEMENT_INFO]
[
4
] [
Type
: _HSM_ELEMENT_INFO]
[
5
] [
Type
: _HSM_ELEMENT_INFO]
[
6
] [
Type
: _HSM_ELEMENT_INFO]
[
7
] [
Type
: _HSM_ELEMENT_INFO]
[
8
] [
Type
: _HSM_ELEMENT_INFO]
[
9
] [
Type
: _HSM_ELEMENT_INFO]
[
10
] [
Type
: _HSM_ELEMENT_INFO]
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6a9c
))
/
/
0
(
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6a9c
)) [
Type
: _HSM_ELEMENT_INFO]
[
+
0x000
]
Type
:
0x7
[
Type
: unsigned short]
[
+
0x002
] Length :
0x1
[
Type
: unsigned short]
[
+
0x004
] Offset :
0x68
[
Type
: unsigned
long
]
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6aa4
))
/
/
1
(
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6aa4
)) [
Type
: _HSM_ELEMENT_INFO]
[
+
0x000
]
Type
:
0xa
[
Type
: unsigned short]
[
+
0x002
] Length :
0x4
[
Type
: unsigned short]
[
+
0x004
] Offset :
0x6c
[
Type
: unsigned
long
]
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6aac
))
/
/
2
(
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6aac
)) [
Type
: _HSM_ELEMENT_INFO]
[
+
0x000
]
Type
:
0x0
[
Type
: unsigned short]
[
+
0x002
] Length :
0x0
[
Type
: unsigned short]
[
+
0x004
] Offset :
0x0
[
Type
: unsigned
long
]
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6ab4
))
/
/
3
(
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6ab4
)) [
Type
: _HSM_ELEMENT_INFO]
[
+
0x000
]
Type
:
0x11
[
Type
: unsigned short]
[
+
0x002
] Length :
0x0
[
Type
: unsigned short]
[
+
0x004
] Offset :
0x70
[
Type
: unsigned
long
]
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6abc
))
/
/
4
(
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6abc
)) [
Type
: _HSM_ELEMENT_INFO]
[
+
0x000
]
Type
:
0x0
[
Type
: unsigned short]
[
+
0x002
] Length :
0x0
[
Type
: unsigned short]
[
+
0x004
] Offset :
0x0
[
Type
: unsigned
long
]
0
:
000
> dx
-
r1 (
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6aec
))
/
/
10
(
*
((poc3!_HSM_ELEMENT_INFO
*
)
0x23d15ae6aec
)) [
Type
: _HSM_ELEMENT_INFO]
[
+
0x000
]
Type
:
0x0
[
Type
: unsigned short]
[
+
0x002
] Length :
0x3f94
[
Type
: unsigned short]
[
+
0x004
] Offset :
0x70
[
Type
: unsigned
long
]
|
PoC构造
将结构体导入到ida中,在HsmpRpCommitNoLock
中首先对ReparseTag进行验证,而后将hsm_reparse_data和对应的长度导入到 HsmpRpValidateBuffer
函数中验证。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
if
( (reparse_data_buffer->ReparseTag & 0xFFFF0FFF) != dword_1C00235D0 )
{
LODWORD(v9) = -1073688821;
HsmDbgBreakOnStatus(3221278475i64);
if
( WPP_GLOBAL_Control != (PDEVICE_OBJECT)&WPP_GLOBAL_Control
&& (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) != 0
&& BYTE1(WPP_GLOBAL_Control->Timer) >= 2u )
{
WPP_SF_qiqDDd(
WPP_GLOBAL_Control->AttachedDevice,
2i64,
v30,
a2,
*(_QWORD *)(v5 + 32),
v29,
dword_1C00235D0,
reparse_data_buffer->ReparseTag);
}
goto
LABEL_8;
}
ReparseDataLength = reparse_data_buffer->ReparseDataLength;
v9 = (unsigned
int
)HsmpRpValidateBuffer(&reparse_data_buffer->DUMMYUNIONNAME.hsm_reparse_data, ReparseDataLength);
|
在 HsmpRpValidateBuffer
函数中对HSM_DATA结构体的一些字段做了如下校验。
特别的,从如下代码中可以看到对ElementInfos[0]
和ElementInfos[1]
进行了校验,容易得出如下条件:
NumberOfElements > 1
FileData.Length >= 0x20
FileData.ElementInfos[1].Offset >= 8 * NumberOfElements + 16 && FileData.ElementInfos[1].Offset < FileData.Length
FileData.ElementInfos[1].Length == 4
FileData.ElementInfos[1].Length + FileData.ElementInfos[1].Offset < 65535
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
if
( (unsigned
__int16
)NumberOfElements > 1u
&& (unsigned
int
)Length >= 0x20
&& (v22 = a1->FileData.ElementInfos[1].Type, v22 < 0x12u)
&& ((v23 = a1->FileData.ElementInfos[1].Offset, !(_DWORD)v23) || v23 >= hsm_data_length)
&& (unsigned
int
)v23 <= (unsigned
int
)Length
&& (v24 = a1->FileData.ElementInfos[1].Length, v24 <= (unsigned
int
)Length)
&& v24 + (unsigned
int
)v23 >= (unsigned
int
)v23
&& v24 + (unsigned
int
)v23 <= (unsigned
int
)Length
&& v22 == 10
&& v24 == 4 )
{
v5 = *(
ULONG
*)((
char
*)&p_FileData->Magic + v23);
IsReparseBufferSupported = 0;
}
else
{
IsReparseBufferSupported = 0xC0000225;
}
|
如下代码对ElementInfos[2]
进行了校验,有如下:
FileData.ElementInfos[2].Offset < FileData.Length
FileData.ElementInfos[2].Length < FileData.Length
FileData.ElementInfos[2].Type == 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
if
( (element_1_Data & 0x10) != 0 )
return
IsReparseBufferSupported;
v27 = a1->FileData.Length;
if
( v27 < 0x18
|| (v28 = a1->FileData.NumberOfElements, (unsigned
__int16
)v28 <= 2u)
|| v27 < 0x28
|| (v29 = a1->FileData.ElementInfos[2].Type, v29 >= 0x12u)
|| (v30 = a1->FileData.ElementInfos[2].Offset, (_DWORD)v30) && v30 < 8 * v28 + 16
|| (unsigned
int
)v30 > v27
|| (v31 = a1->FileData.ElementInfos[2].Length, v31 > v27)
|| v31 + (unsigned
int
)v30 < (unsigned
int
)v30
|| v31 + (unsigned
int
)v30 > v27
|| v29 != 6
|| (IsReparseBufferSupported = 0, v31 != 8) )
{
IsReparseBufferSupported = 0xC0000225;
}
|
后面还有一堆校验逻辑就不贴了。
在 HsmpRpCommitNoLock
中对 HsmpRpValidateBuffer
返回值做了校验,如果IsReparseBufferSupported
不为0则会进入报错逻辑,而在 HsmpRpValidateBuffer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
IsReparseBufferSupported = (unsigned
int
)HsmpRpValidateBuffer(
&reparse_data_buffer->DUMMYUNIONNAME.hsm_reparse_data,
ReparseDataLength);
HsmDbgBreakOnStatus(IsReparseBufferSupported);
v32 = 0i64;
if
( (IsReparseBufferSupported & 0x80000000) == 0i64 )
{
...
}
else
{
HsmDbgBreakOnCorruption();
if
( a4 == (_BYTE)v32 )
{
if
( WPP_GLOBAL_Control != (PDEVICE_OBJECT)&WPP_GLOBAL_Control
&& (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) != 0
|
在HsmpRpValidateBuffer
中可以看到当通过第一次校验后,如果ElementInfos[1]
的Data & 0x10 则会直接返回,此时IsReparseBufferSupported=0
能通过校验。
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
|
if
( (unsigned
__int16
)NumberOfElements > 1u
&& (unsigned
int
)Length >= 0x20
&& (v22 = a1->FileData.ElementInfos[1].Type, v22 < 0x12u)
&& ((v23 = a1->FileData.ElementInfos[1].Offset, !(_DWORD)v23) || v23 >= hsm_data_length)
&& (unsigned
int
)v23 <= (unsigned
int
)Length
&& (v24 = a1->FileData.ElementInfos[1].Length, v24 <= (unsigned
int
)Length)
&& v24 + (unsigned
int
)v23 >= (unsigned
int
)v23
&& v24 + (unsigned
int
)v23 <= (unsigned
int
)Length
&& v22 == 10
&& v24 == 4 )
{
element_1_Data = *(
ULONG
*)((
char
*)&p_FileData->Magic + v23);
IsReparseBufferSupported = 0;
}
else
{
IsReparseBufferSupported = 0xC0000225;
}
HsmDbgBreakOnStatus(IsReparseBufferSupported);
if
( (IsReparseBufferSupported & 0x80000000) != 0 )
{
v25 = WPP_GLOBAL_Control;
if
( WPP_GLOBAL_Control == (PDEVICE_OBJECT)&WPP_GLOBAL_Control
|| (HIDWORD(WPP_GLOBAL_Control->Timer) & 1) == 0
|| BYTE1(WPP_GLOBAL_Control->Timer) < 2u )
{
return
IsReparseBufferSupported;
}
v26 = 24i64;
goto
LABEL_163;
}
if
( (element_1_Data & 0x10) != 0 )
return
IsReparseBufferSupported;
|
通过构造ElementInfos[0]
和ElementInfos[1]
可以通过HsmpRpValidateBuffer
校验,而后漏洞触发点会读取ElementInfos[10]
的数据和Length通过memcpy进行拷贝,所以还需要构造ElementInfos[10]
的数据,并且ElementInfos[10]
的Length需要超过目标缓冲区,特别的在计算CRC32后,需要通过RtlCompressBuffer压缩目标数据,并放入到FileData处。
构造多大的缓冲区?根据前面补丁分析,在补丁中限制了ReparseDataLength < 0x4000,所以超过四千的部分会造成溢出,如果想溢出8个字节则需要构造0x4008 + 8 = 0x4010,依此类推,在构造缓冲区时。
如何将构造好的数据传递给驱动并在目标位置触发呢?在网上查到有类似漏洞分析文章,不难看出CVE-2021-31969修复和本次分析的漏洞CVE-2023-36036修复位置类似,都对ReparseDataLength进行了判断,所以本次PoC编写也可以借鉴。
在CVE-2021-31969分析文章中贴出了部分PoC,结合这部分PoC和前面的结构体,写出PoC也就不难了。
RtlCompressBuffer声明
动态调试
在如下两个位置下断点
1
2
|
bp cldflt!HsmpRpCommitNoLock
bp cldflt!HsmpRpCommitNoLock
+
0x13de
|
运行poc,可以看到已经进入HsmpRpCommitNoLock
函数
1
2
3
4
|
1
: kd> g
Breakpoint
0
hit
cldflt!HsmpRpCommitNoLock:
fffff804`
6f6a1e88
48895c2420
mov qword ptr [rsp
+
20h
],rbx
|
继续运行,触发第二个断点
1
2
3
4
|
0
: kd> g
Breakpoint
1
hit
cldflt!HsmpRpCommitNoLock
+
0x13de
:
fffff804`
6f6a3266
e81571faff call cldflt!memcpy (fffff804`
6f64a380
)
|
此时memmove已经被优化为memcpy,而要拷贝的长度为0x3f94,dst所在的堆大小为0x4000,dst指向偏移0x74处,最多有0x3f8c大小,所以memcpy拷贝时会越界写入8个字节,造成堆溢出。
1
2
3
4
5
6
|
1
: kd> rr8
r8
=
0000000000003f94
1
: kd> !pool rcx
Pool page ffffd980717f7074 region
is
Paged pool
*
ffffd980717f7000 : large page allocation, tag
is
HsRp, size
is
0x4000
bytes
Owning component : Unknown (update pooltag.txt)
|
继续运行,则在memcpy内部触发异常,因为尝试往未分配的内存里面写入00
1
2
3
4
5
6
7
8
9
10
|
0
: kd> u
cldflt!memcpy
+
0x165
:
fffff800`
8186a4e5
0f2941f0
movaps xmmword ptr [rcx
-
10h
],xmm0
0
: kd> !pool rcx
-
0x10
Pool page ffffe5028e4fa000 region
is
Paged pool
ffffe5028e4fa000
is
not
a valid large pool allocation, checking large session pool...
ffffe5028e4fa000
is
not
valid pool. Checking
for
freed (
or
corrupt) pool
Address ffffe5028e4fa000 could
not
be read. It may be a freed, invalid
or
paged out page
0
: kd> rxmm0
mm0
=
0000000000000000
|
对应代码为
1
2
3
|
if
( v25 )
*(_OWORD *)(v15 + v25 - 16) = *(_OWORD *)(v15 + v25 - 16 + v13);
*(__m128 *)(v15 - 0x10) = v14;
|
以下为调用栈
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
|
1
: kd> k
# Child-SP RetAddr Call Site
00
fffffb8a`
8a45e4f8
fffff804`
63717f82
nt!DbgBreakPointWithStatus
01
fffffb8a`
8a45e500
fffff804`
63717566
nt!KiBugCheckDebugBreak
+
0x12
02
fffffb8a`
8a45e560
fffff804`
635fd747
nt!KeBugCheck2
+
0x946
03
fffffb8a`
8a45ec70
fffff804`
63638f6f
nt!KeBugCheckEx
+
0x107
04
fffffb8a`
8a45ecb0
fffff804`
63430730
nt!MiSystemFault
+
0x1de5ff
05
fffffb8a`
8a45edb0
fffff804`
6360d1d8
nt!MmAccessFault
+
0x400
06
fffffb8a`
8a45ef50
fffff804`
6f64a4e1
nt!KiPageFault
+
0x358
07
fffffb8a`
8a45f0e8
fffff804`
6f6a326b
cldflt!memcpy
+
0x161
08
fffffb8a`
8a45f0f0
fffff804`
6f6a983b
cldflt!HsmpRpCommitNoLock
+
0x13e3
09
fffffb8a`
8a45f230
fffff804`
6f66f0d7
cldflt!HsmiOpUpdatePlaceholderDirectory
+
0x57f
0a
fffffb8a`
8a45f320
fffff804`
6f674b65
cldflt!HsmFltProcessUpdatePlaceholder
+
0x443
0b
fffffb8a`
8a45f3d0
fffff804`
6f6a4504
cldflt!HsmFltProcessHSMControl
+
0x3d5
0c
fffffb8a`
8a45f500
fffff804`
647264cc
cldflt!HsmFltPreFILE_SYSTEM_CONTROL
+
0x6a4
0d
fffffb8a`
8a45f5a0
fffff804`
64725f7a
FLTMGR!FltpPerformPreCallbacksWorker
+
0x36c
0e
fffffb8a`
8a45f6c0
fffff804`
64725021
FLTMGR!FltpPassThroughInternal
+
0xca
0f
fffffb8a`
8a45f710
fffff804`
6475ae2f
FLTMGR!FltpPassThrough
+
0x541
10
fffffb8a`
8a45f7a0
fffff804`
63410665
FLTMGR!FltpFsControl
+
0xbf
11
fffffb8a`
8a45f800
fffff804`
6380142c
nt!IofCallDriver
+
0x55
12
fffffb8a`
8a45f840
fffff804`
63801081
nt!IopSynchronousServiceTail
+
0x34c
13
fffffb8a`
8a45f8e0
fffff804`
638d9ed6
nt!IopXxxControlFile
+
0xc71
14
fffffb8a`
8a45fa20
fffff804`
63610ef5
nt!NtFsControlFile
+
0x56
15
fffffb8a`
8a45fa90
00007ff9
`c648d704 nt!KiSystemServiceCopyEnd
+
0x25
16
00000056
`
01aff5b8
00007ff6
`
5e59167f
ntdll!NtFsControlFile
+
0x14
17
00000056
`
01aff5c0
00000000
`
000001bc
0x00007ff6
`
5e59167f
18
00000056
`
01aff5c8
00000000
`
00000000
0x1bc
|
这里调用HsmpRpValidateBuffer
1
2
3
|
cldflt!HsmpRpCommitNoLock
+
0x573
fffff804`
155823f6
e81539fdff call cldflt!HsmpRpValidateBuffer (fffff80415555d10)
fffff804`
155823fb
8bc8
mov ecx, eax
|
这里调用ExAllocatePoolWithTag
1
2
3
4
5
6
|
cldflt!HsmpRpCommitNoLock
+
0x93D
PAGE:
00000001C00727BE
48
FF
15
B3
61
FB FF call cs:__imp_ExAllocatePoolWithTag
PAGE:
00000001C00727BE
PAGE:
00000001C00727C5
;
601
: P
=
PoolWithTag;
PAGE:
00000001C00727C5
0F
1F
44
00
00
nop dword ptr [rax
+
rax
+
00h
]
/
/
0x942
PAGE:
00000001C00727CA
33
FF xor edi, edi
|
PoC会在过几天上传到GitHub
1
|
https:
/
/
github.com
/
Chestnuts4
/
POC
|
本次漏洞分析离不开业内前辈逆向得出的_HSM_REPARSE_DATA结构体信息,这个结构体微软没有公开的文档,相关资料也很少。目前只有这一个仓库有相关信息,向前辈致敬。
![[../../../images/vulneribility/CVE-2023-36036/7.png]]
这里引用一下前辈的主页。
整体来看,这个漏洞原理和触发方式较为简单,在使用memcpy之前没有校验长度,而修复也简单,再解压之前验证长度是否超过0x4000,超过则认为数据有错,进入到错误逻辑,从而在源头阻止了触发漏洞逻辑。
在漏洞修复处在修复上个整数下溢的漏洞时,开发人员只修复当时的整数下溢漏洞,没有去考虑长度会不会过长,某些程度来说这也是开发的粗心大意导致了这个漏洞留到现在。
在编写PoC参考了其他安全研究员已有的分析。
参考链接
更多【二进制漏洞-CVE-2023-36036 Windows Cloud Files Mini Filter Driver 权限提升漏洞分析】相关视频教程:www.yxfzedu.com