安全路透社
当前位置:安全路透社 > 安全客 > 正文

【技术分享】 CVE-2017-0290:史上最糟糕的Windows远程执行代码漏洞


前言


像过去一周的许多其他故事一样,本文也要首先从下面的推文开始。

http://p3.qhimg.com/t019cfb00621a8d065d.png

Google Project Zero的Natalie Silvanovich和Tavis Ormandy在Microsoft恶意软件保护引擎中发现了一个严重的安全漏洞,攻击者可以利用这个漏洞在只要运行了Microsoft任何反恶意软件产品(如Security Essentials或Windows Defender)的Windows计算机上执行任意代码,并且攻击方法也很简单,只需设法让该计算机访问一个恶意文件即可。同时,针对这个漏洞的攻击向量也非常多,例如通过电子邮件发送文件,或通过任何其他渠道(如Skype或Messenger)发送,或者将其托管在恶意网站上或将其上传到IIS Web服务器中,等等。

与过去一周的许多其他故事不同的是,本文不会将读者详述Natalie和Tavis是如何发现这个漏洞、如何向Microsoft报告的。相反,本文将重点关注这个漏洞本身,导致该漏洞的根本原因,以及如何为它编写一个微补丁。

但是,首先你可能会问:我们为什么会想到给这个漏洞编写一个微补丁呢?众所周知,微软在修复这个漏洞方面反应非常及时,在周末就已经提供了更新。此外,恶意软件保护引擎是作为动态加载库mpengine.dll实现的,而微软巧妙地设计了其反恶意软件产品,所以人们无需重新启动计算机:只要卸载旧的DLL,加载新的DLL即可。

那么为什么要写一个微补丁呢?实际上,并不是每台计算机都会自动更新:虽然默认情况下的配置是自动应用更新,但是,如果管理员想要控制何时应用更新的话,管理员就会对这个设置进行修改。重要的是,企业管理员都喜欢这样做,因为如此一来,他们就有机会将这些更新部署到组织中的计算机之前,首先对其进行必要的测试。想象一下,如果更新的mpengine.dll有一个缺陷,就有可能阻止用户访问合法文件。

编写这个微补丁的另一个动机在于学习,因为我们以前没有修补过安全产品,所以希望能够从这件事情中学到新的东西。最后的原因是,向读者介绍分析漏洞方面的知识,以及如何编写微补丁。 


再现CVE-2017-0290


分析漏洞的第一步是再现漏洞。 Project Zero报告提供了一个可下载的概念验证文件,虽然具有.zip扩展名,但实际上是一个类HTML文件,其中包含一小段漏洞利用代码和大量的随机HTML内容,以确保引擎会处理该文件。

在64位Windows 8.1上再现这个漏洞其实非常简单:只需下载并保存文件就足以使Windows Defender服务崩溃,如下所示:

http://p5.qhimg.com/t01e8b62e15d6c1f184.png

然后看到:

http://p8.qhimg.com/t014c45e769b842778f.png

崩溃后,应用程序事件日志会记录有关此崩溃的错误事件,显示崩溃的模块是mpengine.dll,并且崩溃位置在偏移0x21745a处。 (Google报告中提供了不同的崩溃地址,因为他们使用的是32位计算机。)

http://p6.qhimg.com/t019efbd31653c1fd1a.png

请注意,我正在使用mpengine.dll版本1.1.13701.0,这是修复1.1.13704.0之前的最后一个易受攻击的版本。对最后一个易受攻击的版本进行分析总是很好,以便最小化与固定版本的差异 – 您将在分辨这些版本时感谢自己。 


CVE-2017-0290漏洞分析


在成功再现这个漏洞之后,我们接下来要做的事情就是对其进行详细的分析。为此,我们可以首先从阅读Google的报告开始,因为Natalie和Tavis已经详细地介绍了相关的情况。 对我来说最重要的细节是,这是一个类型混淆错误,具体来说有一些函数期望接收一个 string对象,但是得到的却是一个number对象(这导致调用一个字符串vtable函数,其中却没有vtable)。

这很重要,因为对于类型混淆漏洞来说,典型的修复方法是添加相应的检查,看看类型是否正确。当观察具有漏洞的代码和修复后的代码之间的差异后,这种修复代码通常很容易识别。

下面该IDA上场了。下图显示了崩溃的函数——确切的访问违例出现在mov rax,[rcx]指令(见红色框)处,地址为0x75A31745A,距离mpengine.dll默认基地址的偏移为0x21745a。 

http://p3.qhimg.com/t018e79e9320064b395.png

当厂家提供了相应的补丁后,通过比较具有漏洞的代码和修补后的代码,完美我们不仅能够得到非常有用的信息,还能更好地了解含有漏洞的程序和补丁代码。当然,这项比较的工作可能比较耗时,因为通常会涉及大型的二进制文件(比如mpengine.dll是12MB),所以经常得到很多匹配的函数,并且我们要搞清楚的是代码逻辑的不同之处,而非仅仅代码本身的区别——这可能有点令人沮丧。

所以我分析了两个版本的mpengine.dll:一个含有漏洞的1.1.13701.0版本和修补后的1.1.13704.0版本。其中,共有38440个匹配的函数,看看,这可不是一个小数目。我们要做的事情,就是比较两个版本之间的导致发生崩溃函数。如果我很幸运的话,很快就找到补丁,这样就可以早点回家了。

http://p2.qhimg.com/t01bae1cf141a7f0f9a.png

这两个函数在逻辑上是相同的,这意味着缺陷(和补丁)在调用堆栈的较高位置。这时,可以对所有调用这个函数的函数进行相应的比较,但是这里大约有50个函数,如果所有的函数都被证明是相同的,那么这种方法可能变成一个非常艰巨的任务。 (更不用说IDA可能弄不清楚所有调用方。)

现在考察调用堆栈:你可能已经注意到了,到目前为止我还没有使用调试器,原因是Windows Defender是受保护的服务,因此会设法保护自己免受篡改。即使您是本地管理员,也不能将调试器附加到受保护的进程。并且即使对不保护受保护的服务做这件事情也不容易:虽然它的保护状态是由LaunchProtected注册表值定义的(在我们的例子为HKLM \ SYSTEM \ CurrentControlSet \ Services \ WinDefend),但是,当Windows Defender正在运行时,你无法修改相应的注册表的值,这是为了防止Windows Defender受到“攻击”。

幸运的是,我们有办法阻止Windows Defender——通过PoC让其崩溃。所以,为了让Windows Defender崩溃,重命名其LaunchProtected注册表值,重新启动计算机(服务的受保护状态仅在系统启动时被读取),然后配置Windows错误报告以生成崩溃进程的转储文件。 (我只创建了LocalDumps键和DumpFolder值,其中包含“C:\ dumps”)。

在再次让Windows Defender崩溃之后,我将其转储文件放到C:\ dumps中,它包含一个访问违例相关的完整调用堆栈。实际上,我只对mpengine.dll的位置感兴趣: 

mpengine!FreeSigFiles+0x11ea9a 
mpengine!FreeSigFiles+0x12046f 
mpengine!FreeSigFiles+0x111e81 
mpengine!FreeSigFiles+0x111d9e 
mpengine!FreeSigFiles+0x125eaa 
mpengine!FreeSigFiles+0x3de1d  
mpengine!FreeSigFiles+0x3dbf5  
mpengine!FreeSigFiles+0x125eaa 
mpengine!FreeSigFiles+0x117ade 
mpengine!FreeSigFiles+0x120146 
mpengine!FreeSigFiles+0x113d76 
mpengine!FreeSigFiles+0xcce7f  
mpengine+0x54a99               
mpengine+0x865e1               
mpengine+0x50f3f               
mpengine+0x50d1f               
mpengine+0x8c208               
mpengine+0x8bf47               
mpengine!FreeSigFiles+0x174a3  
mpengine+0x13b7d               
mpengine!FreeSigFiles+0x1535a  
mpengine!_rsignal+0x243        
mpengine!_rsignal+0xe7

对于上面的第一个我们已经有所了解——这是我们刚刚分辨出的发生崩溃的函数出现访问违例的位置。所以,我继续处理第二个地址,FreeSigFiles + 0x12046f,并将其放在IDA中。正如预期的那样,它位于发生崩溃的函数调用之后。然后我考察了包含该地址的函数的地址,并考察了它与修补版本的区别。

http://p0.qhimg.com/t01a0e105e2f1dd3bba.png

这看起来添加了相应的检查,如果某些东西不正确,它将退出函数。 (修补版本位于左边。)上面的红色区域是添加的代码,它接收rdi(对象)为参数并将其传递给某个函数的调用,如果该函数的结果为4,则执行情况如前所述,否则将返回值(a1)设置为0(见下部红色区域代码),并且退出该函数。从上面的红色区域中调用的函数似乎决定了对象的类型并返回其类型代码。通过查看对这个函数的其他调用,我找到了JavaScript的typeof运算符的实现,明确了字符串的类型代码实际上就是4。

这显然就是我正在寻找的补丁。确认这一点并非难事,它所做的正好是我们所预期的,同时它还位于崩溃代码路径中。 


针对CVE-2017-0290的微补丁


现在,我的目标是创建一个微补丁,将相同的补丁逻辑注入到易受攻击的mpengine.dll版本中。 在理想的情况下,我可以原封不动的套用打补丁后的版本中的相关代码,并将其注入到同一个位置——但现实情况是,编译器喜欢使用不同的寄存器和不同的实现代码来实现相同的逻辑。所以,我不得不根据原来的补丁重新实现补丁逻辑。

我们来看看IDA中的含有漏洞的函数。

http://p4.qhimg.com/t01cfd32ef4d13c9a0c.png

上图显示了易受攻击的函数,以及注入我们的代码的理想位置。之所以选择该位置,是因为其允许我们从自己的补丁代码直接跳转到函数结尾(地址最低的代码块)。

以下是版本为1.1.13701.0的64位mpengine.dll的补丁代码: 

;0patch for CVE-2017-0290 in 64-bit mpengine.dll version 1.1.13701.0
MODULE_PATH "C:\Analysis\CVE-2017-0290\mpengine.dll_64bit_1.1.13701.0\mpengine.dll"
PATCH_ID 271
PATCH_FORMAT_VER 2
VULN_ID 2436
PLATFORM win64
patchlet_start
 PATCHLET_ID 1
 PATCHLET_TYPE 2
 PATCHLET_OFFSET 0x218E10
 ; We'll need the GetTypeOf function and the location of function epilogue
 PIT mpengine.dll!0x218940,mpengine.dll!0x218E9A
 ; Note that GetTypeOf taints the following registers:
 ; rdx - always
 ; rcx - only in case of an exception
 ; rax - expected, this is the return value
 code_start
  push rcx          ; We need to preserve rcx, as it's still used after our patchlet code
                    ; while GetTypeOf taints rdx, we don't need to preserve it
  mov rcx, r9       ; r9 points to the object
  call PIT_0x218940 ; GetTypeOf object
  pop rcx           ; restore rcx
  cmp eax, 4        ; is the object of type string?
  jz OK             ; It is? Very well, continue...
  xor al, al        ; It isn't? Exit this function without doing anything, return 0
  call PIT_ExploitBlocked ; Show "Exploit attempt blocked"
  jmp PIT_0x218E9A  ; Jump to function epilogue
  OK:
 code_end
patchlet_end

这个补丁程序将被放到上面代码中显示的位置,其作用像原始的补丁一样,针对该对象(其地址在寄存器r9中)调用GetTypeOf,并查看其类型代码是否为4(字符串)。如果是的话,它将继续执行原始代码注入。否则,它将返回码(寄存器al)设置为0,并跳转到函数结尾。

请注意,为了避免任何副作用,我必须(1)审查GetTypeOf函数,看看哪些寄存器可能会被污染,以及这是否会影响我们注入的补丁后面的代码(它会影响rdx和rcx,但是rdx存放的值不会对我们的注入点有影响),然后(2)在调用GetTypeOf函数之前,将rcx的值存储到堆栈上,因为rcx保留了一些在我们注入的补丁后面的代码仍然要用到的值。

此外,我也为易受攻击的32位版本的mpengine.dll写了相同的补丁。如果您安装了0patch Agent,补丁ZP-271和ZP-272应该已经下载到您的计算机,静候导致加载mpengine.dll的事件的到来。 


受保护服务的讽刺性


要恢复原始系统配置,我将Windows Defender恢复为受保护的服务,并且补丁也停止应用。很明显,我们无法将我们的加载程序注入受保护的Windows Defender中,因为只允许加载Microsoft签名的二进制文件。之所以这样,是因为Windows Defender想保护自身免受本地恶意软件(即使有管理员权限的代码)的侵害。

具有讽刺意味的是,Windows的防恶意软件,竟然会阻止我们的安全产品在Windows Defender中修复漏洞,而该漏洞的利用代码却可以在Windows Defender中自由执行任意代码。 (嗯,也许我们应该利用这个漏洞利用代码,使我们的代码得以在Windows Defender中运行,从而修复它的漏洞。)

需要注意的是,给Malware Protection Engine的ZP-271和ZP-272打补丁只适用于没有提供受保护服务的Windows 7和Windows Vista。 


试用CVE-2017-0290微补丁


如果您想对这些微补丁进行试验的话,需要用到:

1. 运行Windows Defender软件的32位或64位Windows 7电脑。虽然您也可以在较新的Windows版本上进行测试,但您必须取消Windows Defender的服务保护功能才能继续。

2. 含有漏洞的mpengine.dll。如果您的Windows Defender不是这个版本(这倒不太可能,由于它会自动更新),您可以在这里下载:

1. 用于32位Windows的32位mpengine.dll,版本1.1.13701.0

3. 用于64位Windows的64位mpengine.dll,版本1.1.13701.0

首先,通过提权后的服务控制台停止Windows Defender服务。

然后导航至C:\ ProgramData \ Microsoft \ Windows Defender(文件夹权限最初不允许你打开它,所以Windows会要求提高权限,之后它会添加你的帐户到文件夹ACL)。打开Definition Updates文件夹,这时会看到一个或多个具有类似GUID的名称的子文件夹,它们以大括号开头。打开这些文件夹,找到包含mpengine.dll的文件夹。将现有的mpengine.dll重命名为其他名称,然后在其中放入含有漏洞的mpengine.dll。 

启动Windows Defender服务。

下载概念验证文件并将其存储到一个空的临时文件夹中。

通过控制面板启动Windows Defender控制台,并通过自定义扫描上述文件夹。请注意,Windows Defender服务将会崩溃。

现在在计算机上安装0patch Agent。如果您还没有安装的话,请下载免费的副本,并使用您的免费的0patch帐户注册。

最后,重新启动Windows Defender服务并重新扫描临时文件夹。这一次,你会看到一个“Exploit Attempt Blocked”弹出窗口,而不是Windows Defender崩溃。

如果您想自己编译我们的补丁,您可以下载其源代码,并通过0patch Agent for Developers编译它们。

虽然这个漏洞已经在大多数计算机上自动修复,但事实证明,对于打微补丁来说,这是一个有趣的学习机会。我希望这篇文章可以对未来的微补丁开发和使用者有所帮助。


后记


当我在写这篇文章的时候,世界正在被WannaCry ransomware蠕虫狂虐,实际上,该蠕虫利用的是一个已知的漏洞,该漏洞的官方补丁也及时发布了。不幸的是,许多医院和其他重要的基础设施都是脱机的,部分原因还是因为他们仍在使用Windows XP等不再提供支持的操作系统。在2017年,由于某些合理和复杂的原因,这些过时的系统仍会被继续使用,毫无疑问,在明年和后年,还是有些机构仍然会继续使用它们。 0patch的目标之一,就是为用户提供相应的更新,来保护这些终端系统。防御现代攻击者需要人们快速的反应,尽管CVE-2017-0290的这项练习对用户的价值可能很低,但却是磨练技能和提高速度的一个机会。当今世界将需要越来越多的第三方补丁程序开发人员,因此,热烈欢迎所有现有和潜在的安全研究人员都加入到我们的队伍中来。 


原文链接:https://0patch.blogspot.jp/2017/05/0patching-worst-windows-remote-code.html

未经允许不得转载:安全路透社 » 【技术分享】 CVE-2017-0290:史上最糟糕的Windows远程执行代码漏洞

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册