-
-
[翻译]Edge Chakra JIT类型混淆漏洞利用分析报告(CVE-2019-0539)
-
发表于: 2019-7-12 15:13 6426
-
最近在分析学习浏览器漏洞,看到一篇非常好的类型混淆漏洞分析文章,翻过来学习一下,在2019年1月的Microsoft Edge Chakra Engine 更新中修复了CVE-2019-0539 漏洞。
Google Project Zero的Lokihardt 发现并报告了这个漏洞。该漏洞可以通过让用户访问恶意网页导致远程代码执行,当Chakra实时(JIT)javascript编译器生成的代码在执行对象的类型转换时,会出现这种类型混淆漏洞。
Chakra开发团队的Abhijith Chatra在他的博客中描述,动态类型对象具有映射和动态数组的属性。映射用于拿到动态数组中对象属性的索引,动态数组用于存储属性的实际数据,CVE-2019-0539会使JIT代码混淆内存中的对象,会导致动态数组指针被任意数据覆盖。
在Windows上创建有漏洞的ChakraCore版本:
(在Visual Studio MSBuild命令提示符中输入)
Time Travel Debugging允许调试者记录正在运行进程的执行流程,然后对这些执行流程进行重放。TTD调试可以回放调试器会话,帮助调试者更轻松地调试bug。
从Microsoft Store安装最新的Windbg,以管理员权限运行。
PoC:
使用TTD运行windbg调试器,崩溃后执行以下命令:
下面是最终覆盖指向数组针的JIT代码,注意调用chakracore!Js :: JavascriptOperators :: OP_InitClass的地方。
这个函数最终将调用SetIsPrototype,它将会被转换成对象类型。
下面是JIT代码调用OP_InitClass之前的对象的内存转储,注意两个对象是如何在对象的内存中内联的
下面callstack的SetIsPrototype最终由OP_InitClass调用,从而转换对象的类型,转换导致两个数组不再内联,而是存储在动态数组中。
下面是OP_InitClass调用后对象的内存转储,这里的对象已经转换,并且不再内联2个数组。
下面是在JIT代码错误地分配属性值之后对象的内存转储,覆盖了数组指针
最后,当访问其中一个对象的属性时,被重写的数组指针将被解除引用,从而导致程序崩溃
由于添加了Windbg的TTD,调试过程得以简化,具体来说,设置断点然后运行程序直接导致实际的数组指针被覆盖后程序崩溃。
现在继续分析如何实现完整EXP,最终可以导致RCE(远程执行代码),漏洞利用的重点是要注意Microsoft Edge进程是在沙盒中,因此为了完全破坏浏览器系统,需要一个额外的漏洞来做沙箱逃逸。
在漏洞成因分析中已经看到,该漏洞使我们能够覆盖javascript对象的数组指针,在Chakra中,javascript对象(o = {a:1,b:2};)在Js :: DynamicObject类中实现,可能有不同的内存布局,从DynamicObject类定义中,可以看到DynamicObject的三种可能的内存布局实际方式:
三种情况是:对象只有一个auxSlots指针,但没有内联插槽(#1);只有内联插槽但没有auxSlots指针(#3);或者两者都有(#2)。
在CVE-2019-0539 PoC中,'o'对象以(#3)内存布局形式开始其生命周期,然后,当JIT代码最后一次调用OP_InitClass函数时,对象“o”的内存布局就更改为了(#1)。
JIT代码在OP_InitClass函数调用之前和之后的'o'的精确内存布局如下:
在OP_InitClass调用之前,o.a属性保存在第一个内联函数中;在调用之后,它保存在auxSlots数组中,因此JIT代码尝试使用0x1234更新第一个内联函数中的o.a属性,但由于它不知道对象的内存布局已经更改,它实际上覆盖了auxSlots指针。
现在,为了利用这个漏洞,需要破坏其他一些有用的对象并用它来R\W内存中的任意地址,但是首先,需要更好地了解利用这个漏洞能做什么。当覆盖DynamicObject的auxSlots指针时,可以处理我们在auxSlots中放入的任何内容作为auxSlots数组。
因此,例如使用漏洞将auxSlots设置为指向JavascriptArray对象,如下所示:
然后可以通过赋予'o'属性来覆盖'some_array'JavascriptArray对象内存,在使用漏洞覆盖auxSlots后的内存状态图中描述如下:
因此,理论上如果想要覆盖数组长度,可以做类似o.e = 0xFFFFFFFF的事情,然后使用some_array [1000]从数组的基地址访问一些远程地址。
但是有几个问题:
因此,需要一些特殊技术来克服上述问题,首先讨论问题1和2,首先想到的是在触发漏洞之前预先在'o'对象中预先定义更多属性。然后,当覆盖auxSlots指针时,我们已经在正确的数组中定义了oe。不幸的是,在提前添加更多属性时,会发生以下两种情况之一:
Bruno Keith在他的演讲中提出了一个很好的想法,一起解决问题1和2。首先破坏预先准备好的具有许多属性的另一个DynamicObject,而不是直接破坏目标对象(在示例中为JavascriptArray),并且已经在内存布局中(#1):
观察运行oc = some_array之前和之后的内存:
现在,执行obj.e = 0xFFFFFFFF实际上会替换'some_array'对象的长度字段。但是,如问题3中所述,该值不会按原样写入。
即使忽略问题3,问题4-5仍然会使我们选择的对象无效,因此,应该选择另一个对象。Bruno巧妙地选择在他的漏洞利用中使用ArrayBuffer对象,但不幸的是,在提交cf71a962c1ce0905a12cb3c8f23b6a37987e68df中,ArrayBuffer对象的内存布局已更改。它不是直接指向数据缓冲区,而是通过bufferContent字段指向一个名为RefCountedBuffer的中间结构,只有这个结构指向实际数据。因此,需要其他解决方案。
最终,想出了破坏DataView对象的想法,该对象实际上在内部使用了ArrayBuffer。因此,它具有与使用ArrayBuffer类似的优点,它也直接指向ArrayBuffer的底层数据缓冲区!下面是使用ArrayBuffer初始化的DataView对象的内存布局(dv = new DataView(new ArrayBuffer(0x100));):
可以看到,DataView对象指向ArrayBuffer对象。ArrayBuffer指向前面提到的RefCountedBuffer对象,然后该对象指向内存中的实际数据缓冲区。
但是,如上所述,观察DataView对象也直接指向实际的数据缓冲区,如果使用自己的指针覆盖DataView对象的缓冲区字段,实际上会根据需要实现所需的R/W。
现在只有问题3 :不能使用损坏的DynamicObject在内存中写入数字,但是由于DataView对象允许我们在其指向的缓冲区上编写普通数字,可以再次受到Bruno的启发,并且有两个DataView对象,其中第一个指向第二个,并且精确地以我们想要的方式来破坏它。
整个开发过程:
这里是完整的EXP代码:
上面就是如何使用DynamicObject的auxSlots的JIT来完成漏洞利用。
我们利用损坏的对象进一步破坏其他感兴趣的对象。——特别是两个DataView
对象,前一个精确地破坏了第二个,从而控制原语地址的选择。我们不得不绕过 javascript’s DynamicObject “API” 中的一些限制。最后,请注意,获得完全读写原语仅仅此漏洞利用的第一步。攻击者仍然需要重定向程序执行流程来获得完成的RCE。
0x05 参考信息
https://www.anquanke.com/vul/id/1449992
https://perception-point.io/resources/research/cve-2019-0539-root-cause-analysis/
https://perception-point.io/resources/research/cve-2019-0539-exploitation/
github博客地址:
https://github.com/streetleague/0xbird.github.io/
个人公众号平台:
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)