深入分析Windows系统DHCP漏洞(CVE-2019-0726)

阅读量    87864 | 评论 1   稿费 200

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

0x00 前言

微软发布1月份补丁时,人们发现竟然有关于DHCP客户端存在严重漏洞的信息(CVE-2019-0547)。这个漏洞CVSS评分很高,微软也没有立即发布Exploitability Index(可利用指数),因此用户难以抉择是否应该立即更新系统,这引起了广泛讨论。有些机构甚至认为,官方没有公布Exploitability Index,代表很快就会有利用代码公布。

MaxPatrol等解决方案可以识别网络上哪些计算机存在漏洞,其他解决方案可以检测利用漏洞的攻击行为。为了能让这些解决方案行之有效,我们需要描述能够用来检测漏洞以及检测攻击行为的具体规则。另一方面,当我们找到攻击方法以及利用条件后,我们就可以制定出相应的规则。换句话说,我们需要找到与漏洞利用相关的所有细枝末节。与厂商网站以及CVE中的粗略描述不同,这个过程需要我们更加深入以及全面理解这些信息,厂商的描述通常比较简单,比如:

这个漏洞的原因在于操作系统没有正确处理内存中的对象。

因此,为了及时更新我们产品中的规则,以便检测利用DHCP漏洞的攻击行为、确定受影响的设备,我们需要深入分析所有细节。在二进制漏洞方面,许多人一上来就会通过patch-diff方式直达底层,比较并找到打补丁前后应用程序、库或者操作系统内核上的修改,但实际上第一步还是应该搜集信息。

注意:如果想直接阅读漏洞分析内容,大家可以跳过DHCP背景知识,直接查看“DecodeDomainSearchListData函数”这部分内容。

 

0x01 信息搜集

使用搜索引擎查找目前关于漏洞的所有信息后,我们并没有找到太多细节,并且获得的主要信息都转述自MSRC网站的原始资料,当微软在内部审计期间发现错误时经常会出现这种情况。

从这些公开信息中,我们发现这个漏洞与内存破坏有关,运行Windows 10 version 1803的客户端和服务端都会受此漏洞影响,当攻击者向DHCP客户端发送精心构造的响应时就会触发该漏洞。几天之后,该页面又新增了Exploitation Index等级。

如上图所示,MSRC给的等级指数为2:不大可能成功利用。这意味着这个漏洞很有可能要么不可利用,要么利用起来非常困难。不可否认的是,微软在这方面并没有打低分的习惯,部分原因可能是考虑到声誉风险,也有可能是公司内部响应中心职能相对独立的结果。因此我们可以相信微软,认为官方给的可利用等级很有可能是真实的,然后以此开始分析利用方法。但不论如何,仔细检查漏洞成因总归不是坏事。漏洞形式可能多种多样,可能某事某刻又在某个地方重新出现。

我们在官方站点上下载.msu形式的补丁(安全更新),解压补丁后查找最可能与客户端DHCP响应处理有关的文件。现在这种操作已经越来越难,微软已经不通过独立的补丁来修复特定的错误,而是采用一个补丁包来包含整个月的补丁。这种方式增加了许多无关信息,我们必须找到真正感兴趣的目标。

在各种文件中,我们设置的过滤器筛选出了几个库,然后将这些库与未打补丁时的库进行对比,其中dhcpcore.dll库看上去似乎最有希望。此外,BinDiff还找到了最小改动的地方:

实际上,微软只在1个函数上做了或多或少的改动:DecodeDomainSearchListData。如果大家对DHCP协议及相关函数非常熟悉,那么应该知道该函数负责处理哪个列表。如果不熟悉,我们可以来分析一下这个协议。

 

0x02 DHCP协议及相关选项

DHCP(RFC 2131 | wiki)是一个可扩展协议,由选项字段保证整个协议的扩展性。每个选项由唯一的tag(编号,标识符)来描述,选项中包含数据内容及数据大小,这也是网络协议常用的布局方式。在这些选项中,有一个“Domain Search”选项(参考RFC 3397),该选项允许DHCP服务器在客户端上设置标准的域名后缀。这种方式可以作为DNS后缀,方便网络连接。

比如,假设我们在客户端上设置了如下后缀域名:

 .microsoft.com
 .wikipedia.org

随后,如果采用域名方式获取客户端地址,这些尾部域名就会逐一插入DNS请求中,直到找到匹配项为止。比如,如果用户在浏览器地址中输入ru字符串,那么DNS请求就会先构造ru.microsoft.com,然后再构造ru.wikipedia.org

实际上,现代浏览器都太智能了,它们会将类似FQDN的名字重定向到搜索引擎,因此我们可以看到不那么“智能”的输出结果:

大家可能会觉得这就是漏洞的根源所在。就这个行为而言,DHCP服务器能够修改DNS后缀,这对使用DHCP协议来请求网络参数的客户端而言的确可以算是一种安全风险。然而根据RFC,这是非常正常且已经在文档中描述的一种行为。DHCP服务器实际上是一种可信组件,的确能够影响接入的设备。

 

0x03 域名搜索选项

域名搜索(Domain Search)选项编号为0x77119)。与其他选项一样,该选项由单字节tag的选项编号来标识。与大多数选项类似,该选项tag后都跟着数据大小(1字节)。DHCP信息可以包含多个域名搜索选项,在这种情况下,这些选项会按照消息中的顺序拼接在一起。

上图来自RFC 3397中的一个示例,相关数据被分成3块,每块数据大小为9字节。如上图所示,各个子域名开头为子域名所占大小(1字节),随后是子域名字符串。完整域名以null字节作为结尾符。

此外,该选项使用的是最简单的数据压缩方法:reparse points(重解析点)。除了域名大小之外,选项字段中可能还包含0xc0。此时,下一个字节就代表相对于选项开头数据的一个偏移量,用来搜索域名的结尾符。因此,在这个例子中,我们可以得到两个域名后缀:

.eng.apple.com
.marketing.apple.com

 

0x04 DecodeDomainSearchListData函数

编号为0x77119)的DHCP选项可以让服务器设置客户端的DNS后缀,但这不适用于搭载Windows操作系统的计算机。之前的微软系统会忽略这个选项,因此之前在必要时,系统会使用组策略来应用DNS后缀名。然而,在Windows 10 version 1803发布后,Windows已经可以处理Domain Search选项。由于dhcpcore.dll中的函数名已经改变,因此可以判断系统添加的处理函数中包含错误。

现在我们可以开始分析。稍微梳理代码后,我们发现DecodeDomainSearchListData函数会解码来自服务端消息中Domain Search选项所包含的数据,输入数据满足前面描述的数组格式,输出以null结尾的一个字符串,字符串中包含由逗号分隔的结尾域名列表。比如,如果输入上述示例中的数据,那么该函数会得到如下字符串:

eng.apple.com,marketing.apple.com

系统会在UpdateDomainSearchOption过程中调用DecodeDomainSearchListData,将返回的列表写到注册表的DhcpDomainSearchList参数中:

HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{INTERFACE_GUID}\

该注册表键值中保存特定网络接口的主要参数:

DecodeDomainSearchListData函数包含两大步骤,第一步是执行所有的必要操作,但并没有分配输出缓冲区。因此第一步该函数会计算保存返回数据所需的内存空间大小。第二步将分配内存空间,使用该数据填充已分配的内存。该函数代码量并不大,大约只有250条指令,主要功能是处理输入流中可能出现的3种类型: 0x000xc0以及其他值。DHCP漏洞补丁在第二步开头处添加了针对结果缓冲区大小的一个检查步骤,如果该大小为0,那么就不会为该缓冲区分配内存,函数就会结束运行,返回一个错误:

因此只有当目标缓冲区大小为0时,才会触发该漏洞。并且该函数在一开始也会检查输入参数,输入数据大小不能小于2字节。因此,如果想成功利用漏洞,我们需要找到一个非空的域名后缀,使其输出缓冲区大小等于0.

 

0x05 漏洞利用

我首先想到的第一件事就是使用重解析点来确保非空输入数据会生成一个空的输出字符串:

如果我们设置服务器,使其返回的响应包中带有精心构造的这个选项,那么没有打补丁的客户端的确会出现访问access violation(非法访问)错误,接下来我们解释具体原因。在每个步骤中,当函数因此解析部分域名时,就会将这部分域名拷贝至目标缓冲区中,然后添加一个点号。对于RFC给出的这个示例,如下数据会被依次拷贝到缓冲区中:

1). eng.
2). eng.apple.
3). eng.apple.com.

然后,当在输入数据中遇到大小为0的域名时,该函数就会修改目标缓冲区数据,将前一个符号从点号改成逗号:

4). eng.apple.com,

然后继续解析:

5). eng.apple.com,marketing.
6). eng.apple.com,marketing.apple.
7). eng.apple.com,marketing.apple.com.
8). eng.apple.com,marketing.apple.com,

当输入数据结束时,函数最后只需要将最后一个逗号替换为null字符,此时待写入注册表中的字符串如下所示:

9). eng.apple.com,marketing.apple.com

那么当攻击者发送前面构造的缓冲区时会发生什么情况呢?在这个例子中,我们可以看到列表中只包含一个元素:一个空字符串。在第一步处理中,函数会计算输出缓冲数据大小。由于数据并没有包含任何非零域名,因此该缓冲区大小为0。

在第二步处理中,函数会为该数据分配一个堆内存块,然后拷贝数据。然而由于解析函数会立刻碰到一个null字符,表明已经到达域名结尾处,因此如前所述,函数会将前一个字符从点号更改为逗号,此时我们就会碰到一个问题。目标缓冲区迭代器被设置为0,没有前一个字符。前一个字符属于堆内存块头,该字符会被修改为0x2c(逗号)。

然而这种情况只有在32位系统上才会发生。使用unsigned int来存储目标缓冲区迭代器的当前位置在64位系统上会有所不同。让我们仔细研究负责将逗号写入缓冲区的部分代码片段:

系统会使用32位eax寄存器从当前位置减去1,然而在缓冲区寻址时,代码会使用完整的64位rax来寻址。在AMD64架构上,使用32位寄存器的任何操作都会将寄存器的高半字(high halfword)置零。这意味着原来值位0的rax寄存器在经过减法操作后,得到的值将会变成0xffffffff,而不是-1。因此,在64位系统上,0x2c这个值将会被写入buf[0xffffffff]处,位于为缓冲区分配的内存区域之外。

这些信息与微软对该漏洞的可利用评级紧密相关。为了利用该漏洞,攻击者必须知道如何在DHCP客户端上执行远程堆喷射操作,也需要具备堆内存分布的足够控制权,才能确保预设值(即逗号和点号)会被写入已准备好的地址处,从而实现可控的漏洞触发效果。

否则,将数据写入未经检查的地址将导致svchost.exe进程出现错误,可能也会影响该进程当前托管的所有服务,随后操作系统可能会重新启动这些服务。如果条件允许,攻击者也可能利用这一点。

关于这个漏洞,目前这似乎是我们能得到的所有研究结果,但我们感觉不应当这么草草结束,毕竟我们还没有考虑所有选项,肯定还有我们尚未发现的点。

 

0x06 CVE-2019-0726

如果我们详细查看导致错误发生的数据类型,将其与出现错误的具体成因进行对比,可以看到系统可能会修改域名列表,导致生成的缓冲区大小不等于0,并且系统还会尝试将其写入缓冲区外。为了促成这种情况,列表第一个元素必须是一个空字符串,剩下的其他元素可能包含一些象征性域名,比如:

该选项包含两个元素。第一个域名后缀为空,使用null字节结尾。第二个后缀为.ru。此时计算出的输出字符串大小为3字节,因此能通过1月份补丁引入的针对空缓冲区的检查过程。与此同时,数据开头处为0,这样导致该函数在生成字符串中将逗号作为前一个字符写入,但如前文所述,字符串迭代器的当前位置为0,因此函数会在已分配的缓冲区之外写入数据。

现在我们需要通过实际测试来验证我们的理论。我们模拟一个DHCP服务器,当客户端发送请求时,返回包含该选项的一个消息。系统已经为结果字符串分配了一个缓冲区,当系统尝试往缓冲区的0xffffffff写入一个逗号时,我们立刻就能观察到一次异常:

这里r8寄存器包含指向传入选项的一个指针,rdi包含已分配目标缓冲区的地址,rax包含待写入字符在该缓冲区的具体位置。这些值都来自于我们在已安装所有补丁的系统上所观察到的结果(补丁截至2019年1月)。

我们向微软反馈了这个问题,大家猜结果如何?他们竟然搞丢了我们提供的信息。是的,即使对于最好且信誉度最高的厂商来说,有时候也会出现这种情况。没有任何一个系统是十全十美的,在这种情况下我们需要找到其他的沟通渠道。直到一周以后,我们还没有收到自动回复,因此我们决定直接通过推特联系。经过几天的分析后,我们发现我们反馈的漏洞实际上与CVE-2019-0547毫无关联,这是一个单独的漏洞,因此我们也收获了一个新的CVE编号。一个月之后(3月份),微软公布了新的补丁,也分配了一个新的编号:CVE-2019-0726

以上就是在分析1day漏洞时的一个过程,有时候大家可能会凭自己直觉,从这个过程中找到另一个0day漏洞。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多