目录

CVE-2019-0547 漏洞分析

CVE-2019-0547 Analyse

漏洞描述

CVE-2019-0547,一个Windows系统下DHCP客户端的任意代码执行漏洞,漏洞的主要原因是对DHCP消息的错误的处理方式造成内存损坏。攻击者可以通过构造恶意的DHCP响应数据包到存在漏洞的系统中来触发漏洞,最终可以实现以管理员权限执行任意代码,危害巨大。

漏洞影响范围

• Microsoft Windows10 version1803
• Microsoft Windows Serverversion 1803(ServerCoreInstallation)

该漏洞影响的系统版本只有两个,但是随着系统版本迭代,现在使用Windows 10的人越来越多,这个漏洞还是需要关注的。

漏洞基本信息

漏洞触发文件:DHCP服务主机上运行的dcpcore.dll 漏洞触发函数:dhcpcore!DecodeDomainSearchListData() 漏洞触发数据对象:一个原本用于存储域搜索结果的堆缓冲区

漏洞分析

基础知识

动态主机配置协议(DHCP),主要用于集中管理和自动化网络上IP地址的分配。 它是BOOTP协议的扩展。 除了IP地址分配外,DHCP客户端还从DHCP服务器接收管理其网络配置所需的信息,包括子网掩码,网关地址,DNS服务器地址等。 DHCP使用UDP端口67和68进行通信。 DHCP在所有现代操作系统上都是标准的-并且默认情况下已对网络接口启用-在Microsoft Windows上。

典型的DHCP事务流程如下:

  1. client发送DHCP DISCOVER到server
  2. server发送DHCP OFFER到client
  3. client发送DHCP REQUEST到server
  4. server响应一个DHCP ACK到client

总结上面的过程,DHCP的工作方式如下:在客户端获取IP地址之前,它会在本地网络上广播DHCP DISCOVER消息。 本地网络上的任何DHCP服务器都可以使用DHCP OFFER响应,其中包含分配给客户端的IP地址。 该IP地址通常是租用的,这意味着它会在一定时间后过期。为了续订租约,客户端向DHCP服务器发送单播DHCP REQUEST消息。 DHCP服务器以DHCP ACK消息响应。
所有DHCP message均以通用的报头结构开头。所有多字节值均以网络字节顺序排列。 该结构描述如下[1]:

offset size value
0x0000 1 Operation code (1 - request, 2 - response)
0x0001 1 Hardware type (1 - Ethernet)
0x0002 1 Hardware address length (usually 6 for Ethernet)
0x0003 1 Hops
0x0004 4 Transaction ID
0x0008 2 Time since client started
0x000A 2 Flags
0x000C 4 Client IP address if assigned
0x0010 4 Client IP address
0x0014 4 Next server IP address
0x0018 4 Relay IP address
0x001C 16 Client hardware address
0x002C 64 Server hostname (optional)
0x006C 128 Boot file name
0x00EC 4 Magic cookie (0x63 0x82 0x53 0x63)
0x00F0 variable Options

通用标头的长度是固定的,但是后面可以跟可变长度的DHCP选项。 每个单独的DHCP选项具有以下格式:

Offset Size Value
0000 1 Option tag
0001 1 Option length (len)
0002 len Option data

除了IP地址(包含在“客户端IP地址”字段中)之外,DHCP message还使用“option”来包括其他几个配置参数,例如“子网掩码”(option tag:1),“路由器”(option tag:3),DNS服务器(option tag:6),NTP服务器(option tag:4),域搜索(option tag:119)。 有关标准option tag的列表,请参见[3]。

该漏洞主要与“域搜索”选项有关,该选项包含一个或多个DNS后缀,如果DNS名称不能自行解析,则客户端可以使用该后缀附加到DNS名称。 例如,考虑将分发“example.com”的DHCP服务器作为域搜索DNS后缀。 如果客户端向DNS查询“foo”,但没有收到任何DNS记录,则它将继续查询“foo.example.com”。使用此功能可避免对网络内的所有主机重复使用通用组织DNS后缀。

域搜索选项的选项数据字段包含wire format的DNS名称列表。DNS名称对一个或多个DNS标签进行编码,并以终止于零的字符结尾。DNS标签可以压缩或不压缩。未压缩的DNS标签是一字节长度的前缀的八位字节字符串。压缩标签是一个两字节的无符号整数值,其前两个最高有效位设置为1,其余位以字节为单位存储偏移量。因此,单个DNS名称可能由压缩和未压缩标签混合组成。DNS根目录“.”由单字节“\x00”表示。使用未压缩的名称编码DNS名称“example.example.com”将变成“ \x07example\x07example\x03com\x00”。可以使用压缩标签将其编码如下:“\x07example\xc0\x00\x03com\x00”。有关DNS名称的更多信息,请参见[2]。

原理分析

在Windows的DHCP客户端中存在越界写漏洞。DHCP客户端在启动时作为svchost.exe服务运行,并遵循DHCP协议来获取系统上网络接口的IP地址。当收到DHCP答复时,它将使用dhcpcore解析DHCP选项! DhcpExtractFullOptions(),当遇到域搜索选项(option tag:119)时,该调用再调用dhcpcore!DecodeDomainSearchListData()。此函数主要将wire format的DNS名称转换为基于文本的DNS名称。它遍历每个DNS名称,在堆上分配内存,解压缩遇到的任何标签,并使用memcpy()复制标签,并在标签之间插入“.”,名称之间插入","。用于存储DNS名称的已分配缓冲区大小是基于长度的字符串,并且由于DNS名称以空值结尾,因此缓冲区大小比DNS名称小1。因此,DNS名称“\x07example\x03com\x00”导致缓冲区大小为12(请注意,字符串的长度为13)。如果DHCP回复消息包含前两个字节为零的域搜索选项,则调用程序函数将它们视为两个不同名称的两个以空字符结尾的字符,并将大小为0传递给dhcpcore!DecodeDomainSearchListData(),该函数将无法正确验证,而是调用HeapAlloc分配0字节的缓冲区。然后,它继续处理两个根标签,并写入无效缓冲区,从而导致越界写入。

攻击者可以设置一个恶意的DHCP服务器并使用恶意的DHCP响应消息来响应同一网段中的DHCP请求,从而利用该漏洞。

代码分析

分析使用的dhcpcore.dll版本为10.0.17134.191。

  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
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
; dhcpcore!DecodeDomainSearchListData:

6ffcb0cc 8bff mov edi,edi 
6ffcb0ce 55 push ebp 
6ffcb0cf 8bec mov ebp,esp 
6ffcb0d1 83ec2c sub esp,2Ch 
6ffcb0d4 8bc2 mov eax,edx 
6ffcb0d6 894de4 mov dword ptr [ebp-1Ch],ecx 
6ffcb0d9 8bc8 mov ecx,eax 
6ffcb0db 8945f0 mov dword ptr [ebp-10h],eax 
6ffcb0de 53 push ebx 
6ffcb0df 8b5d0c mov ebx,dword ptr [ebp+0Ch] 
6ffcb0e2 33d2 xor edx,edx 
6ffcb0e4 c1e902 shr ecx,2 
6ffcb0e7 83c164 add ecx,64h
6ffcb0ea 56 push esi 
6ffcb0eb 33f6 xor esi,esi 
6ffcb0ed 894dd4 mov dword ptr [ebp-2Ch],ecx 
6ffcb0f0 8b4df0 mov ecx,dword ptr [ebp-10h] 
6ffcb0f3 83f802 cmp eax,2 
6ffcb0f6 57 push edi 
6ffcb0f7 8b7d14 mov edi,dword ptr [ebp+14h] 
6ffcb0fa 1bc0 sbb eax,eax 
6ffcb0fc 40 inc eax 
6ffcb0fd 8907 mov dword ptr [edi],eax ; 外层循环开始
6ffcb0ff 833f00 cmp dword ptr [edi],0 
6ffcb102 0f8498010000 je dhcpcore!DecodeDomainSearchListData+0x1d4 
6ffcb108 42 inc edx ; edx是计数器
6ffcb109 8955f4 mov dword ptr [ebp-0Ch],edx ; 第一次迭代时跳过HeapFree

6ffcb10c 83fa02 cmp edx,2 
6ffcb0fd 8907 mov dword ptr [edi],eax 
6ffcb0ff 833f00 cmp dword ptr [edi],0 ; 第二次迭代, HeapFree and HeapAlloc都发生了.
6ffcb102 0f8498010000 je dhcpcore!DecodeDomainSearchListData+0x1d4 
6ffcb108 42 inc edx 
6ffcb109 8955f4 mov dword ptr [ebp-0Ch],edx 
6ffcb10c 83fa02 cmp edx,2 
6ffcb10f 7533 jne dhcpcore!DecodeDomainSearchListData+0x78 
6ffcb111 8b7508 mov esi,dword ptr [ebp+8] 
6ffcb114 833e00 cmp dword ptr [esi],0 
6ffcb117 7413 je dhcpcore!DecodeDomainSearchListData+0x60 
6ffcb119 ff36 push dword ptr [esi] 
6ffcb11b 6a00 push 0 
6ffcb11d ff35580dff6f push dword ptr [dhcpcore!DhcpGlobalHeap] 
6ffcb123 ff155c21ff6f call dword ptr [dhcpcore!_imp__HeapFree] 
6ffcb129 832600 and dword ptr [esi],0 ; [ebx]是该函数的参数。 DNS名称的累积长度; 排除终止空字节.
6ffcb12c ff33 push dword ptr [ebx] ; dwBytes (size) In malicious case ebx = 0 6ffcb12e 6a08 push 8 ; dwFlags 
6ffcb130 ff35580dff6f push dword ptr [dhcpcore!DhcpGlobalHeap] ; hHeap 
6ffcb136 ff155021ff6f call dword ptr [dhcpcore!_imp__HeapAlloc] ; HeapAlloc 0 bytes 6ffcb13c 8b55f4 mov edx,dword ptr [ebp-0Ch] 
6ffcb13f 8bf0 mov esi,eax ; esi = buffer allocated.size不能为0
6ffcb141 8b4df0 mov ecx,dword ptr [ebp-10h] 
6ffcb144 8b4510 mov eax,dword ptr [ebp+10h] 
6ffcb147 832300 and dword ptr [ebx],0 
6ffcb14a 8365f800 and dword ptr [ebp-8],0 
6ffcb14e 832000 and dword ptr [eax],0 
6ffcb151 33c0 xor eax,eax 
6ffcb153 8945ec mov dword ptr [ebp-14h],eax 
6ffcb156 8845ff mov byte ptr [ebp-1],al 
6ffcb159 85c9 test ecx,ecx 
6ffcb15b 0f8419010000 je dhcpcore!DecodeDomainSearchListData+0x1ae ;内层循环开始
6ffcb161 8b07 mov eax,dword ptr [edi] 
6ffcb163 85c0 test eax,eax 
6ffcb165 0f840c010000 je dhcpcore!DecodeDomainSearchListData+0x1ab 
6ffcb16b 8365e800 and dword ptr [ebp-18h],0 
6ffcb16f 8b4dec mov ecx,dword ptr [ebp-14h] 
6ffcb172 8b5de4 mov ebx,dword ptr [ebp-1Ch] 
6ffcb175 03d9 add ebx,ecx 
6ffcb177 895de0 mov dword ptr [ebp-20h],ebx 
6ffcb17a 8a1b mov bl,byte ptr [ebx] 
6ffcb17c 885dfe mov byte ptr [ebp-2],bl 
6ffcb17f 84db test bl,bl 
6ffcb181 8b5d0c mov ebx,dword ptr [ebp+0Ch] ; 第一次迭代时跳转.
6ffcb184 0f8492000000 je dhcpcore!DecodeDomainSearchListData+0x150
	6ffcb21c 807dff00 cmp byte ptr [ebp-1],0 
	6ffcb220 7605 jbe dhcpcore!DecodeDomainSearchListData+0x15b 
	6ffcb222 8b4510 mov eax,dword ptr [ebp+10h] 
	..........
	6ffcb267 8b45ec mov eax,dword ptr [ebp-14h] 
	6ffcb26a 8b55f4 mov edx,dword ptr [ebp-0Ch] 
	6ffcb26d 3bc1 cmp eax,ecx ; 跳转到第一次迭代的内层循环的开始位置
	6ffcb26f 0f82ecfeffff jb dhcpcore!DecodeDomainSearchListData+0x95 
	; 继续第二次迭代
	6ffcb275 eb03 jmp dhcpcore!DecodeDomainSearchListData+0x1ae 
	6ffcb277 8b45ec mov eax,dword ptr [ebp-14h] 
	6ffcb27a 8b55f4 mov edx,dword ptr [ebp-0Ch] 
	6ffcb27d 3bc1 cmp eax,ecx 
	6ffcb27f 7405 je dhcpcore!DecodeDomainSearchListData+0x1ba 
	6ffcb281 832700 and dword ptr [edi],0 
	6ffcb284 eb0c jmp dhcpcore!DecodeDomainSearchListData+0x1c6 
	6ffcb286 83fa02 cmp edx,2 
	6ffcb289 750a jne dhcpcore!DecodeDomainSearchListData+0x1c9 
	6ffcb28b 8b03 mov eax,dword ptr [ebx] ;发生越界写,程序可能不会在这里崩溃,因为单个字节被写入esi-1,这可能是一个有效的地址。
	6ffcb28d c64430ff00 mov byte ptr [eax+esi-1],0 
6ffcb18a 8a55fe mov dl,byte ptr [ebp-2] 
6ffcb18d 80fac0 cmp dl,0C0h

; dhcpcore!ClearDomainSearchOption:
6ffcb025 8bff mov edi,edi 
6ffcb027 55 push ebp 
6ffcb028 8bec mov ebp,esp 
6ffcb02a 51 push ecx 
6ffcb02b 56 push esi 
6ffcb02c 8bf1 mov esi,ecx 
6ffcb02e 57 push edi 
6ffcb02f 33ff xor edi,edi ;来自上述HeapAlloc的无效地址(esi) 
6ffcb031 8b8624070000 mov eax,dword ptr [esi+724h] 
6ffcb037 85c0 test eax,eax 
6ffcb039 7414 je dhcpcore!ClearDomainSearchOption+0x2a 
6ffcb03b 50 push eax 
6ffcb03c 57 push edi 
6ffcb03d ff35580dff6f push dword ptr [dhcpcore!DhcpGlobalHeap] ;在无效堆上释放导致svchost.exe进程崩溃
6ffcb043 ff155c21ff6f call dword ptr [dhcpcore!_imp__HeapFree] 
6ffcb049 89be24070000 mov dword ptr [esi+724h],edi

攻击场景分析

攻击者可以构造一个恶意的DHCP服务器,充分发挥该漏洞的作用需要进入到某一个网段内,然后监听网段内的DHCP请求,接收到请求之后就响应一个特制的恶意的DHCP响应数据,这样就可以造成存在漏洞的系统执行代码。

流量分析

首先client发起一个request:

./CVE-2019-0547/DHCP-Request.png

然后,攻击者构建的恶意DHCP服务器响应一个恶意的ACK消息:

./CVE-2019-0547/DHCP-Ack.png

在响应包中可以明显看到触发漏洞的恶意数据。

检测思路

首先要监听UDP的67/68端口的流量。检测设备可以根据DHCP Magic Cookie值\x63\x82\x53\x63来判断是否为DHCP消息。 如果操作码为2,则检测设备必须解析每个DHCP选项,并检查option tag设置为0x77的所有选项的选项数据。如果发现任何此类选项的选项数据以“\x00\x00”开头,则应将流量视为可疑的攻击流量。

总结

这个分析思路很清楚,基本上是一个漏洞响应的微缩过程,到最后给出解决方案,个人感觉比较成熟。最后的流量检测现在很多的防火墙都可以实现,从流量侧拦截攻击好过主机防御。

参考文献

[1] RFC 2131, Dynamic Host Configuration Protocol https://tools.ietf.org/html/rfc2131
[2] P. Mockapetris, RFC 1035: DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, https://tools.ietf.org/html/rfc1035
[3] IANA, Dynamic Host Configuration Protocol (DHCP)and Bootstrap Protocol (BOOTP) Parameters, https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml