首页
社区
课程
招聘
[原创]CVE-2023-36036 Windows Cloud Files Mini Filter Driver 权限提升漏洞分析
2023-12-5 15:19 7986

[原创]CVE-2023-36036 Windows Cloud Files Mini Filter Driver 权限提升漏洞分析

2023-12-5 15:19
7986

基本信息

Windows Cloud Files Mini Filter 驱动中存在越界写入漏洞,在解析Reparse point数据时,由于memcpy函数的长度参数用户可控,源内存可控,导致攻击者可以构造恶意结构并传递给Windows Cloud Files Mini Filter 驱动,造成越界写入,并在内核执行任意代码。

影响版本

环境搭建

  • Windows 10 23年10月补丁

技术分析&调试

cldflt.sys驱动中实现了云文件的各项功能,diff该驱动,修改函数如下:

HsmpRpiDecompressBuffer函数中有如下修改,对*(_WORD *)(a1 + 10)添加了一个判断,是否>0x4000

如果大于则抛出错误 0xC000CF02对应 STATUS_CLOUD_FILE_METADATA_CORRUPT

https://endodermis59.rssing.com/chan-36587470/all_p6.html

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还原为可读的未压缩数据。

HsmpRpiDecompressBufferHsmpRpReadBuffer调用

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数据,然后对数据进行解压和解析,最后返回解析后的数据。
 
主要功能聚焦在读取压缩数据并解压这块。

HsmpRpReadBufferHsmpRpCommitNoLockHsmpSetupContexts调用。

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,其中 ReparseTagIO_REPARSE_TAG_CLOUD_30x9000301A
并且在结构体 HsmReparseBufferRawRawData成员中存储了由 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;

这个Github仓库中实现了对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结构体的一些字段做了如下校验。

  • reparse_data_buffer->ReparseDataLength > 4
  • reparse_data_buffer->hsm_reparse_data.Flags=1
  • reparse_data_buffer->hsm_reparse_data.FileData.Magic = 'pReF'
  • reparse_data_buffer->hsm_reparse_data.FileData.Flags = 2, 并且reparse_data_buffer->hsm_reparse_data.FileData.Crc32 == RtlComputeCrc32(0, (PUCHAR)&a1->FileData.Length, v2 - 8
  • NumberOfElements 不为0,且最大为10,最后一个以NONE结尾

特别的,从如下代码中可以看到对ElementInfos[0]ElementInfos[1]进行了校验,容易得出如下条件:

  • NumberOfElements > 1
  • FileData.Length >= 0x20
  • `FileData.ElementInfos[1].Type == 0xA
  • 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,依此类推,在构造缓冲区时。

如何将构造好的数据传递给驱动并在目标位置触发呢?在网上查到有类似漏洞分析文章Windows云文件迷你过滤器驱动程序中的提权漏洞(CVE-2021-31969),不难看出CVE-2021-31969修复和本次分析的漏洞CVE-2023-36036修复位置类似,都对ReparseDataLength进行了判断,所以本次PoC编写也可以借鉴。

在CVE-2021-31969分析文章中贴出了部分PoC,结合这部分PoC和前面的结构体,写出PoC也就不难了。

RtlCompressBuffer声明
https://www.cnblogs.com/LyShark/p/17848724.html

动态调试

在如下两个位置下断点

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]]

这里引用一下前辈的主页。

https://www.zezula.net/en/tools/main.html

整体来看,这个漏洞原理和触发方式较为简单,在使用memcpy之前没有校验长度,而修复也简单,再解压之前验证长度是否超过0x4000,超过则认为数据有错,进入到错误逻辑,从而在源头阻止了触发漏洞逻辑。

在漏洞修复处在修复上个整数下溢的漏洞时,开发人员只修复当时的整数下溢漏洞,没有去考虑长度会不会过长,某些程度来说这也是开发的粗心大意导致了这个漏洞留到现在。

在编写PoC参考了其他安全研究员已有的分析。

参考链接

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36036
https://zhuanlan.zhihu.com/p/392194464
https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/CloudMirror
https://learn.microsoft.com/en-us/windows/win32/cfapi/cloud-filter-reference
https://learn.microsoft.com/zh-cn/windows/win32/cfapi/cloud-files-functions
https://learn.microsoft.com/en-us/windows/win32/api/_cloudapi/


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞6
打赏
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  kanxue   +1.00 2024/01/29
最新回复 (1)
雪    币: 19389
活跃值: (29037)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-12-6 09:58
2
2
感谢分享
游客
登录 | 注册 方可回帖
返回