2020-02-25

Android Hook框架Frida实践Part2-自动化安全检测

1. Android Hook框架Frida实践Part2-自动化安全检测

在上一章节中,我们初步了解了Frida安装和使用,并且进行简单的hook测试,这个章节,我们将通过实际的例子,来完成自动化安全检测。

1.1. Frida API

Frida官方文档:https://frida.re/docs/home/

Frida官方文档中提供的API接口包括 JavaScript, C, Swift, 但是具有详细文档的只有 JavaScript.

JavaScript接口相当丰富了,接口功能包括但不限于进程操作、模块操作、内存操作、函数操作、线程操作、网络通信、数据流操作、文件操作、数据库操作、寄存器操作, 并且官网有着详细的说明文档(https://www.frida.re/docs/javascript-api/),对于每个接口的功能和参数给出了详细的解释,并且一部分接口还给出了例子。

除了官方文档提到的API接口以外,其实还提供了一套Python接口,Python接口提供的功能很少,而且基本都是用来获取进程、模块、函数的信息的。

其中部分接口:

# 启动应用,应用处于挂起状态
def spawn(*args, **kwargs):
    return get_local_device().spawn(*args, **kwargs)

# 恢复应用运行状态
def resume(target, **kwargs):
    get_local_device().resume(target, **kwargs)

# 结束应用
def kill(target, **kwargs):
    get_local_device().kill(target, **kwargs)

# 附加到目标进程中
def attach(target, *args, **kwargs):
    return get_local_device().attach(target, *args, **kwargs)

# 获取连接的USB设备
def get_usb_device(timeout=0, **kwargs):
    return get_device_matching(lambda d: d.type == 'usb', timeout, **kwargs)

# 创建javascript脚本
def create_script(self, *args, **kwargs):
   return Script(self._impl.create_script(*args, **kwargs))

更多接口请查看源码:frida-python

下边我们尝试使用Python接口枚举所有进程:

import frida

device = frida.get_usb_device()
for process in device.enumerate_processes():
    print(process)

执行以后,终端就会打印出所有的进程:


> python testing.py

Process(pid=1, name="init", parameters={})
Process(pid=146, name="init", parameters={})
Process(pid=148, name="ueventd", parameters={})
Process(pid=173, name="logd", parameters={})
Process(pid=174, name="lmkd", parameters={})
Process(pid=176, name="servicemanager", parameters={})
Process(pid=181, name="hwservicemanager", parameters={})
Process(pid=182, name="sh", parameters={})
Process(pid=183, name="qemu-props", parameters={})
Process(pid=186, name="vold", parameters={})
Process(pid=194, name="android.hardware.keymaster@4.1-service", parameters={})
Process(pid=219, name="android.system.suspend@1.0-service", parameters={})
Process(pid=220, name="android.hardware.atrace@1.0-service", parameters={})
Process(pid=287, name="createns", parameters={})
Process(pid=292, name="tombstoned", parameters={})
Process(pid=298, name="statsd", parameters={})
...


1.2. 自动化测试

上一章节中我们初步使用了frida内置的工具来进行简单的测试,下边我们将通过编写代码来实现自动化测试。

测试代码依然是hook open函数,python代码如下:

   import sys
   import time
   import frida


   js_code = """
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
  onEnter: function (args) {
    console.log('open called args[0] address: ' + args[0] + ', value: ' + Memory.readUtf8String(args[0]));
  }
});
    """
   device = frida.get_usb_device()
   pid = device.spawn('com.android.chrome')
   device.resume(pid)
   time.sleep(3)
   session = device.attach('com.android.chrome:privileged_process0')
   script = session.create_script(js_code)
   script.load()
   print('starting...')
   sys.stdin.read()

这里的hook部分实现使用的是JavaScript API,相关API用法请参考官方文档。

执行过后,控制台将显示出open函数的参数内容,如下所示:



> python testing.py
starting...
open called args[0] address: 0xd342f290, value: /proc/self/statm
open called args[0] address: 0xd3428ab0, value: /proc/29242/status
open called args[0] address: 0xd3437ea0, value: /proc/29242/stat
open called args[0] address: 0xd3437ea0, value: /proc/self/status
open called args[0] address: 0xd3437ea0, value: /proc/self/clear_refs
open called args[0] address: 0xb5c3e332, value: /proc/self/pagemap
open called args[0] address: 0xd3425db0, value: /proc/self/statm
open called args[0] address: 0xd34317b0, value: /proc/29242/status
open called args[0] address: 0xd34260b0, value: /proc/29242/stat
open called args[0] address: 0xd34260b0, value: /proc/self/status
open called args[0] address: 0xd34260b0, value: /proc/self/clear_refs
open called args[0] address: 0xb5c3e332, value: /proc/self/pagemap

上边的示例中,hook的数据通过 console.log 打印在了控制台中,如果我们要记录每次的参数数据,后续做二次分析, 我们可以在 script.load() 之前使用script对象的on方法,捕捉js脚本输出的信息。 Js脚本提供了向主控端发送数据的接口 send, 而在主控端是通过python脚本的回调来接收数据,使用方法如下:

    js_code = """
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
  onEnter: function (args) {
    // console.log('open called args[0] address: ' + args[0] + ', value: ' + Memory.readUtf8String(args[0]));
    send('open args[0]: ' + Memory.readUtf8String(args[0]));
  }
}); 
    """

    def on_message(message, data):
        print('recv: {}'.format(message))

    device = frida.get_usb_device()
    pid = device.spawn('com.android.chrome')
    device.resume(pid)
    time.sleep(3)
    session = device.attach('com.android.chrome:privileged_process0')
    script = session.create_script(js_code)
    script.on("message", on_message)
    script.load()
    print('starting...')
    sys.stdin.read()

执行后,控制台将打印接受的数据:


> python testing.py

starting...
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/statm'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/status'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/stat'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/status'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/clear_refs'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/pagemap'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/statm'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/status'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/stat'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/status'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/clear_refs'}
recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/pagemap'}


至此,我们就完成了初步的自动化测试环境,后续可以按照不通的需求,来实现更强大的功能。

1.3. 安全检测

在尝试了自动化测试之后,我们如何将这种流程应用到实际的漏洞检测当中呢,下边以漏洞检测和漏洞挖掘两个方面来进行实践。

  1. Wormhole漏洞检测

    Wormhole 是百度 moplus SDK 的一个后门, 当用户启动一个应用程序, Moplus SDK 会偷偷地在设备上自动设置一个本地HTTP服务器,用来监控通过socket的消息。

    该部分通过反汇编定位代码位置为 com.baidu.hello.patch.moplus.nebula.b.a

    help_frida_usage_baidu_01.png

    Moplus SDK 被包含在一个独立的进程中,服务名为 com.baidu.android.moplus.MoPlusService, 可以通过不同的广播事件触发,其中包括系统启动的广播。

    与其说这是一个漏洞,倒不如说这是百度自己留下的一个后门,百度全系APP连同使用了百度地图的APP纷纷中枪,影响用户同样过亿。

    该后门之流氓程度与XcodeGhost相比有过之而无不及,包括静默下载任意文件、后台上传用户文件等各种流氓行径。

    help_frida_usage_baidu_02.png

    help_frida_usage_baidu_03.png

    该后门通过在本机利用socket开了一个简易的WebServer,绑定的端口包括 6259 (com.ufo.dcb.lingyi), 40310 (com.baidu.BaiduMap), 等等。

    所以我们只需要检测应用启动是否存在绑定端口 625940310 的行为即可。

    应用要绑定端口,最终都会调用到libc层的bind函数,我们看下bind函数的参数:

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    ipv4:   AF_INET = 2
      struct sockaddr_in {
        __kernel_sa_family_t  sin_family;
        __be16                sin_port;
        struct in_addr        sin_addr;
      };
    
    ipv6:   AF_INET6 = 10
      struct sockaddr_in6 {
        sa_family_t     sin6_family;
        in_port_t       sin6_port;
        uint32_t        sin6_flowinfo;
        struct in6_addr sin6_addr;
        uint32_t        sin6_scope_id;
      };
    
    

    我们通过解析第二个参数,即可解析出绑定的端口,然后判断即可。

    JavaScript脚本代码如下所示:

    Interceptor.attach(Module.findExportByName('libc.so', 'bind'), {
        onEnter: function(args) {
            console.log('sockfd: ' + sockfd);
    
            var sin_port = Memory.readU8(sa.add(2)) * 0x100 + Memory.readU8(sa.add(3));
            var sin_ip = Memory.readU8(sa.add(4)) + '.' + Memory.readU8(sa.add(5)) + '.' + Memory.readU8(sa.add(6)) + '.' + Memory.readU8(sa.add(7));
            console.log('bind to ' + sin_ip + ':' + sin_port);
    
            if(sin_port) {
                var VUL_PORT = [6259, 40310];
    
                if(VUL_PORT.indexOf(parseInt(sin_port) >= 0) {
                    console.log('wormhole found!');
                }
                  }
            }
        });
    
    

    之后我们找到带漏洞版本的应用,就可以进行检测了,检测结果如下所示:

    
    > python testing.py
    
    sockfd: 2
    bind to 10.0.2.16:50662
    sockfd: 3
    bind to 10.0.2.16:42856
    sockfd: 7
    bind to 127.0.0.1:40310
    wormhole found!
    ...
    
    
  2. 某APP漏洞挖掘

    除了检测常规漏洞外,frida还可以辅助我们进行漏洞挖掘,先看下边一段反汇编代码:

    help_frida_usage_vul_buf.png

    该部分代码源于某APP中调用Native层so库的导出函数,其中存在bug的代码为:

    if ( a1[1] == 0x45 && a1[2] == 0x41 && a1[3] == 0x44 )
      {
        puts("buf[]=DEAD");
        MEMORY[0xDEADBEEF] = 1;
      }
    

    我们触发第32行代码 MEMORY[0xDEADBEEF] = 1 就会造成内存非法访问。

    需要满足的条件为 a1[1] = 0x45 && a1[2] = 0x41 && a1[3] == 0x44

    参数 a 为该函数 vul_func_buf 传入的一个是指针类型的buffer,因此我们只要随机生成buffer进行测试即可。

    该过程实现的javascript代码如下:

    var target_so = 'lib-xxx.so';
    var target_func = 'vul_func_buf';
    
    var target_address = Module.findExportByName(target_so , target_func);
    console.log('[+] target address: ' + target_address);
    
    var func_handle = new NativeFunction(target_address, 'int', ['pointer', 'int']);
    console.log('[+] func handle:    ' + func_handle);
    
    function getString(num) {
        // a1[1] == 0x45 && a1[2] == 0x41 && a1[3] == 0x44
        var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
        return chars.charAt(Math.floor(Math.random() * chars.length)) +
            String.fromCharCode(0x45) + String.fromCharCode(0x41) + String.fromCharCode(0x44);
    }
    
    var index = 0;
    
    while (true) {
        var test_str = getString(index);
        console.log(index + ': ' + test_str);
        var p_str = Memory.allocUtf8String(test_str);
    
        try {
            var p_str_ret = func_handle(p_str, 4);
        } catch(err) {
            console.log('[+] ' + 'bugs found! ' + err);
            break;
        }
        index += 1;
    }
    
    

    改代码的核心部分在于frida用于调用c层函数的API NativeFunction(address, returnType, argTypes[, abi])

    • address:要hook的函数地址
    • returnType:返回值类型
    • argTypes[, abi]: 参数类型 这里参数可以是多个

    参数说明详见官方API:NativeFunction

    执行以后,大概几秒就会找出满足bug的字符串,如下图所示:

    help_frida_usage_find_bugs.png

    至此本章节内容暂时结束,在安全检测中,尤其是在漏洞挖掘方向,frida的能力远不止如此。

    如上边的漏洞挖掘过程中,我们为了触发漏洞位置,通过部分逆向分析加上字符串随机的方式进行测试,虽然能够成功拿到case,

    但是在实际环境中,对于复杂函数的逆向分析是非常困难的,并且字符串随机效率也是个问题,单纯的随机根本无法触发复杂的代码路径,除了浪费CPU资源没什么实际意义。

    在详读frida官方文档之后我们发现,frida内置了一套代码跟踪引擎 Stalker,

    通过这套代码跟踪引擎我们就可以对代码块跟踪并统计覆盖率,然后结合AFL数据变异规则,一套强大且高效的真机Fuzz就可以实现了。

    该部分内容计划在后续章节中逐渐讲解,敬请期待!

Author: idhyt

Created: 2020-02-25 Tue 18:51

Validate

没有评论:

发表评论

Android Root Zap Framework

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