以下是对2021年chrome的issue-1196683和issue-1195777进行的粗略的分析(主要是对v8没有那么的熟悉)
环境搭建
编译一个8.x版本的v8(反正找一个2021年4月份前的),还需要准备好turbolizer:
| 1 | cd tools/turbolizer | 
接着在浏览器访问:http://127.0.0.1:8000/ 就可以看到turbolizer了
issue-1196683
相关链接:https://chromium-review.googlesource.com/c/v8/v8/+/2820971
我们稍微改改其提供的regress-1196683.js :
| 1 | const arr = new Uint32Array([2**31]); | 
运行一下:
| 1 | >./d8 issue-1196683.js | 
正常情况下结果应该是-2147483647,但是其在生成优化代码后变成了2147483649
用turbolizer来看看优化过程,先生成其json文件:
| 1 | ./d8 issue-1196683.js --trace-turbo | 
用turbolizer打开生成的json文件(turbo-foo-0.json)
TyperPhase

点箭头标记的那个三角,可以显示出与该异或操作相关的节点(30),其输入的两个节点为常数0(节点22)和arr[0](节点44)
SimplifiedLoweringPhase

在SimplifiedLowering阶段,原先在Typer阶段的结点30:SpeculativeNumberBitwiseXor被优化成了Word32Xor节点,而且与patch相关的ChangeInt32ToInt64节点也出现了,patch:

EarlyOptimizationPhase

到了EarlyOptimization阶段,原本的Word32Xor节点已经被Reduce了,导致原本ChangeInt32ToInt64节点的输入类型由原本的Signed32(见SimplifiedLoweringPhase)变成了Unsigned32,和这里的逻辑相关的代码为:
| 1 | template <typename WordNAdapter> | 
我们的情况真好是 x^0 , 所以用左边节点来替换当前节点,导致了类型由Signed32变成了Unsigned32
schedule

再到register allocation:
register allocation

imm:20 的值为1,红框对应的就是我们js代码中的 (arr[0] ^ 0 + 1)
这里应该是对应了patch中的指令选择阶段,我们的输入类型由于是Unsigned32,所以在指令选择阶段选择了kX64Movl指令,而不是会做符号拓展的kX64Movsxlq指令,导致结果变成了无符号的0x80000000+1也就是2147483649
我们还可以调试一下,给代码加一个断点:
| 1 | const arr = new Uint32Array([2**31]); | 
加一个--print-opt-code来确定优化代码的位置并下断点:
| 1 | pwndbg> r --allow-natives-syntax --print-opt-code ./issue-1196683.js | 
下好断点跳过去查看:

后续的add rcx,1 也就是我们的返回结果了
issue-1195777
相关链接:https://chromium-review.googlesource.com/c/v8/v8/+/2826114
我们稍微改改其提供的regress-1195777.js :
| 1 | function foo(b){ | 
运行之后:
| 1 | >./d8 issue-1195777.js | 
可以看到在优化后,返回了一个错误的false,关键逻辑是在 -1 < Math.max(0, x, -1),用turbolizer来查看其优化过程
TyperPhase

这是与-1 < Math.max(0,x,-1)相关的节点
TypeLoweringPhase

原本的53和56节点被替换成常数0(节点31)和-1(节点13)
SimplifiedLoweringPhase

到了SimplifiedLowering阶段,NumberMax节点被替换成了Int64LessThan 和Selec节点,SpeculativeNumberLessThan被替换成了Int32LessThan;还可以看到最后生成了TruncateInt64ToInt32节点,该节点就是漏洞触发的地方,我们查看patch:

但是调试的时候无法断到此处,先留个坑在这。。。
Math.max(0, x, -1)的结果被truncate成了Signed32,导致会计算出错误的结果
漏洞利用
经过上面的分析,这两个漏洞都能使我们获得一个错误的值为1的变量。通过分析在野利用的exp,利用步骤为:
- 用该错误的变量作为长度创建一个Array
- 通过Array.prototype.shift来使上一步创建的Array的长度变成0xFFFFFFFF
第二步的利用方法在4月份的时候也被谷歌修了:https://chromium.googlesource.com/v8/v8/+/d4aafa4022b718596b3deadcc3cdcb9209896154
poc1:
| 1 | function foo() { | 
poc2:
| 1 | // use issue-1195777 | 
可以看下其生成的优化代码:

有两处对长度的赋值,分别对应flag == true 和 flag == false ,第二处的赋值直接把0xfffffffe(Int32(0xffffffff << 1))赋值给长度,所以我们能得到一个长度为-1的数组,具体为啥会这样还得等后面熟悉了再回头看看。。。
后续步骤:
- 修改corrupt_arr的length
- 利用oob_arr和corrupt_arr来构造addrof和fakeobj原语
- 泄露出corrupt_arr的map
- 申请一个FixedDoubleArray对象A,布置好其内容(为下一步伪造对象做准备)
- 利用fakeobj原语fake一个FixedDoubleArray对象B
- 利用对象A和B实现任意地址读写
具体代码:
| 1 | var buf = new ArrayBuffer(16); | 
调试的时候注意有指针压缩
参考链接
https://iamelli0t.github.io/2021/04/20/Chromium-Issue-1196683-1195777.html
https://doar-e.github.io/blog/2019/01/28/introduction-to-turbofan/#control-edges
https://github.com/r4j0x00/exploits/blob/master/chrome-0day/exploit.js
