-
-
[原创]CVE-2023-36036 Windows Cloud Files Mini Filter Driver 权限提升漏洞分析
-
2023-12-5 15:19 8711
-
[原创]CVE-2023-36036 Windows Cloud Files Mini Filter Driver 权限提升漏洞分析
基本信息
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
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; |
在这个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]]
这里引用一下前辈的主页。
整体来看,这个漏洞原理和触发方式较为简单,在使用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/