【漏洞分析】MicrosoftWindows通讯簿中堆缓冲区溢出漏洞分析

admin 2021年12月16日23:41:16评论142 views字数 16159阅读53分51秒阅读模式

点击上方蓝字“Ots安全”一起玩耍

概述

这篇文章分析了 Microsoft Windows 通讯簿中的堆缓冲区溢出。微软周二发布了针对2021 年 2 月补丁的此漏洞的公告。这篇博文将详细介绍 Microsoft Windows 通讯簿是什么、漏洞本身以及制作概念验证漏洞的步骤,从而使易受攻击的应用程序崩溃。


视窗通讯簿

Windows 通讯簿是 Microsoft Windows 操作系统的一部分,是一项为用户提供集中式联系人列表的服务,Microsoft 和第三方应用程序均可访问和修改这些联系人列表。Windows 通讯簿维护一个本地数据库和界面,用于查找和编辑有关联系人的信息,并且可以使用轻量级目录访问协议 (LDAP) 查询网络目录服务器。Windows 通讯簿于 1996 年推出,后来在 Windows Vista 中被 Windows 联系人取代,随后在 Windows 10 中被人脉应用程序取代。


Windows 通讯簿提供了一个 API,使其他应用程序能够直接使用其数据库和用户界面服务,从而使服务能够访问和修改联系人信息。虽然 Microsoft 已经替换了提供通讯簿功能的应用程序,但较新的替换程序使用旧功能并确保向后兼容。Windows 通讯簿功能存在于 Windows 10 应用程序(包括 Outlook 和 Windows Mail)使用的多个 Windows 库中。通过这种方式,现代应用程序可以使用 Windows 通讯簿,甚至可以从旧版本的 Windows 导入通讯簿。


CVE-2021-24083

在处理联系人的嵌套属性时,wab32.dll 中的SecurityCheckPropArrayBuffer()函数中存在堆缓冲区溢出漏洞。基于网络的攻击媒介包括诱使用户打开精心制作的.wab文件,其中包含 WAB 记录中的恶意复合属性。


漏洞

以下漏洞分析基于 Windows 10 x64 上运行的 Windows 地址簿联系人 DLL (wab32.dll) 版本 10.0.19041.388。


Windows 通讯簿联系人 DLL(即wab32.dll)提供对通讯簿 API 的访问,多个应用程序使用它来与 Windows 通讯簿交互。Contacts DLL 处理与联系人和身份管理相关的操作。其中,Contacts DLL 能够导入从早期版本的 Windows 地址簿导出的地址簿(即 WAB 文件)。


早期版本的 Windows 通讯簿以.wab文件的形式维护了一个包含身份和联系人的数据库。虽然当前版本的 Windows默认不再使用.wab文件,但它们允许从较早安装的 Windows 通讯簿导入 WAB 文件。


有多种方法可以将 WAB 文件导入 Windows 通讯簿,但据观察,应用程序依赖于 Windows 联系人导入工具(即C:Program FilesWindows Mailwabmig.exe)来导入通讯簿。导入工具加载wab32.dll以处理加载 WAB 文件、提取相关联系人并将其导入 Windows 通讯簿。


WAB 文件格式

WAB 文件格式(通常称为 Windows 通讯簿或 Outlook 通讯簿)是一种未记录的专有文件格式,其中包含个人身份。身份又可能包含联系人,每个联系人可能包含一个或多个属性。


尽管该格式未记录,但文件格式已由第三方进行了部分逆向工程。以下结构是从公开可用的第三方应用程序和wab32.dll的反汇编中获得的。因此,结构定义、字段名称和字段类型可能不准确。


WAB 文件具有以下结构:

Offset      Length (bytes)    Field                   Description---------   --------------    --------------------    -------------------0x0         16                Magic Number            Sixteen magic bytes0x10        4                 Count 1                 Unknown Integer0x14        4                 Count 2                 Unknown Integer0x18        16                Table Descriptor 1      Table descriptor0x28        16                Table Descriptor 2      Table descriptor0x38        16                Table Descriptor 3      Table descriptor0x48        16                Table Descriptor 4      Table descriptor0x58        16                Table Descriptor 5      Table descriptor0x68        16                Table Descriptor 6      Table descriptor

除非另有说明,否则所有多字节字段都以小端字节序表示。所有字符串字段均采用 Unicode,以 UTF16-LE 格式编码。


该幻数字段包含下列16个字节:9C CB CB 8D 13 75 D2 11 91 58 00 C0 79 4F 56 A4。虽然一些来源将字节序列81 32 84 C1 85 05 D0 11 B2 90 00 AA 00 3C F6 76列为 WAB 文件的有效幻数,但实验发现替换字节序列可以防止 Windows 地址簿处理文件。


编号为 1 到 6的六个表描述符字段中的每一个都具有以下结构:

Offset    Length    Field    Description          (bytes)-------   --------  -------  -------------------0x0       4         Type     Type of table descriptor0x4       4         Size     Size of the record described0x8       4         Offset   Offset of the record described relative to the beginning of file0xC       4         Count    Number of records present at offset

以下是一些已知类型的表描述符的示例:

  • 文本记录(类型:0x84d0):包含 Unicode 字符串的记录。

  • 索引记录(类型:0xFA0):可能包含多个 WAB 记录描述符的记录。

每个文本记录具有以下结构:

Offset   Length (bytes)    Field          Description------   --------------    ------------   -------------------0x0      N                 Content        Text content of the record; a null terminated UNICODE string0x0+N    0x4               RecordId       A record identifier for the text record

同样,每条索引记录的结构如下

Offset      Length (bytes)    Field        Description---------   --------------    ----------   -------------------0x0         4                 RecordId     A record identifier for the index record0x4         4                 Offset       Offset of the record relative to the beginning of the file

索引记录中的每个条目(即,连续的每个索引记录结构)都有一个指向 WAB 记录的偏移量。


WAB 记录

WAB 记录用于描述联系人。它包含存储在属性中的电子邮件地址和电话号码等字段,这些字段可能是各种类型,例如字符串、整数、GUID 和时间戳。每个 WAB 记录具有以下结构:

Offset      Length   Field              Description---------   ------   ---------------    -------------------0x0         4        Unknown1           Unknown field0x4         4        Unknown2           Unknown field0x8         4        RecordId           A record identifier for the WAB record0xC         4        PropertyCount      The number of properties contained in RecordProperties0x10        4        Unknown3           Unknown field 0x14        4        Unknown4           Unknown field 0x18        4        Unknown5           Unknown field 0x1C        4        DataLen            The length of the RecordProperties field (M)0x20        M        RecordProperties   Succession of subproperties belonging to the WAB record

以下字段是相关的:

  • 该RecordProperties场纪录产权结构的继承。

  • 所述PropertyCount字段指示内属性的数目RecordProperties字段。

记录属性可以是简单的或复合的。


简单属性

简单属性具有以下结构:

Offset      Length (bytes)    Field       Description---------   --------------    ---------   -------------------0x0         0x2               Tag         A property tag describing the type of the contents0x2         0x2               Unknown     Unknown field0x4         0x4               Size        Size in bytes of Value member (X)0x8         X                 Value       Property value or content

简单属性的标签小于 0x1000,包括以下内容:

Tag Name        Tag Value    Length      Description                             (bytes)---------       -----------  ---------   -------------------PtypInteger16   0x00000002   2           A 16-bit integerPtypInteger32   0x00000003   4           A 32-bit integerPtypFloating32  0x00000004   4           A 32-bit floating point numberPtypFloating64  0x00000005   8           A 64-bit floating point numberPtypBoolean     0x0000000B   2           Boolean, restricted to 1 or 0PtypString8     0x0000001E   Variable    A string of multibyte characters in externally specified                                         encoding with terminating null character (single 0 byte)PtypBinary      0x00000102   Variable    A COUNT field followed by that many bytesPtypString      0x0000001F   Variable    A string of Unicode characters in UTF-16LE format encodingwith terminating null character (0x0000).PtypGuid        0x00000048   16          A GUID with Data1, Data2, and Data3 filds in little-endianPtypTime        0x00000040   8           A 64-bit integer representing the number of 100-nanosecond                                         intervals since January 1, 1601PtypErrorCode   0x0000000A   4           A 32-bit integer encoding error information

请注意以下事项:

  • 上述清单并非详尽无遗。有关更多属性标记定义,请参阅此。

  • PtypBinary的值以 COUNT 字段为前缀,该字段对 16 位字进行计数。

  • 除上述之外,还存在以下属性;它们在 WAB 中的用法未知。

  • PtypEmbeddedTable (0x0000000D):属性值是一个组件对象模型 (COM) 对象。

  • PtypNull (0x00000001):无:此属性是占位符。

  • PtypUnspecified (0x00000000): Any:此属性类型值匹配任何类型;


复合属性

复合属性具有以下结构:


Offset  Length     Field             Description        (bytes)------  ---------  ----------------- -------------------0x0     0x2        Tag               A property tag describing the type of the contents0x2     0x2        Unknown           Unknown field0x4     0x4        NestedPropCount   Number of nested properties contained in the current WAB property0x8     0x4        Size              Size in bytes of Value member (X)0xC     X          Value             Property value or content

复合属性的标签大于等于0x1000,包括以下内容:


Tag Name                Tag Value---------               ----------PtypMultipleInteger16   0x00001002PtypMultipleInteger32   0x00001003PtypMultipleString8     0x0000101EPtypMultipleBinary      0x00001102PtypMultipleString      0x0000101FPtypMultipleGuid        0x00001048PtypMultipleTime        0x00001040

每个复合属性的Value字段包含NestedPropCount数量的相应类型的 Simple 属性。


在固定大小的属性(的情况下PtypMultipleInteger16,PtypMultipleInteger32,PtypMultipleGuid,和PtypMultipleTime),该值的复合属性的字段包含NestedPropCount所述的数值对应的简单属性的字段。


例如,在NestedPropCount为 4的PtypMultipleInteger32结构中:

  • 的大小始终是16。

  • 该值包含四个 32 位整数。


对于可变大小的属性(PtypMultipleString8、PtypMultipleBinary和PtypMultipleString),复合属性的Value字段包含NestedPropCount数量的Size和相应 Simple 属性的Value字段。


例如,在包含 Unicode 字符串“foo”和“bar”的NestedPropCount为 2的PtypMultipleString结构中:

  • 该尺寸是14 00 00 00。

  • Value 字段包含以下两个字节字符串的串联:

  • “foo”用四字节长度编码:06 00 00 00 66 00 6f 00 6f 00。

  • “bar”用四字节长度编码:06 00 00 00 62 00 61 00 72 00。


技术细节

当导入 WAB 文件形式的格式错误的 Windows 通讯簿时,会出现有问题的漏洞。当用户尝试将 WAB 文件导入 Windows 通讯簿时,方法WABObjectInternal::Import()被调用,该方法又调用ImportWABFile()。对于 WAB 文件中的每个联系人,ImportWABFile()执行以下嵌套调用:ImportContact()、CWABStorage::ReadRecord()、ReadRecordWithoutLocking(),最后是HrGetPropArrayFromFileRecord()。后一个函数接收一个指向文件的指针作为参数并读取联系人标题并提取PropertyCount和DataLen. 函数HrGetPropArrayFromFileRecord()依次调用SecurityCheckPropArrayBuffer()对导入的文件执行安全检查,并调用 HrGetPropArrayFromBuffer()将联系人属性读入属性数组。


函数HrGetPropArrayFromBuffer()严重依赖于SecurityCheckPropArrayBuffer()执行的检查的正确性。但是,该函数无法对某些属性类型实施安全检查。具体来说,SecurityCheckPropArrayBuffer()可能会跳过检查属性标记未知的嵌套属性的内容,而函数HrGetPropArrayFromBuffer()继续处理所有嵌套属性,而不管安全检查如何。因此,可以欺骗函数HrGetPropArrayFromBuffer()解析未经检查的联系人属性。作为解析这样一个属性的结果,函数HrGetPropArrayFromBuffer() 可以被欺骗使堆缓冲区溢出。


代码分析

以下代码块显示了与此漏洞相关的方法的受影响部分。代码片段由[N]表示的引用标记划分。与此漏洞无关的行将替换为[截断]标记。


以下是函数HrGetPropArrayFromFileRecord的伪代码:

if ( !(unsigned int)SecurityCheckPropArrayBuffer(wab_buffer_full, HIDWORD(uBytes[1]), wab_buffer[3]) )  {
[2] result = 0x8004011b; // Errorgoto LABEL_25; // Return prematurely }
[3] result = HrGetPropArrayFromBuffer(wab_buffer_full, HIDWORD(uBytes[1]), wab_buffer[3], 0, a7);

在 [1] 处,调用函数SecurityCheckPropArrayBuffer()对接收到的缓冲区和其中包含的属性执行一系列安全检查。如果检查结果为正,则输入是可信的,并通过在 [3] 处调用HrGetPropArrayFromBuffer()进行处理。否则,该函数将在 [2] 处返回错误。


以下是函数SecurityCheckPropArrayBuffer()的伪代码:


    __int64 __fastcall SecurityCheckPropArrayBuffer(unsigned __int8 *buffer_ptr, unsigned int buffer_length, int header_dword_3){unsigned int security_check_result; // ebxunsigned int remaining_buffer_bytes; // ediint l_header_dword_3; // er15unsigned __int8 *ptr_to_buffer; // r9int current_property_tag; // ecx      __int64 c_dword_2; // r8unsigned int v9; // ediint VA; // ecxint VB; // ecxint VC; // ecxint VD; // ecxint VE; // ecxint VF; // ecxint VG; // ecxint VH; // ecxsigned __int64 res; // rax      _DWORD *ptr_to_dword_1; // rbpunsigned __int8 *ptr_to_dword_0; // r14unsigned int dword_2; // eaxunsigned int v22; // ediint v23; // esiint v24; // ecxunsigned __int8 *c_ptr_to_property_value; // [rsp+60h] [rbp+8h]unsigned int v27; // [rsp+68h] [rbp+10h]unsigned int copy_dword_2; // [rsp+70h] [rbp+18h]
security_check_result = 0; remaining_buffer_bytes = buffer_length; l_header_dword_3 = header_dword_3; ptr_to_buffer = buffer_ptr;if ( header_dword_3 ) {while ( remaining_buffer_bytes > 4 ) {
[4]
if ( *(_DWORD *)ptr_to_buffer & 0x1000 ) {
[5]
current_property_tag = *(unsigned __int16 *)ptr_to_buffer;if ( current_property_tag == 0x1102 || (unsigned int)(current_property_tag - 0x101E) <= 1 ) {
[6]
ptr_to_dword_1 = ptr_to_buffer + 4; ptr_to_dword_0 = ptr_to_buffer;if ( remaining_buffer_bytes < 0xC ) return security_check_result; dword_2 = *((_DWORD *)ptr_to_buffer + 2); v22 = remaining_buffer_bytes - 0xC;if ( dword_2 > v22 ) return security_check_result; ptr_to_buffer += 12; copy_dword_2 = dword_2; remaining_buffer_bytes = v22 - dword_2; c_ptr_to_property_value = ptr_to_buffer; v23 = 0; if ( *ptr_to_dword_1 > 0u ) {while ( (unsigned int)SecurityCheckSingleValue( *(_DWORD *)ptr_to_dword_0, &c_ptr_to_property_value, ©_dword_2) ) {if ( (unsigned int)++v23 >= *ptr_to_dword_1 ) { ptr_to_buffer = c_ptr_to_property_value;goto LABEL_33; } }return security_check_result; } }else {
[7]
if ( remaining_buffer_bytes < 0xC )return security_check_result; c_dword_2 = *((unsigned int *)ptr_to_buffer + 2); v9 = remaining_buffer_bytes - 12;if ( (unsigned int)c_dword_2 > v9 ) return security_check_result; remaining_buffer_bytes = v9 - c_dword_2; VA = current_property_tag - 0x1002; if ( VA ) { VB = VA - 1;if ( VB && (VC = VB - 1) != 0 ) { VD = VC - 1;if ( VD && (VE = VD - 1) != 0 && (VF = VE - 1) != 0 && (VG = VF - 13) != 0 && (VH = VG - 44) != 0 ) res = VH == 8 ? 16i64 : 0i64;else res = 8i64; }else { res = 4i64; } }else { res = 2i64; }if ( (unsigned int)c_dword_2 / *((_DWORD *)ptr_to_buffer + 1) != res ) return security_check_result;

ptr_to_buffer += c_dword_2 + 12; } }else {
[8]
if ( remaining_buffer_bytes < 4 ) return security_check_result; v24 = *(_DWORD *)ptr_to_buffer; c_ptr_to_property_value = ptr_to_buffer + 4;// new exe: v13 = buffer_ptr + 4; v27 = remaining_buffer_bytes - 4; if ( !(unsigned int)SecurityCheckSingleValue(v24, &c_ptr_to_property_value, &v27) )return security_check_result; remaining_buffer_bytes = v27; ptr_to_buffer = c_ptr_to_property_value; } LABEL_33:if ( !--l_header_dword_3 )break; } }if ( !l_header_dword_3 ) security_check_result = 1;return security_check_result; }

在 [4] 处检查正在处理的属性的标记。执行的检查取决于在每次迭代中处理的属性是简单属性还是复合属性。对于简单的属性(即标签低于 0x1000 的属性),在 [8] 处继续执行。对简单属性执行以下检查:


  1. 如果缓冲区中的剩余字节数少于 4,则函数返回错误。

  2. 获得指向属性值的指针,并调用SecurityCheckSingleValue()对简单属性及其值执行安全检查。SecurityCheckSingleValue()执行安全检查并递增指针以指向缓冲区中的下一个属性,以便SecurityCheckPropArrayBuffer()可以在下一次迭代中检查下一个属性。

  3. 属性总数递减并与零进行比较。如果等于零,则该函数成功返回。如果不同,循环的下一次迭代将检查下一个属性。

类似地,对于复合属性(即标签等于或高于 0x1000 的属性)在 [5] 处继续执行,并完成以下操作。


对于变长复合属性(如果属性标签等于 0x1102 ( PtypMultipleBinary ) 或等于或小于 0x101f ( PtypMultipleString )),[6] 处的代码执行以下操作:

  1. 将缓冲区中剩余要读取的字节数与 0xC 进行比较,以避免溢出缓冲区。

  2. 将该属性的Size字段与剩余缓冲区长度进行比较,以避免溢出缓冲区。

  3. 对于每个嵌套属性,调用函数 SecurityCheckSingleValue()。它:

  4. 对嵌套属性执行安全检查。

  5. 将指针前进到调用者持有的缓冲区,以指向下一个嵌套属性。

  6. 循环运行直到接触中的总属性数(在每次迭代中递减)为零。


对于固定长度的复合属性(如果所讨论的属性标记不同于 0x1102 (PtypMultipleBinary) 且大于 0x101f (PtypMultipleString)),则从 [7] 开始发生以下情况:

  1. 将缓冲区中剩余要读取的字节数与 0xC 进行比较,以避免溢出缓冲区。

  2. 将大小与剩余缓冲区长度进行比较,以避免溢出缓冲区。

  3. 每个嵌套属性的大小,仅取决于属性标签,是从父属性标签计算的。

  4. 的尺寸是由NestedPropCount分频以获得每个嵌套属性的大小。

  5. 如果计算的子属性大小与从父属性标记推导出的属性大小不同,则该函数返回错误。

  6. 缓冲区指针按父属性值的大小递增以指向下一个属性。

  7. 未知或不可处理的属性类型被分配嵌套的属性大小 0x0。


据观察,如果计算出的属性大小为零,则缓冲区指针前移属性值的大小,如标题所述。无论属性大小如何,缓冲区都会提前,通过提前缓冲区,安全检查允许父属性(可能包括子属性)的值保持未检查状态。要使安全检查通过在步骤 4 中对固定长度复合属性执行的除法结果必须为零。因此,要使未知或不可处理的属性通过安全检查,NestedPropCount必须大于Size。请注意,由于任何属性的大小(以字节为单位)至少为 2,因此NestedPropCount必须始终不大于Size 的一半,因此,上述除法在良性情况下绝不能为零。


检查结束后,该函数返回零表示检查失败,返回一表示检查通过。


随后,函数HrGetPropArrayFromFileRecord()调用HrGetPropArrayFromBuffer(),其目的是将属性收集到_SPropValue结构数组中,并将其返回给调用者。该_SPropValue阵列具有长度等于属性的数量(由接触头所给出),并且通过一个呼叫在堆中被分配给LocalAlloc() 。属性的数量乘以sizeof(_SPropValue)以产生总缓冲区大小。以下片段显示了发生的分配:


if ( !property_array_r )    {        ret = -2147024809;goto LABEL_71;    }    *property_array_r = 0i64;    header_dword_3_1 = set_to_zero + header_dword_3;
[9]
if ( (unsigned int)header_dword_3_1 < header_dword_3 || (unsigned int)header_dword_3_1 > 0xAAAAAAA || (v10 = (unsigned int)header_dword_3_1, property_array = (struct _SPropValue *)LocalAlloc(0x40u,0x18 * header_dword_3_1),// sizeof(_SPropValue) * n_properties_in_binary (*property_array_r = property_array) == 0i64) ) { ERROR_INSUFICIENT_MEMORY: ret = 0x8007000E;goto LABEL_71; }

可以在 [9] 中观察到sizeof(_SPropValue) * n_properties_in_binary的分配。紧接着,每个属性结构都被初始化,并且它们的属性标记成员被设置为 1。初始化后,已经对其执行安全检查的缓冲区被逐个属性处理,将属性推进到下一个属性的指针使用相关属性提供的属性标题和值大小。


如果特定循环迭代处理的属性是简单属性,则执行如下代码:


 

if ( !_bittest((const signed int *)¤t_property_tag, 0xCu) ){if ( v16 < 4 )break;dword_1 = wab_ulong_buffer_full[1];ptr_to_dword_2 = (char *)(wab_ulong_buffer_full + 2);v38 = v16 - 4;if ( (unsigned int)dword_1 > v38 )break;current_property_tag = (unsigned __int16)current_property_tag;if ( (unsigned __int16)current_property_tag > 0xBu ){
[10]
v39 = current_property_tag - 0x1E;if ( !v39 )goto LABEL_79;v40 = v39 - 1;if ( !v40 )goto LABEL_79;v41 = v40 - 0x21;if ( !v41 )goto LABEL_56;v42 = v41 - 8;if ( v42 ){if ( v42 != 0xBA )goto LABEL_56;v43 = dword_1;(*property_array_r)[p_idx].Value.bin.lpb = (LPBYTE)LocalAlloc(0x40u, dword_1);if ( !(*property_array_r)[p_idx].Value.bin.lpb )goto ERROR_INSUFICIENT_MEMORY;(*property_array_r)[p_idx].Value.l = dword_1;v44 = *(&(*property_array_r)[p_idx].Value.at + 1);}else{LABEL_79:
[11]
v43 = dword_1;(*property_array_r)[p_idx].Value.cur.int64 = (LONGLONG)LocalAlloc(0x40u, dword_1);v44 = (*property_array_r)[p_idx].Value.dbl;if ( v44 == 0.0 )goto ERROR_INSUFICIENT_MEMORY;}memcpy_0(*(void **)&v44, ptr_to_dword_2, v43);wab_ulong_buffer_full = (ULONG *)&ptr_to_dword_2[v43];}else{LABEL_56:
[12]
memcpy_0(&(*property_array_r)[v15].Value, ptr_to_dword_2, dword_1);wab_ulong_buffer_full = (ULONG *)&ptr_to_dword_2[dword_1];}remaining_bytes_to_process = v38 - dword_1;goto NEXT_PROPERTY;}
[Truncated]
NEXT_PROPERTY:++p_idx;processed_property_count = (unsigned int)(processed_property_count_1 + 1);processed_property_count_1 = processed_property_count;if ( (unsigned int)processed_property_count >= c_header_dword_3 )return 0;}

在[10]中,属性标签被提取出来并与几个常量进行比较。如果属性标记是 0x1e ( PtypString8 )、0x1f ( PtypString ) 或 0x48 ( PtypGuid ),则在 [11] 处继续执行。如果属性标记为 0x40 ( PtypTime ) 或未被识别,则在 [12] 处继续执行。[12] 中的memcpy调用容易发生堆溢出。


反之,如果特定循环迭代中处理的属性不是简单的属性,则执行以下代码。值得注意的是,当执行以下代码时,指针DWORD* wab_ulong_buffer_full指向正在处理的属性的属性标记。无论正在处理哪个复合属性,在识别属性标记之前,缓冲区都会前进以指向属性值的开头,即第 4 个 32 位整数。


[13]
if ( v16 < 4 )break; c_dword_1 = wab_ulong_buffer_full[1]; v19 = v16 - 4;if ( v19 < 4 )break; dword_2 = wab_ulong_buffer_full[2]; wab_ulong_buffer_full += 3;
remaining_bytes_to_process = v19 - 4;
[14]
if ( (unsigned __int16)current_property_tag >= 0x1002u ) {if ( (unsigned __int16)current_property_tag <= 0x1007u || (unsigned __int16)current_property_tag == 0x1014 )goto LABEL_80;if ( (unsigned __int16)current_property_tag == 0x101E ) { [Truncated]
}if ( (unsigned __int16)current_property_tag == 0x101F ) { [Truncated] }if ( ((unsigned __int16)current_property_tag - 0x1040) & 0xFFFFFFF7 ) {if ( (unsigned __int16)current_property_tag == 0x1102 ) { [Truncated] } }else { LABEL_80:
[15]
(*property_array_r)[p_idx].Value.bin.lpb = (LPBYTE)LocalAlloc(0x40u, dword_2);if ( !(*property_array_r)[p_idx].Value.bin.lpb )goto ERROR_INSUFICIENT_MEMORY; (*property_array_r)[p_idx].Value.l = c_dword_1;if ( (unsigned int)dword_2 > remaining_bytes_to_process )break; memcpy_0((*property_array_r)[p_idx].Value.bin.lpb, wab_ulong_buffer_full, dword_2); wab_ulong_buffer_full = (ULONG *)((char *)wab_ulong_buffer_full + dword_2); remaining_bytes_to_process -= dword_2; } }
NEXT_PROPERTY: ++p_idx; processed_property_count = (unsigned int)(processed_property_count_1 + 1); processed_property_count_1 = processed_property_count;if ( (unsigned int)processed_property_count >= c_header_dword_3 )return 0; }

在 [13] 处推进缓冲区后,将属性标记与 [14] 处开始的几个常量进行比较。最后,[15] 中的代码片段尝试处理一个复合属性(即 >= 0x1000),其中包含一个先前常量未考虑的标记。


虽然每种属性的处理逻辑无关,但一个有趣的事实是,如果属性标签不被识别,缓冲区指针仍然前进到其头部的末尾,并且永远不会被收回。如果满足以下所有条件,就会发生这种情况:

  1. 属性标签大于或等于 0x1002。

  2. 属性标签大于 0x1007。

  3. 属性标签与 0x1014 不同。

  4. 属性标签与 0x101e 不同。

  5. 属性标签与 0x101f 不同。

  6. 属性标签与 0x1102 不同。

  7. 从属性标记中减去 0x1040,然后将结果与 0xFFFFFFF7 按位 AND 的结果是非零的。


有趣的是,如果满足上述所有条件,则跳过复合属性的属性头,下一次循环迭代会将其属性体解释为不同的属性。


因此,可以通过使用以下观察来溢出HrGetPropArrayFromBuffer()在堆中分配的_SPropValue数组:

  • 如果NestedPropCount大于Size ,则可以制作特制的复合未知或不可处理的属性来绕过安全检查。

  • 可以使HrGetPropArrayFromBuffer()将特制属性的值解释为单独的属性。


概念验证

为了从良性 WAB 文件创建恶意 WAB 文件,请从 Windows 通讯簿的实例中导出有效的 WAB 文件。请注意,Windows XP 上的 Outlook Express 包括将联系人导出为 WAB 文件的功能。


可以修改良性 WAB 文件以使其成为恶意文件,方法是将其中的联系人更改为具有以下特征:


包含以下内容的嵌套属性:

  1. 未知或不可处理类型的标记,例如标记 0x1058,具有以下条件:

  2. 必须大于或等于 0x1002。

  3. 必须大于 0x1007。

  4. 必须不同于 0x1014、0x101e、0x101f 和 0x1102。

  5. 从属性标签中减去 0x1040,然后将结果与0xFFFFFFF7按位 AND 的结果是非零的。

  6. 必须不同于 0x1002、0x1003、0x1004、0x1005、0x1006、0x1007、0x1014、0x1040 和 0x1048。

  7. NestedPropCount大于Size。

  8. 该值的复合属性是空的。

  9. 包含以下内容的恶意简单属性:

  10. 不同于 0x1e、0x1f、0x40 和 0x48 的属性标签。例如,标签 0x0。

  11. 的尺寸值大于为0x18 X NestedPropCount以便溢出_SPropValue阵列缓冲器。

  12. 未指定数量的尾随字节,将溢出_SPropValue数组缓冲区。


最后,当攻击者诱骗毫无戒心的用户导入特制的 WAB 文件时,就会触发漏洞并实现代码执行。失败的利用尝试很可能会导致 Windows 地址簿导入工具崩溃。


由于存在 ASLR 且缺少脚本引擎,我们无法通过此漏洞在 Windows 10 中获得任意代码执行。


结论

希望您喜欢深入了解 CVE-2021-24083,如果您喜欢,请继续查看我们关于Adobe Acrobat Reader DC 中释放后使用漏洞的另一篇博文。如果您还没有,请务必在Twitter 上关注我们以了解我们的最新工作。快乐黑客!

【漏洞分析】MicrosoftWindows通讯簿中堆缓冲区溢出漏洞分析

本文始发于微信公众号(Ots安全):【漏洞分析】Microsoft Windows 通讯簿中堆缓冲区溢出漏洞分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月16日23:41:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【漏洞分析】MicrosoftWindows通讯簿中堆缓冲区溢出漏洞分析http://cn-sec.com/archives/449756.html

发表评论

匿名网友 填写信息