文章首发于安全KER https://www.anquanke.com/post/id/226710
0x00 前言
结合Turbolizer来分析34c3ctf-v9的v8逃逸题
0x01 patch分析
1 | diff --git a/src/compiler/redundancy-elimination.cc b/src/compiler/redundancy-elimination.cc |
从patch中可以看到,在redundancy-elimination.cc
源文件的RedundancyElimination::Reduce
函数中增加了一句case IrOpcode::kCheckMaps:
,这样对于checkmaps
节点,也会进行reduce
1 | switch (node->opcode()) { |
看到ReduceCheckNode
函数
1 | Reduction RedundancyElimination::ReduceCheckNode(Node* node) { |
该函数调用LookupCheck(node)
获得新值以后,调用ReplaceWithValue(node, check)
将原节点进行了替换。
继续看到LookupCheck(node)
函数,该函数调用了IsCompatibleCheck
函数,如果函数返回true
,那么就会返回check->node
,从而可以对这个节点进行Reduce
消除
1 | Node* RedundancyElimination::EffectPathChecks::LookupCheck(Node* node) const { |
而IsCompatibleCheck
函数则是被patch过的
1 | bool IsCompatibleCheck(Node const* a, Node const* b) { |
在patch的内容分支上,程序获得两个checkmaps值,如果a_maps
是b_maps
的子集,那么变直接返回true
,这将使得节点b被Reduce
掉
0x02 POC构造
首先构造,我们使用了字典对象,我们仅观察checkmaps的reduce过程
1 | var dict = {a:1.1}; |
其中为了防止opt
函数被直接内联到for语句里,我们在里面增加了一句var y = new Array(0x10);
,在代码里,按理来说,var x = obj_dict.a;
和return obj_dict.a;
都应该有一个checkmaps
节点用于进行类型检查。我们还需要先弄清楚RedundancyElimination::ReduceCheckNode
函数调用者是来自哪里,因此,我们在该函数下断点,然后用gdb调试。
最终发现,该阶段发生在LoadEliminationPhase
阶段。接下来,结合IR图来进行验证,运行d8时加入选项--trace-turbo
。
在Loops peeled 95
阶段,43这个节点checkmaps
还存在
然后到了下一个阶段,也就是Load eliminated 95
阶段,43节点的checkmaps
被reduce了
如果把patch去掉,发现在Load eliminated 95
阶段是不会把checkmaps
给去掉的
根据上述结论,我们进一步构造
1 | var dict = {a:1.1}; |
发现没有发生类型混淆,生成IR图进行观察,最后发现
最后发现return obj_dict.a;
已经是按照对待HOLEY_ELEMENTS
类型的方式将值取出的方式。由此,我们考虑加一个函数调用,使得字典对象逃逸,这样由于return obj_dict.a;
的checkmaps在Load eliminated 95
阶段会被移除,而Escape Analyse
阶段在Load eliminated 95
阶段之后,那么就可以造成类型混淆
1 | var dict = {a:1.1}; |
测试发现确实发生了类型混淆
1 | root@ubuntu:~/Desktop/v8/34c3ctf-v9/x64.debug# ./d8 poc.js |
分析IR图
可以看到,在调用完函数后,没有对dict的类型重新进行检查,那么,我们在函数里改变了dict里a属性的类型,但是代码仍然用的是对待原来double elements
的方式来取出值,由此发生类型混淆
0x03 漏洞利用
首先构造addressOf
原语
1 | function addressOf_opt(dict,f) { |
然后构造fakeObject
原语
1 | function fakeObject_opt(dict,f,addr) { |
在构造fakeObject原语时,在fakeObject_opt
时,我们没有直接返回dict.b
而是返回dict
对象,因为我们在前一句有dict.b = addr;
,在与return
之间没有进行其他逃逸操作,因此直接返回dict.b
会在Escape Analyse
阶段折叠掉。
构造好这两个原语以后,就是常规利用了
exp
1 | var buf = new ArrayBuffer(0x8); |
0x04 参考
从一道CTF题零基础学V8漏洞利用
redundancy elimination reducer in v8 and 34c3 ctf v9
0x05 感想
在v8的JIT代码生成过程中,会使用IR来分析程序并且进行优化,v8的IR图使用sea of node
思想,其中checkmaps
节点是用来做deoptimization
的依据,checkmaps
节点用于检查对象类型是否符合,如果符合,则直接执行接下来的JIT代码,否则会使用deoptimization
,以确保类型正确。