目录

CVE-2017-11771 Windows Search 堆溢出漏洞简单分析

CVE-2017-11771 Windows Search 堆溢出漏洞简单分析

一、漏洞信息

1. 漏洞简述

  • 漏洞名称:Windows Search 堆溢出漏洞
  • 漏洞编号:CVE-2017-11771;Bugtraq ID:101114
  • 漏洞类型:Remote Code Execution

2. 组件概述

Windows搜索是一个桌面搜索平台,具有针对大多数常见文件类型和数据类型的即时搜索功能。 它的主要组件是WSearch Windows Service,它负责索引,组织和提取有关本地文件系统的信息。 此外,它实现了通用搜索服务(GSS),它是向搜索查询提供结果所需的后端功能。 客户端使用Windows搜索协议(WSP)向托管GSS的服务器发出查询。 WSP依靠名为管道协议的服务器消息块(SMB)进行消息传输和身份验证。

Microsoft Windows的所有版本均附带服务器消息块(SMB)协议的实现。 SMB是本机Windows网络框架,支持文件共享,网络打印,远程过程调用和其他功能。在Windows系统上,SMB协议通过附加的安全性,文件和磁盘管理支持扩展了CIFS协议。 通过各种SMB命令和子命令类型提供这些功能。

3. 漏洞利用

Windows Search服务在处理内存中的对象时存在缺陷,会造成堆溢出。远程未经过认证的攻击者可以通过向目标主机发起一个恶意请求实现任意代码执行,成功的攻击可以获得目标主机的SYSTEM权限。

4. 漏洞影响版本

• Microsoft Windows 7 • Microsoft Windows 8 • Microsoft Windows 8.1 • Microsoft Windows 10 • Microsoft Windows Server 2003 • Microsoft Windows Server 2008 • Microsoft Windows Server 2012 • Microsoft Windows Server 2016 • Microsoft Windows Vista • Microsoft Windows XP

5. 解决方案

获取微软官方针对此漏洞的安全补丁,地址: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2017-11771

二、漏洞复现

暂无

1. 环境搭建

2. 复现过程

三、漏洞分析

1. 漏洞基本信息

  • 漏洞文件:tquery.dll
  • 漏洞函数:CFixedVarBufferAllocator::CFixedVarBufferAllocator()
  • 漏洞参数:cbReadBuffer and cbReserved
  • 漏洞对象:堆分配的buffer

2. 背景知识

备注:此处略去SMB相关介绍,漏洞自身与SMB关系不大,SMB只是作为WSP传输的工具协议,使用的pipe名称为MsFteWds。

Windows Search Protocol(WSP)

使用WSP的最小搜索查询其流程大概如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[ Client ] --------------------> [ Server ] - CPMConnectIn
[ Client ] <-------------------- [ Server ] - CPMConnectOut
[ Client ] --------------------> [ Server ] - CPMCreateQueryIn
[ Client ] <-------------------- [ Server ] - CPMCreateQueryOut
[ Client ] --------------------> [ Server ] - CPMSetBindingsIn request
[ Client ] <-------------------- [ Server ] - CPMSetBindingsIn response
[ Client ] --------------------> [ Server ] - CPMGetRowsIn
[ Client ] <-------------------- [ Server ] - CPMGetRowsOut
[ Client ] --------------------> [ Server ] - CPMGetFreeCursorIn
[ Client ] <-------------------- [ Server ] - CPMGetFreeCursorOut
[ Client ] --------------------> [ Server ] - CPMDisconnect

CPMConnectIn消息开始于客户端和服务器之间的会话,CPMCreateQueryIn包含查询条件并创建新查询,CPMSetBindingsIn指定如何在CPMGetRowsOut中构建搜索结果,CPMGetRowsIn从服务器返回的查询结果中请求数据。

所有的WSP消息以一个16字节的头部开始,其结构如下:

1
2
3
4
5
6
Offset 	Size (bytes) 	Field
--------------------------------------------
0x00 	0x4 			_msg
0x04 	0x4 			_status
0x08 	0x4 			_ulChecksum
0x0c 	0x4 			_ulReserved2

_msg字段标识标头部后面的消息类型(有的一个value表示两种类型,此时要根据数据流的传输方向判定具体代表哪种类型。带"In"字符的是从client到server,带"Out"字符的是从server到client);
_status字段表明所请求操作的状态,由服务器填充;
_ulChecksum包含从_ulReserved2字段后面开始的消息的校验和;
_ulReserved2字段除了后续的消息为CPMGetRowsIn之外,都必须设置为0。

与本漏洞相关的是CPMGetRowsIn消息。该消息主要用于从查询中请求数据行(row),其详细格式如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Offset 		Size (bytes) 		Field
------------------------------------------
0x00 		0x4 				hCursor
0x04 		0x4 				cRowsToTransfer
0x08 		0x4 				cbRowWidth
0x0c 		0x4 				cbSeek
0x10 		0x4 				cbReserved
0x14 		0x4 				cbReadBuffer
0x18 		0x4 				ulClientBase
0x1c 		0x4 				fBwdFetch
0x20 		0x4					eType
0x24 		0x4 				chapt
0x28 		variable 			 SeekDescription

cRowsToTransfer字段指定CPMGetRowsOut消息中包括多少row,cbRowWidth字段表示row的长度(以字节为单位),cbReserved字段指定结果在CPMGetRowsOut消息中的偏移量(与cbSeek字段相加然后进行计算偏移),cbReadBuffer字段指定 CPMGetRowsOut消息中的数据大小,以字节为单位,该字段必须设置为_cbRowWidth值的最大值或_cRowsToTransfer值的1000倍,四舍五入到最接近的512字节倍数。 该值不得超过0x00004000。

3. 详细分析

server接收到CPMGetRowsIn消息后,首先检查cbReserved的值是否小于cbReadBuffer。然后调用CFixedVarBufferAllocator::CFixedVarBufferAllocator()来初始化buffer,该buffer中应该有相应的CPMGetRowsOut消息。该函数会使用下面的方式对结果数据进行初始位置和结束位置的计算:

1
2
resultDataStart = buffer + cbReversed
resultDataEnd = buffer + cbReadBuffer

如果计算出的resultDataEnd不是8字节对齐的,则函数一次从resultDataEnd减去一个字节,直到对齐为止。

但是,在返回分配的buffer前,函数没有对resultDataEnd是否大于等于resultDataStart进行验证,直接调用了CFixedVarBufferAllocator::CFixedVarBufferAllocator()函数进行初始化buffer的chunk分配。AllocFixed()函数使用下面的方式来确认buffer中是否有足够空间:

1
resultDataEnd - resultDataStart < cbRowWidth

因为CFixedvarBufferAllocator()没有进行两个字段大小的比较,所以有可能会出现resultDataEnd小于resultDataStart的情况,这样当两个字段进行减法运算的时候就会造成溢出。然后会调用AllocFixed()函数进行内存分配,但是此时的buffer的offset有可能是错误的。当row被复制到分配的chunk时,数据会被写到buffer的末尾,最终导致一个堆溢出。

4. 源码分析

 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
# CRequestServer::DoGetRows():
.text:62A6912A mov eax, [esi+20h] ; cbReserved
.text:62A6912D mov edi, [esi+24h] ; cbReadBuffer
[......]
.text:62A6915F cmp eax, edi
.text:62A69161 jb loc_62AA21B3 ; ensure cbReserved < cbReadBuffer
[......]
.text:62A691DE push [ebp+cbReserved] ; cbReserved
.text:62A691E1 lea ecx, [ebp+fixedVarBuffer] ; this
.text:62A691E7 push ebx ; cbRowWidth
.text:62A691E8 push edi ; cbReadBuffer
.text:62A691E9 push [ebp+clientBase] ; clientBase
.text:62A691EC push esi ; base of message
.text:62A691ED call ??0CFixedVarBuff... ; CFixedVarBufferAllocator()


# CFixedVarBufferAllocator::CFixedVarBufferAllocator():
.text:62A68413 mov ecx, [ebp+msgBase_arg0]
.text:62A68416 push ebx
.text:62A68417 mov dword ptr [eax+8], offset ??_7PFixedA...
.text:62A6841E push esi
.text:62A6841F mov esi, [ebp+cbReserved_arg10]
.text:62A68422 mov [eax+14h], edx
.text:62A68425 lea edx, [esi+ecx] ; resultDataStart
.text:62A68428 mov [eax+18h], edx
.text:62A6842B mov edx, [ebp+cbReadBuffer_arg8]
.text:62A6842E xor ebx, ebx
.text:62A68430 add edx, ecx ; resultDataEnd
.text:62A68432 test ecx, ecx
.text:62A68434 push edi
.text:62A68435 mov edi, [ebp+cbRowWidth_argC]
.text:62A68438 setnz bl
.text:62A6843B mov [eax+20h], edi
.text:62A6843E pop edi
.text:62A6843F mov [eax+24h], esi
.text:62A68442 pop esi
.text:62A68443 mov dword ptr [eax], offset ??_7CFixedVar...
.text:62A68449 mov dword ptr [eax+8], offset ??_7CFixedV...
.text:62A68450 mov [eax+4], ebx
.text:62A68453 mov [eax+0Ch], ecx
.text:62A68456 mov [eax+10h], ecx
.text:62A68459 mov [eax+1Ch], edx
.text:62A6845C pop ebx
.text:62A6845D test dl, 7 ; check if resultDataEnd is 8-byte aligned
.text:62A68460 jnz loc_62AA20DA
.text:62A68466 pop ebp ; fails to validate resultDataEnd >= resultDataStart
.text:62A68467 retn 14h
[......]
.text:62AA20DA dec dword ptr [eax+1Ch] ; subtract resultDataEnd
.text:62AA20DD test byte ptr [eax+1Ch], 7 ; check alignment
.text:62AA20E1 jz loc_62A68466
.text:62AA20E7 jmp short loc_62AA20DA


# CFixedVarBufferAllocator::AllocFixed():
.text:62A745B0 mov edi, edi
.text:62A745B2 push ebp
.text:62A745B3 mov ebp, esp
.text:62A745B5 mov eax, [ecx+10h]
.text:62A745B8 mov edx, [ecx+18h]
.text:62A745BB sub esp, 20h
.text:62A745BE push esi
.text:62A745BF mov esi, [ecx+14h]
.text:62A745C2 sub esi, eax ; resultDataEnd - resultDataStart
.text:62A745C4 cmp edx, esi ; compare above difference to cbRowWidth
.text:62A745C6 ja loc_62AA20E9
.text:62A745CC add edx, eax ; add cbRowWidth to resultDataStart
.text:62A745CE mov [ecx+10h], edx
.text:62A745D1 pop esi
.text:62A745D2 leave
.text:62A745D3 retn

5. 攻击流量

SMB1:

https://cdn.jsdelivr.net/gh/AlexsanderShaw/BlogImages@main/img/2017-11771-ana1.png

SMB2:

https://cdn.jsdelivr.net/gh/AlexsanderShaw/BlogImages@main/img/2017-11771-ana2.png

6. PoC分析

https://cdn.jsdelivr.net/gh/AlexsanderShaw/BlogImages@main/img/ana3.png

首先进行了前期的验证工作,例如创建命名管道,建立查询连接,判断文件夹是否处于共享访问。按照查询流程依次进行数据发送,来到GetRowsIn消息:

https://cdn.jsdelivr.net/gh/AlexsanderShaw/BlogImages@main/img/ana4.png

四、漏洞检测和防御

1. 漏洞检测

暂无。

2. 漏洞防御

1. 检测思路

首先监控通过SMB建立MsFtewds命名管道的操作,然后使用byte_math检测出现漏洞的字段

2. 可能存在的风险

SMBandx命令有链式结构,可以嵌套,容易产生绕过。