chrome浏览器漏洞强化3-CVE-2020-6418
前言
强化中,准备做作业用。
环境搭建
同样去chrome官网的bug页面看看这个bug,找到对应的修复补丁和需要回滚到的漏洞版本。
下载源码开始编译:
1 |
|
漏洞分析
首先看看patch,修改的是node-properties.cc这个文件:
1 |
|
这段代码位于InferReceiverMapsUnsafe函数中,原本代码如下:
1 |
|
这个函数会检查receiver(一般指的是操作的主体)及effect(大概就是主体要做的操作),从而确认后续的effect是否会影响这个主体的Map。这里多加的一行代码给result赋值了kUnreliableReceiverMap,意思大概就是说这个receiver的Map不可信,需要加以检查。
回去阅读漏洞说明:
1 |
|
被修改的InferReceiverMapsUnsafe函数负责通过遍历effect链的方式来推测一个对象的Map,如果是不可信的,caller就需要通过CheckMap或者CodeDependencies节点来确保这个对象的类型是正确的。
此次漏洞的问题在于JIT优化过程在遇到kJSCreate操作时,InferReceiverMapsUnsafe函数会错误地认为其不存在改变对象Map的操作,不会将其推测为不可信,进而在优化代码中不会对其Map进行检查。而实际上,如果使用Proxy作为Reflect.consutruct的第三个参数,然后通过pop函数触发回调函数的方式可以在kJSCreate操作改变对象Map,进而由于缺乏检查的缘故导致了类型混淆漏洞。
在JIT优化过程中,优化pop函数的是ReduceArrayPrototypePop函数,其关键代码如下:
1 |
|
inference就是调用InferReceiverMapsUnsafe函数推测对象Map是否可信的一个变量,其构造函数如下:
1 |
|
RelyOnMapsPreferStability函数就是根据Map是否可信来确定是否需要加入CheckMaps节点进行Map检查,其定义如下:
1 |
|
通过这个类型混淆漏洞能做些什么,可以从官方给出的测试代码中分析一下:
1 |
|
使用release版本运行后可以发现,最后一次pop返回的是一个奇特的数字-858993459。调试一下,在数组a从SMI数组变成Double数组之前,其elements内存布局如下:
1 |
|
每4个字节为一个地址单元,前两个地址单元代表Map、elements数量,后5个地址单元存放SMI数据,一共占据了28字节的空间。
变成Double数组后:
1 |
|
因为数组类型发生了变化,所以v8重新分配了一块内存来存放elements,可以看到elements指针指向的地址发生了变化。此外,由于数组类型从SMI转换为了Double,每个成员的占用空间从4字节变成了8字节,由于缺乏检查的缘故,如果还以SMI的方式访问该数组,就会访问到Double数组a中的a[2]的低32位数据,即0x9999999a,再根据SMI的处理方式右移一位得到最后的结果,我们将-858993459左移一位可以得到其在内存中的形态:
1 |
|
漏洞利用
在紧贴数组a的后方内存处分配一个Double数组,然后通过push进行越界写,我们就能获得一个可以进一步进行更大范围越界读写的Double数组:
1 |
|
可以看到,此时数组b的长度已经被我们覆写成了8160。下一步就是实现任意地址读写,参考文章给出这么一种内存布局方式:
1 |
|
打印一下内存,可以看到除了数组a之外,他们都是排布在相邻内存中的:
1 |
|
我们一步步来看看这些对象都能做到些什么。
obj_leaker - addressOf泄露对象地址
偏移固定,可以将该对象放置在obj_leaker对象中,然后通过数组b越界读取其地址:
1 |
|
结果:
1 |
|
数组c - 伪任意地址读写
通过改写数组c的elements指针,可以基本实现任意地址读写:
1 |
|
结果:
1 |
|
uint64_array - 任意地址读写
BigUint64Array对象类似之前用到过的ArrayBuffer对象,通过改写其指针我们可以实现任意地址读写,其在内存中的布局通过debug打印如下:
1 |
|
其指针由base_pointer和external_pointer两个部分组成,完整的指针由这两个指针相加而成。
在内存中,BigUint64Array布局如下:
1 |
|
可以发现,external_pointer负责地址的高32位再加个7,base_pointer负责地址的低32位再减个7,所以我们只需要修改base_pointer就可以实现任意地址读写:
1 |
|
命令执行
跟之前的一样,通过wasm实现命令执行:
1 |
|
执行结果:
1 |
|
其他问题
在上文使用的漏洞利用代码中,如果将调用f函数的参数empty改为匿名函数function(){},漏洞就会触发失败。
根据参考文章,原因应该跟JIT处理Reflect.construct有,留个坑,懒得继续看了。