0x00 前言
去年6月份,我向微软报告了IE浏览器中的一个UAF(释放后重用)漏洞,漏洞被官方定位严重级别,编号为CVE-2019-1208,微软在9月份的周二补丁日修复了这个漏洞。我通过BinDiff(一款二进制代码分析工具)发现了这个缺陷,编写了一个PoC来演示如何在Windows 10 RS5系统中利用该漏洞。
本文简要介绍了这个漏洞研究过程,如果想深入分析该漏洞,大家可参考这篇技术简报。
0x01 CVE-2019-1208
前面提到过,CVE-2019-1208是一个UAF漏洞。这类安全问题会破坏数据有效性、导致进程崩溃,并且攻击者可以根据漏洞触发方式来执行任意代码或者远程代码。一旦成功利用CVE-2019-1208漏洞,攻击者就可以获得系统中与当前用户的相同权限。如果当前用户具备管理员权限,那么攻击者就可以劫持受影响的系统,比如安装或者卸载程序、查看并修改数据来创建具备完整权限的用户账户等。
0x02 潜在影响
在比较直观的攻击场景中,攻击者可以通过社会工程学方式向未知用户发送钓鱼邮件,诱导用户通过IE浏览器访问(包含CVE-2019-1208利用代码的)恶意站点即可。此外,攻击者也可以发送垃圾邮件,附件中包含该漏洞的利用代码。这些附件可以是启用了IE渲染引擎的微软Office文档,或者包含ActiveX控件的应用程序文件,然后在控件中包含漏洞利用代码。攻击者同样可以攻破与用户有数据交互(比如广告数据)的合法网站后,在上面托管利用代码。
图1. VbsJoin代码执行流
0x03 发现过程
故事源自于BinDiff,当时我想比较下微软在5月份和6月份的vbscript.dll
中函数做了哪些改动(vbscript.dll
是包含VBScript引擎相关API函数的一个模块)。我发现微软在SafeArrayAddRef、SafeArrayReleaseData以及SafeArrayReleaseDescriptor这几个函数上做了改动。
进一步研究后,在之前自己发现的另一个漏洞(CVE-2018-8373)的启发下,我通过如下步骤,使用VBScriptClass
触发了一个UAF问题:
1、arr = Array(New MyClass)
:创建一个SafeArray
,将VBScriptclass: MyClass
保存在arr[0]
中;
2、Callback: arr = Array(0)
:Join(arr)
会触发MyClass
的Public Default Property Get
回调函数。这个回调中会为变量arr
创建一个新的SafeArray
。如图1所示,新的SafeArray
并没有受SafeArrayAddRef
函数保护。因此,浏览器正常设想的代码流会被这个回调函数打破;
3、arr(0) = Join(arr)
:当从Public Default Property Get
回调函数返回后,VbsJoin
的代码执行流将调用SafeArrayReleaseData
以及SafeArrayReleaseDescriptor
来减少SafeArrayData
以及SafeArrayDescriptor
的引用计数。然而,新的SafeArray
不受SafeArrayAddRef
保护,且SafeArrayData
以及SafeArrayDescriptor
的引用计数为0。因此,新的SafeArray
的SafeArrayData
以及SafeArrayDescriptor
会在SafeArrayReleaseData
以及SafeArrayReleaseDescriptor
函数中释放,如图2所示。
图2. 内存布局,从上到下分别为arr = Array(New MyClass)
、arr = Array(0)
以及回调函数(红色高亮框)
当浏览器将VbsJoin
的返回值保存到arr(0)
中时,PoC会在vbscript!AccessArray
中崩溃(如图3所示),因为SafeArrayDescriptor
已经被释放,但arr
变量依然保存着指向已被释放的SafeArrayDescriptor
的指针。
图3. PoC在vbscript!AccessArray
中崩溃
0x04 触发UAF
PoC是否成功触发了UAF?从某种程度上来看,答案是肯定的。为了演示如何完全触发UAF,我使用基本的BSTR字符串(基本字符串/二进制字符串)作为数据结构。SafeArray
是一个多维数组,但由于VbsJoin
只能处理单维数组,我在回调函数中修改了SafeArray
的维度。不幸的是,这种方法无法奏效,会抛出一个运行时错误,表示数组类型与Join
中的类型不匹配。我使用了On Error Resume Next来绕过这个运行时错误。修改版的PoC如图4所示。
图4. 使用On Error Resume Next
的修改版PoC
获取0x20
字节的被释放内存后,我使用了大小为0x20
字节的BSTR来伪造一个较大的SafeArray
。通过堆风水技术,这个BSTR可以稳定重用0x20
字节被释放的内存。如图5上半图所示,最终我得到了一个伪造的一维SafeArray
,该数组元素个数为0x7ffffffff
,每个元素大小为1
字节:
图5. 伪造的SafeArray
(上)以及用于读/写的地址(下)
我成功伪造了一个SafeArray
,可以用来读写从0x00000000
到0x7fffffff
的内存空间。为了泄露一些读/写地址,便于漏洞利用,我借鉴了Zuckerbraun之前的研究成果,使用堆喷射来获取一些固定的读/写地址(0x28281000
,如图5下所示)。
0x05 利用UAF实现RCE
按照Simon Zuckerbraun文章中的思路,我使用Scripting.Dictionary
对象来实现RCE(远程代码执行),但使用另一种方法来构造虚假的Dictionary
。这一次我使用的是BSTR,采用如下步骤,如图6所示。
1、使用读/写内存函数来读取原始的Dictionary
内存,将对应的数据保存到一个BSTR中,然后将VBADictionary::Exists
替换为kernel32!Winexec
;
2、将Winexec
参数(\..\calc.exe
)写入这个BSTR;
3、将这个BSTR保存到util_memory + 0x1000
,然后修改util_memory + 0x1000 – 8 = 9
,使得fake_array(util_memory + 0x1000)
能被识别成一个对象;
4、利用fake_array(util_memory + &h1000).Exists
来触发Winexec
函数。
图6. 伪造的Dictionary
内存布局
图10. 成功实现RCE
0x06 总结
之前Windows 10中已经禁用了VBScript,在2019年8月13日,微软又在Windows 7、8以及8.1的IE浏览器中禁用了VBScript,因此我们只在本地模式验证了这个PoC。然而微软也提到过,用户可以通过注册表或者组策略重新启用这个设置。与此同时,用户以及公司应当始终采用最佳的防御策略:保持系统及时更新并打上补丁、禁用不需使用的(或者需要限制使用的)组件、在网络安全上警惕攻击者可能使用的攻击媒介(比如垃圾邮件或者其他社会工程学威胁等)。
完整的研究细节可参考这篇技术简报。