最近复现了一些v8的CVE,想找一题v8相关的CTF题目来试一试自己能不能独立的写出exp来,于是想了想就找到了当时瞎几把复现的题目:OmniTmizer,一道与turbofan JIT的escape analysis相关的题目
题目分析
先来看看patch:
1 | https://github.com/v8/v8/tree/50dd84ca317ae35c926ed34d001a72b62aea6662 |
patch中给了commit id,先编译一个一样的没打补丁的版本:
1 | git reset --hard 50dd84ca317ae35c926ed34d001a72b62aea6662 |
可以看到该patch是打在src/compiler/escape-analysis.cc的ReduceNode函数,先找找是谁调用了该函数
先从src/compiler/pipeline.cc开始找起:
1 | bool PipelineImpl::OptimizeGraph(Linkage* linkage) { |
Run<EscapeAnalysisPhase>()
函数会调用到EscapeAnalysisPhase::Run
:
1 | struct EscapeAnalysisPhase { |
EscapeAnalysisPhase::Run
函数会先在[1]处对graph进行一次reduce:
1 | // EscapeAnalysisPhase 继承自EffectGraphReducer,reduce函数使用的是EscapeAnalysis::Reduce(从构造函数中可以得知) |
[2]处的ReduceNode就是被打patch的函数
初次尝试
为了查看被打patch的函数对kCheckMaps的行为,一开始写了个如下的测试代码:
1 | function foo(a){ |
查看生成的graph:
在和打了patch的版本进行对比:
可以看到打了patch的版本的CheckMaps节点被删除了,回到打了patch之后的代码:
1 | case IrOpcode::kCheckMaps: { |
markForDeletion会把当前节点替换成Dead节点,在这里是等同于把CheckMaps节点删除了
CheckMaps节点一般是用来保证对象的Map不会发生变化,如果发生变化了就进行deopt,这里在EscapeAnalysis阶段把CheckMaps给删除了,那是不是说我们可能可以类型混淆了
一开始我试了个这个:
1 | function foo(a){ |
运行了下:
1 | PS D:\shared\pwn2win_2020\pwn-OmniTmizer\files\x64.release> .\d8.exe .\test.js |
我猜是zoo的length,因为从上面的LoadField[+12],而Array对象的+12处偏移(开启了指针压缩)正是length,也就是4
再改改zoo的长度,看看输出:
1 | // var zoo = [1.1,2.2,3.3,4.4,5.5]; |
猜测正确
addrof
那我们可以直接根据上面的代码改一改,构造一个addrof原语:
1 | function foo(a){ |
输出:
1 | PS D:\shared\pwn2win_2020\pwn-OmniTmizer\files\x64.release> .\d8.exe --allow-natives-syntax .\test.js |
转换一下最后的输出:
1 | >>> hex(struct.unpack("<q",struct.pack("<d",5.757981678926115e-270))[0]) |
可以看到我们成功的把zoo的地址泄露了出来,嘿嘿
封装一下:
1 | var format_buffer = new ArrayBuffer(16); |
想要伪造一个对象,我们需要泄露出相对应对象的map,但是前面的addrof只能泄露变量的地址,做不到泄露出对象的map
这里卡住了,,,
思来想去,想到一开始我们不是把Array.length当作a.x返回了,那我直接写它是不是等于把Array.length直接给改了,带着这个思路:
1 | function foo(a){ |
输出:
还真可以,那就等于是越界了,那就不搞addrof和fakeobj,直接去写ArrayBuffer的backing_store岂不美滋滋
带着这个思路,我们在oob的数组后面分配ArrayBuffer和BigUint64Array(用于泄露external_pointer),修改ArrayBuffer的backing_store来达到任意地址读写的目的,最后exp:
1 | var format_buffer = new ArrayBuffer(16); |
后记
其实这题和EscapeAnalysis阶段的操作好像没什么相关的,感觉只是借这个阶段来把checkMaps节点来消除。
我后面去看了下别人的分析文章,发现Array对象的前2个内存布局(map,property)居然可以硬编码,orz,这样就不用想尽脑汁去泄露map了
参考资料
Escape Analysis in V8 :https://www.youtube.com/watch?v=KiWEWLwQ3oI&t=744s
2020 Pwn2Win OmniTmizer Challenge:https://yichenchai.github.io/blog/omnitmizer