2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现

  • A+
所属分类:安全文章
今年5月,在一起针对韩国某公司的APT攻击中,研究人员发现该黑客集团使用了两个0day漏洞:Internet Explorer的远程代码执行漏洞和Windows的特权提升漏洞通过利用这两个漏洞,黑客能逃逸IE浏览器的沙箱,危险性极高。

而完整模拟复现0Day漏洞的利用过程,能帮助我们更深入地了解漏洞是如何被黑客利用并发动攻击的,对漏洞研究有十分重要的意义。

下面就让我们来回顾看雪2020第四届安全开发者峰会《逃逸IE浏览器沙箱:在野0Day漏洞利用复现的精彩内容。








演讲嘉宾








2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现



曹磊(@iamelli0t),安全研究员。主要从事沙箱引擎开发;浏览器,Windows内核漏洞利用与检测;红蓝攻防;威胁追踪等技术研究。


曾在微软BlueHat Shanghai 2019, Virus Bulletin 2020等安全会议上发表演讲。









演讲内容








以下为速记全文:

大家上午好,非常荣幸可以参加这次由看雪学院举办的安全开发者峰会。本议题分为四个部分。首先是提议的背景介绍;第二、三部分别是两个在野0Day的漏洞原理和利用方法;第四部分是完整的沙箱逃逸利用演示。





一、背景介绍


这个议题其实是我临时想起来的。熟悉Windows操作系统安全的同学一定知道,微软会在每个月第二周的周二发布安全补丁公告。其中引起大家讨论的一个话题是在野0Day的攻击事件。


今年8月份的时候,微软就发布了这样一个CVE公告——CVE-2020-1380:


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


它是一个存在于脚本引擎中的内存破坏漏洞。熟悉IE浏览器漏洞的同学应该知道,从2018年开始,黑客组织就一直在针对IE浏览器的一些脚本引擎漏洞发起攻击。微软在发现VB脚本引擎存在漏洞之后,索性将其在IE中默认禁用了。但是黑客的攻击并没有因此停止,紧接着他们又将攻击目标转向另外一个IE浏览器的老版本JavaScript脚本引擎jscript.dll


在发布CVE-2020-1380这个漏洞公告的时候,安全研究员们就开始猜测,这次是否又是一个针对jscript.dll的漏洞利用。很快,捕获这个漏洞的安全厂Kaspersky当天下午就发布了他们的分析Blog。可以看到,整个攻击是利用了IE浏览器的新版本JavaScript脚本引擎jscript9.dll的jit引擎漏洞。除此之外还用了另外一个打印机驱动的提权漏洞实现了IE浏览器沙箱逃逸


虽然Kaspersky和Google都发布相应的分析Blog,但对于整个利用过程的具体代码却是语焉不详。因此我深入分析了这两个漏洞的原理,并了复现完整的利用代码


在讲这两个漏洞之前,首先需要先介绍一下为什么现代浏览器需要沙箱保护。大家知道,浏览器是用来加载脚本引擎的,输入是相当复杂且不可信的,往往会因为错误的处理输入引发一些内存破坏的问题。这就为攻击者提供了远程代码执行的机会。因此为了保护浏览器的安全,现在浏览器使用沙箱来保护渲染进程。而IE浏览器的沙箱会相对简单一些,基于多进程的架构。渲染进程一般工作于Low Intergity权限,而开启增强保护模式(EPM)的时候则工作于AppContainer中。因此针对渲染进程的远程代码执行漏洞往往没有权限执行一些恶意操作,比如系统文件读写,注册表操作等。这个时候就需要配合一个提权漏洞实现沙箱的逃逸


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现





二.CVE -2020-1380


第二部分首先介绍一下这个JIT漏洞。这是jscript9的一个简单的执行流程:


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


JavaScript源码首先被解析成抽象语法树,字节码生成器基于AST生成供虚拟机解释执行的字节码。但是Interpreter的执行效率往往是比较低的,于是现在浏览器为了加速代码执行效率就会有一个后端优化的JIT引擎。当一个代码块被反复执行的时候,就会被标记成热点代码。Interpreter会收集这段热点代码变量的数据类型,并将这类代码传到后端的JIT引擎,再基于这些信息将代码进行本地代码生成的操作,随后进行替换。当JIT引擎当中发现某些变量数据类型和优化时的假设不一致的时候,就会跳回到Interpreter重新解释执行。这里面最复杂的部分就是后端JIT,它也是现代浏览器漏洞攻击面较多的一个子模块,特别是在对变量类型判断推导错误导致的过优化时,就会造成一些漏洞,比如UAF,类型混淆,数组越界读写等。


介绍了jscript9.dll整体流程之后,我们再来看它的JIT子引擎的执行流程,具体分前端和后端两个部分。这是一个可以触发JIT的poc代码:


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


poc代码主要由三部分组成,首先函数opt对数组的第一个元素进行赋值。接下来会创建一个TypedArray数组arr,最后for循环会反复调用这个函数,触发JIT。下面,我们看看jscript9是如何生成JIT代码的。


在我当时的调试版本中,jscript9首先会对循环次数进行比较,这里面比较的阈值是32h。当循环次数大于32h的时候会生成一个循环体JIT代码的job放入队列。后端会从队列中取job,最后调用Func::Codegen生成JIT代码。


Func::Codegen是后端JIT的主函数,包括IR,Inline,CFG,全局优化等等,整个过程非常复杂。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


生成完JIT代码后会替换原始字节码入口函数,进一步跟进,会看到OPT函数的代码,通过这样的方式就可以定位到JIT代码的片段。为什么需要这样的工作呢?因为要调试JIT代码的漏洞,就需要在内存中找到他的地址。由于jscript9是闭源的,没有提供相应的JIT dump操作,因此需要做一些逆向工作。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


下面我们就来看一下这个漏洞的poc:


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


主体流程和刚才解释JIT原理那部分代码片段是基本一致的。在OPT函数里有一个arguments对象,指向函数的参数,这里的三行arguments操作效果其实就相当于上面注释的绿色代码,说白了就是将value2的值赋值给value1,后面还有一个if基本块,为value1提供int的数据类型。当生成JIT后,会将OPT函数的第二个参数替换成一个Object,再去执行的时候就会触发这个Object的回调。


我们刚才介绍过这三行arguments操作就相当上面注释的绿色代码。我们首先去掉这三行arguments操作,看一下生成的JIT代码是什么样的。可以看到,它在做数组元素赋值的时候会调用ToFloat_Helper函数,在这个函数上面有一个全局变量的赋值操作,这是什么意思呢?


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


这就是DisableImplicitCallFlag标志位,也就是说在JIT引擎中对于可能触发用户脚本回调的操作会出现这样一个标志位设值,它表示脚本回调是不安全的。在ExcuteImplicitCall这个函数当中,它首先会去检查这个标志位有没有被置位,如果被置位的话就不去调用脚本回调函数,这是一个正确的执行流程。如果我们把刚才三行arguments操作还原,再去看生成的JIT代码,此时就已经没有了之前的标志位的置位操作了,因此在这种情况下是不允许JIT中触发脚本回调的。这显然是不安全的,可以利用neuter ArrayBuffer操作释放当前上下文中的buffer,再回到JIT的时候并不知道这块内存已经被释放了,就会造成UAF问题。因此这个漏洞产生的根本原因是Array.prototype.push 的副作用导致JIT引擎数据类型推导错误。以上就是整个漏洞的原理。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


讲了漏洞原理之后我们看下怎么利用这个UAF漏洞。基本上也就是一些常见的套路,首先要考虑的是如何去占位这个UAF造成的空洞,IE的堆内存可以分为CRTHeap和IE的CustomHeap。通过刚才的那个漏洞,我们可以得到一个内存大小可控的存在于CRTHeap的空洞。接下来可以去创建一系列的Array,为了维护这些Array对象,jscrip9会创建大量的LargeHeapBlock对象,接着通过函数CollectGarbage()手动触发GC,再次形成空洞,用同样的方式再创建一系列的Array


这个时候数组T[i]和K[j]就可能指向同一块内存,从而将CRTHeap的UAF漏洞转化更容易利用的IE CustomHeap的UAF。这里会有这样一个问题,为什么可以通过调用CollectGarbage()释放掉这块的内存?我们知道,在脚本中并没有手动地释放数组T的内存,就需要利用第一次UAF提供给篡改能力


我们看一下LargeHeapBlock的数据结构。在刚才的回调当中我们不再将回调的返回赋值给arr[0],而是arr[5],arr[5]正好指向UAF后LargeHeapBlock的Allocated Block Count属性。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


下面是一次动态调试的过程,首先创建0X8C大小的ArrayBuffer,这边需要去关注一下5DF5C18这个地址,这会是我们反复利用的一块内存。我们利用第一次的UAF漏洞释放这块内存,然后申请大量Array,可以看到它已经被LargeHeapBlock对象占用了,说明第一次占位已经成功。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


再利用valueOf回调将Allocated Block Count置为0,这样的话手动调用CollectGarbage()就会看到这个对象再次被释放了,这是第二次UAF。但是这个LargeHeapBlock对象原来维护数组T[i]并没有被释放。接着再去创建大量的Array K[j],可以发现T[i]和K[j]会指向同一块内存。这样的话我们的内存布局就完成了。我们将刚才的T[i]和K[j]重新命名为R1和R2,通过R2[0] = Object就可以实现对象地址泄露。进一步通过这样的机会去构建一个Faked DataView实现任意地址读写,都是些常用技巧,不再详述。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现





三.CVE-2020-0986


现在我们再来看第三部分,CVE-2020-0986这个漏洞。介绍这个漏洞之前先要介绍下LPC通信。它的通信对象包括进程到进程,进程到驱动和驱动到驱动,可以在不同的环之间进行通信,低权限可以和高权限进行通信。整个模型是一个客户端服务端模型,整个通信的数据有两部分,一个是LPC MESSAGE,当传输数据量比较大的时候需要通过内存映射。


这是一个简单的LPC通信流程,首先Server需要去指定一个端口名,创建端口,接着去监听这个端口。Client要去连接Server这个端口,Clinet会同步地去发送请求并等待Server的响应,处理完之后再完成整个通信,这就是一个简单的LPC通信过程。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


介绍LPC的原理之后我们看一下这个漏洞。它存在splwow64.exe进程中,微软的解释是它是一个打印机驱动的宿主程序,这个程序具体是用来做什么的呢?在64位系统当中还会有一些32位的程序,比如说IE的渲染进程调用打印机服务就会启动这个进程,同样window.print()也可以。


splwow64.exe进程在启动完之后会用TLPCMgr::ProcessRequest函数监听客户端发送的请求,最关键的点就是在这里的最后一行代码,会将客户端传入的inputBuffer直接传给后面的调用的函数gdi32full!GdiPrinterThunk。这里可以看到,它是将客户端送过来的地址直接传给了后端处理函数。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


步入这个函数之后,不同的索引会分发到不同的处理函数,其中跟漏洞相关的是0x6D。这里是0x6D处理的主要流程,可以看到它会进入两个函数,第一个是调用FindPrinterHandle寻找一个打印机句柄,第二个是调用WINSPOOL!DocumentEvent,通过这两个函数后就会进入最后触发漏洞的代码memcpy


这是比较常见的容易引起漏洞的函数,可以看到memcpy的src,dst均来自客户端输入的inputBuffer,也就是说客户端发送过来的数据可以直接去控制memcpy的原地址和目标地址,因此这个漏洞也是非常好利用的。这个漏洞根本原因就是服务端对客户端输入的数据盲目信任,并且FindPrinterHandle函数没有验证输入句柄的有效性,从而可以被客户端任意伪造


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


讲完了漏洞原理,我们再看一下漏洞的利用。刚才说到,如果需要连接服务端的PORT的话,首先需要知道一个PortName。这是WinOBJ查到的一个PortName,每次机器重启都会不同。服务端会通过NtCreatePort函数去创建端口,因此我们去查找一下splwow64.exe导入表,根据交叉引用,就可以找到PortName生成的逻辑。同样,我们可以在这个地址去下一个断点,dump出相关内存,印证我们的假设。有了这段代码逻辑就可以将整个获取端口名的算法重新实现。


拿到PortName之后我们就可以跟服务端正常数据通信了,后面要做的事情就是要绕过FindPrinterHandle这个函数。这里有两件事情要做,第一是我们在红框1的位置,可以看到这边有一个RBX+40,可以看出它是用户可控的,保证内存正常读写就可以了。第二是要绕过红框2的位置,如果返回的是0的话就不会进入下一段的代码逻辑,所以这边需要再绕过红框2。红框2的逻辑也非常简单,就是比较了另外两个参数,ESI和EDI,他们都是用户可控的。由于所有对函数逻辑的比较都是在可控范围内的,因此这个函数可以被轻易绕过,并且进入下一部分触发漏洞的逻辑



2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


我们回到刚才处理的函数,可以看到有这样一个新的调用,它是一个变量,经过一个DecodePointer函数,它是做什么的呢?其实就是用来做指针的加密和解密的,也是用来保护指针的安全


由于我们可以知道加密前后DocumentEvent函数地址,再通过这样的代码块去计算加解密的Cookie,那么计算完之后怎么做呢?我们可以将DocumentEvent函数调用替换为System,最终实现shellcode执行


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现





四、逃逸沙箱完整利用演示


完整的攻击过程是:首先利用CVE-2020-1380去实现一个IE渲染进程的远程代码执行。之后首先会调用CreateDC启动splwow64.exe进程来监听客户端。接着会把第二阶段的提权payload下载下来,通过LPC与服务端splwow64.exe进行通信,完成对CVE-2020-0986漏洞的利用,实现提权,最后拿到一个Medium权限的shell。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


这里我提一下为什么浏览器的渲染进程可以调用打印机驱动。其实这是跟IE的提权策略有关系的。之后需要一个shellcode把两阶段的exp串起来,这就是一些比较常见的技术了。可以通过反射DLL注入实现。接下来是完整的视频演示。


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现


最后是这次议题相关的一些参考文献。那么今天我分享的议题就到这里,谢谢大家!


注意:关注看雪学院公众号(ikanxue)回复“SDC”,即可获得本次峰会演讲ppt!其他议题演讲PPT,经讲师同意后会陆续放出,请大家持续关注看雪论坛及看雪学院公众号!


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现
- End -




2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



求分享

求点赞

2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现

求在看


2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现
“阅读原文查看议题完整PPT吧!

本文始发于微信公众号(看雪学院):2020看雪SDC议题回顾 | 逃逸IE浏览器沙箱:在野0Day漏洞利用复现

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: