图片: 像素随着Windows一月份更新的发布,DHCP客户端中非常危险的漏洞
CVE-2019-0547的消息激起了公众的注意。 CVSS评分很高,并且Microsoft尚未立即发布性能评估,这使用户难以决定是否进行紧急系统更新,这引起了人们的兴趣。 一些出版物甚至建议缺乏索引可以解释为在不久的将来会出现有效利用的证据。
像
MaxPatrol 8这样的解决方案可以识别网络上容易受到特定攻击的计算机。 其他解决方案(例如PT NAD)本身也会检测到此类攻击。 为了使之成为可能,有必要描述检测产品漏洞的规则和检测对这些产品的攻击的规则。 反过来,要使之成为可能,每个漏洞都必须找出向量,其操作方法和条件,即与操作相关的所有细节和细微差别。 比通常可以根据供应商站点或CVE中的描述所编译的内容(例如:
出现此漏洞是因为操作系统错误地处理了内存中的对象。
因此,为了在公司产品中添加检测对DHCP中新产生的漏洞的攻击的规则以及识别受此漏洞影响的设备的规则,您应该了解详细信息。 对于二进制漏洞,通常使用patch-diff来深入了解潜在的错误,即将对应用程序,库或操作系统的二进制代码进行的更改与特定补丁程序的比较进行比较,以修复此错误。 但是第一步始终是侦察。
注意 :
要绕过基本的DHCP概念直接进入漏洞描述,可以跳过前几页,直接进入“ DecodeDomainSearchListData函数”部分。侦察
我们转向搜索引擎,查看所有当前已知的漏洞详细信息。 这次只有最少的细节了,所有这些都是从MSRC网站
原始出版物中收集的信息的免费游行。 对于Microsoft在内部审核期间发现的错误,这种情况非常典型。
从出版物中我们发现我们面临一个内存损坏漏洞,该漏洞包含在Windows 10版本1803的客户端和服务器系统中,并且在攻击者向DHCP客户端发送特制响应时出现。 从页面上的那一刻起过了几天,效果指标也会出现:

如您所见,MSRC的评级为“ 2-不太可能利用”。 这意味着极有可能发生的错误要么根本就不存在,要么操作充满了这样的困难,要克服这些困难将需要太高的人工成本。 诚然,Microsoft不会低估这种估计。 这部分受到声誉损失风险的影响,部分受到公司内部响应中心的独立性的影响。 因此,假设:由于报告中指出剥削的威胁不太可能发生,因此肯定是这样。 实际上,这本来可以完成分析,但是对它进行仔细检查并至少找出漏洞是不是多余的。 归根结底,尽管个性无可否认,但错误往往会重演,并在其他地方表现出来。
我们从同一页面下载以.msu归档文件形式提供的补丁程序(安全更新),将其解压缩并在客户端上查找最有可能与DHCP响应处理有关的文件。 最近,这样做变得更加困难,因为更新开始不是以解决特定错误的单独软件包的形式提供,而是以包含所有每月更正的单个累积软件包的形式提供。 这大大增加了多余的噪音,也就是与我们的任务无关的变化。
在整个文件集中,搜索会找到几个适合过滤器的库,我们将它们与未打补丁的系统上的版本进行比较。 dhcpcore.dll库看起来是最有前途的。 在这种情况下,
BinDiff产生的更改最少:

实际上,除了对单个函数进行的外观更改以外-DecodeDomainSearchListData。 如果您熟悉不经常使用的DHCP协议及其选项,那么您可以假定此功能已处理列表。 如果没有,则进入第二阶段-研究方案。
DHCP及其选项
DHCP(
RFC 2131 |
wiki )是一个可扩展的协议,其补充功能由options字段提供。 每个选项都由唯一的标记(数字,标识符),选项中包含的数据所占用的大小以及数据本身来描述。 这种做法是网络协议的典型做法,“植入”到协议中的选项之一是
RFC 3397中描述的“域搜索选项”。 它允许DHCP服务器在客户端上设置标准域名的结尾,它将用作以此方式配置的连接的DNS后缀。
例如,让我们在客户端上设置以下名称结尾:
.microsoft.com .wikipedia.org

然后,在尝试通过域名确定地址时,DNS查询将依次替换此列表中的后缀,直到找到成功的映射。 例如,如果用户在浏览器的地址栏中输入ru,则将首先为ru.microsoft.com,然后为ru.wikipedia.org生成DNS查询:

实际上,现代浏览器太聪明了,因此会响应到搜索引擎的重定向到与FQDN不同的名称。 因此,下面我们附上公用事业较少受损的结论:

在读者看来,这可能是漏洞,因为仅用DHCP服务器替换DNS后缀的可能性(网络上的任何设备都可以使用其标识自己)对通过DHCP请求任何网络参数的客户端构成威胁。 。 但不是:按照RFC的规定,这被认为是相当合法的,已记录的行为。 实际上,DHCP服务器本质上是那些受信任的组件之一,会对访问它们的设备产生很大的影响。
域搜索选项
域搜索选项编号为0x77(119)。 像所有选项一样,它使用带有选项号的单字节标签进行编码。 与大多数其他选项一样,标记之后紧随其后的是数据的单字节大小。 选项实例可能会多次出现在DHCP消息中。 在这种情况下,所有这些部分的数据都按照它们在消息中出现的顺序进行串联。

在提供的示例中,取自
RFC 3397 ,数据分为三部分,每部分9个字节。 从图片中可以看到,完全限定域名中的子域名使用名称的单字节长度编码,紧随其后的是名称本身。 完全限定域名的编码以空字节(即,空大小的子域名)结尾。
另外,该选项使用最简单的数据压缩方法,或者仅是重新解析点。 该字段可能包含值0xc0,而不是域名的大小。 然后,下一个字节设置相对于选项数据开头的偏移,该偏移应用于搜索域名的结尾。
因此,在此示例中,对两个域后缀的列表进行了编码:
.eng.apple.com .marketing.apple.com
DecodeDomainSearchListData函数
因此,DHCP选项号0x77(119)允许服务器在客户端上配置DNS后缀。 但不是在装有Windows操作系统的计算机上。 Microsoft系统传统上会忽略此选项,因此从历史上看,DNS名称的结尾(如有必要)通过组策略进行滚动。 一直持续到最近,Windows 10的下一个版本1803添加了对域搜索选项的处理。 从进行更改的dhcpcore.dll中的函数名称来看,所涉及的错误位于添加的处理程序中。
开始工作。 我们对代码进行了梳理,然后发现了以下内容。 与名称完全一致的DecodeDomainSearchListData过程对从服务器接收到的消息的“域搜索选项”中的数据进行解码。 在输入处,它接收以上一段所述的方式打包的数据数组,在输出处,它生成一个以空值结尾的字符串,其中包含域名结尾的列表,以逗号分隔。 例如,此函数将上面示例中的数据转换为字符串:
eng.apple.com,marketing.apple.com
从UpdateDomainSearchOption过程调用DecodeDomainSearchListData,该过程将返回的列表设置为注册表项的“ DhcpDomainSearchList”值:
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{INTERFACE_GUID}\
存储特定网络接口的主要参数。

函数DecodeDomainSearchListData完成两次。 第一次执行时,它将执行除写入输出缓冲区外的所有操作。 因此,第一遍致力于计算容纳返回数据所需的内存大小。 在第二遍,已经为该数据分配了内存,并且已分配的内存已满。 该函数非常小,大约有250条指令,其主要工作是处理输入流中表示的字符的三个可能选项中的每个选项:1)0x00、2)0xc0或3)所有其他值。 与DHCP有关的错误的假设修复方法基本上归结为在第二遍开始时对结果缓冲区的大小进行了检查。 如果该大小为零,则不会为缓冲区分配内存,并且该函数会立即结束执行并返回错误:

事实证明,该漏洞在目标缓冲区的大小为零的情况下显现。 同时,在执行的最开始,函数会检查输入数据,其大小不能小于两个字节。 因此,为了进行操作,要求选择域后缀的非空选项,以使输出缓冲区的大小为零。
运作方式
首先想到的是,您可以使用前面描述的重新解析点,以便非空的输入数据生成空的输出行:

配置为在响应中发送包含此类内容的选项的服务器实际上将导致未更新客户端上的访问冲突。 发生这种情况的原因如下。 在每个步骤中,当函数解析完全合格域名的一部分时,它将其复制到目标缓冲区,并在其后放置一个点。 在RFC的示例中,数据将按以下顺序复制到缓冲区:
1). eng. 2). eng.apple. 3). eng.apple.com.
然后,当域包含零域大小时,该函数用逗号替换目标缓冲区的前一个字符:
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,
在输入的末尾,它仅是将最后一个逗号替换为零字符,并且您已准备好一行可写入注册表:
9). eng.apple.com,marketing.apple.com
当攻击者发送以上述方式形成的缓冲区时会发生什么? 如果查看该示例,可以看到其中包含的列表由一个元素组成-空字符串。 在第一次通过时,该函数将计算输出数据的大小。 由于数据不包含单个非零域名,因此大小为零。
在第二遍,分配了一个动态内存块,用于将数据放入其中并复制数据本身。 但是,解析函数立即遇到一个空字符,表示域名的结尾,因此,正如所说的那样,它将前一个字符从点替换为逗号。 在这里,我们面临一个问题。 目标缓冲区迭代器位于零位置。 没有前一个字符。 前一个字符属于动态存储块的标题。 并且该相同字符将被替换为0x2c,即逗号。
但是,这仅在32位系统上发生。 使用unsigned int存储目标缓冲区迭代器的当前位置将对x64系统上的处理进行调整。 让我们更加注意负责将逗号写入缓冲区的那段代码:

使用32位eax寄存器从当前位置减去单位,而在寻址缓冲区时,代码访问完整的64位rax寄存器。 在AMD64架构中,任何使用32位寄存器的操作都会使寄存器的上半部分无效。 这意味着在减去之前的零之前的rax寄存器中,将不会存储值–1,而是0xffffffff。 因此,在64位系统上,值0x2c将被写入地址buf [0xffffffff],也就是说,将远远超出为缓冲区分配的内存范围。
所获得的数据与Microsoft的性能评估非常吻合,因为为了利用此漏洞,攻击者需要学习如何在DHCP客户端上远程执行堆喷射,同时要对动态内存的分配有足够的控制权以记录预定义的值,即逗号。零字节,在准备好的地址中产生并导致受控的负面结果。 否则,将数据写入未经验证的地址将导致svchost.exe进程以及当前托管在其中的所有服务下降,并由操作系统进一步重新启动这些服务。 攻击者在某些情况下也可以利用自己的利益。
对于正在调查的错误,这似乎就是全部。 仅有的感觉是,这还远远没有结束。 好像我们没有考虑所有选项。 这些行中肯定还隐藏着其他内容。
CVE-2019-0726
大概是这样。 如果您仔细查看引起错误的数据类型,并将其与错误发生的确切程度进行比较,您会注意到可以更改域名列表,以使结果缓冲区的大小为非零,但尝试在外部写缓冲区是同样会做。 为此,列表中的第一个元素必须为空字符串,所有其他元素都可以包含普通的域结尾。 例如:

提出的选项包括两个元素。 第一个域后缀为空,它立即以零字节结尾。 第二个后缀是.ru。 在输出处计算出的行大小将等于三个字节,这将克服一月份更新对目标缓冲区的空性所施加的检查。 同时,在数据的最开始处为零将强制函数在结果字符串中用前一个字符写一个逗号,但是由于迭代器在字符串中的当前位置为零(如上所述),因此记录将再次在分配的缓冲区之外进行。
现在有必要确认在实践中获得的理论结果。 我们模拟了一种情况,其中DHCP服务器发送一条消息,该消息带有响应于来自客户端的请求而显示的选项,并在尝试向在结果缓冲区行下分配的0xffffffff位置写入逗号时立即捕获异常:

此处,r8寄存器包含一个指向传入选项的指针,rdi是所选目标缓冲区的地址,rax是此缓冲区中要写入字符的位置。 在完全更新的系统上(截至2019年1月),我们得到了这样的结果。
我们在Microsoft中撰写有关发现的问题的信,然后...他们丢了信。 是的,即使有信誉的供应商,有时也会发生这种情况。 没有一个系统是完美的,在这种情况下,您必须寻找其他通信方式。 因此,一周后,在此期间甚至没有收到自动回复的情况下,我们直接通过Twitter与经理联系,并且根据对应用程序进行几天分析的结果,我们发现发送的详细信息与CVE-2019-0547没有关系,并且代表了一个独立的漏洞新的CVE标识符。 一个月后的3月,进行了相应的更正,并且错误收到编号
CVE-2019-0726 。
通过这种方式,有时您可以通过相信直觉来尝试找出1天漏洞的详细信息,以意外发现0天。
积极技术应用分析专家Mikhail Tsvetkov
发布 。