Android Frida 框架

一、Frida框架简介

  • Frida是一款基于Python + JavaScript 的hook框架,本质是一种动态插桩技术。可以用于Android、Windows、iOS等各大平台,其执行脚本基于Python或者Node.js写成,而注入代码用JavaScript写成,所以有必要了解一些这些语言的语法。本文主要讲述了Android上Frida框架的使用。

二、Android Frida环境搭建

  • 使用Frida需要Python 3环境,在Windows上目前预编译的版本需要Python3.7。关于Python的安装这里就不再赘述。同时你还需要安装好pip。
  • 然后就可以安装frida了,使用如下的命令:
pip install frida frida-tools
  • 除此之外,我们还需要下载Frida的Server端,可以从Github上下载:Releases frida/frida。这里我们选择Android的版本,arm是32位而arm64是64位,还有x86版本提供,根据手机的类型下载。
  • 下载解压之后要将Server端推送到手机上,和IDA的Debug Server类似,我们将其推送至/data/local/tmp/目录:
adb push frida-server-12.5.0-android-arm64 /data/local/tmp/
  • 类似地,为其赋予执行权限,并使用root身份启动它。
$ cd /data/local/tmp/
$ su
# chmod +x frida-server-12.5.0-android-arm64
# ./frida-server-12.5.0-android-arm64
  • 如果不想使用命令行工具,也可以尝试一下我自己封装的FridaHooker,它提供了一种利用图形化界面管理frida的方法:wrlu/FridaHooker: Android平台可视化Frida管理工具

  • 启动后,在电脑上执行命令,以连接到USB上的frida server:

frida-ps -Ua
  • 如果能成功显示出进程列表,那么就证明启动成功。如果显示不出,说明是Server端没有启动,如果只能显示出两三个进程,说明没有以root身份启动Server端。不过有时候会显示出Windows的进程列表,这种情况最好使用Python绑定使用frida。
  • 同时这里还支持连接远程frida server,可以使用如下命令:
frida-ps -H

三、Frida Hook Android Java代码

  • Frida程序使用Python写成,可以使用IDLE也可以用PyCharm,这里不再叙述。示例代码如下:
import sys
import frida
js_file_name = 'Hook.js'
process_name = 'com.xxx.xxx'
# 自定义回调函数
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)
def get_js_code():
    js_file = open(js_file_name, 'r')
    return js_file.read()
if __name__ == '__main__':
    # 附加到进程并得到进程对象
    process = frida.get_usb_device().attach(process_name)
    # 指定JavaScript脚本
    script = process.create_script(get_js_code())
    # 加载JavaScript脚本
    script.on('message', on_message)
    script.load()
    # 读取返回输入
    sys.stdin.read()
  • 该脚本首先获取了USB设备,也就是我们连接的手机。然后附加到了我们指定的进程上,这里是com.xxx.xxx,所以在执行脚本之前要先在手机上启动好欲hook的目标程序。然后加载了JavaScript脚本并获取了其返回的内容。
  • JavaScript脚本中将实现具体的Hook功能,内容如下:
Java.perform(function () {
    var LogUtil = Java.use('com.xxx.xxx.xxx.LogUtil');
    LogUtil.isDebug.implementation = function () {
        send('Hooking isDebug method');
        return true;
   }
});
  • 这里是要Hook一个Java方法,首先使用Java.use找到Java的类,然后调用方法的implementation来实现Hook,提供一个回调函数,这个函数将覆盖原有的函数。本例中是将该方法的返回值强制修改为true,这样就完成了Hook。
  • 有时候Hook会超时,所以可以把上述代码放在以下的代码块里,但是不是必须的:
setImmediate(function() { ... });
  • frida允许你一次性Hook多个进程,也可以一次性注入多个脚本,所以假如有m个进程,n个脚本,每次就可以执行m * n个注入任务。但是要注意的是如果注入的进程或脚本过多,可能会导致手机崩溃。下面是我的一个示例代码,提供了较为完善的脚本加载和注入功能,源代码详见:wrlu/Frida-Hook-Android: Android平台的Frida Hook工程
import sys
import frida
js_file_names = ['TLS']
process_names = [
    'com.huawei.hwid',
    'com.huawei.hdpartner'
]
def raise_send(msg):
    print('[Send] ' + msg)
def raise_info(msg):
    print('\033[0;32m[Info] ' + msg + '\033[0m')
def raise_warning(msg):
    print('\033[0;33m[Warning] ' + msg + '\033[0m')
def raise_error(msg):
    print('\033[0;31m[Error] ' + msg + '\033[0m')
def on_message(message, data):
    if message['type'] == 'send':
        raise_send(message['payload'])
    elif message['type'] == 'error':
        raise_error(message['description'])
    else:
        raise_error(message)
if __name__ == '__main__':
    try:
        raise_info('Current frida version: '+str(frida.__version__))
        manager = frida.get_device_manager()
        devices = manager.enumerate_devices()
        for ldevice in devices:
            raise_info('Device discovered: '+str(ldevice))
        # Google Pixel
        device = manager.get_device('FA74D0301125')
        # Huawei Nexus 6P
        # device = manager.get_device('84B5T15B03006088')
        raise_info('Connect to the target device successfully: '+str(device))
        front_app = device.get_frontmost_application()
        raise_info('Front Application on the device: '+str(front_app))
        if front_app.identifier not in process_names:
            raise_warning('Device front application is different from all the hooked application ( '+front_app.identifier+' != '+str(process_names)+' )')
        for process_name in process_names:
            process = device.attach(process_name)
            for js_file_name in js_file_names:
                process_name_var = 'var _pname = "'+process_name+'";'
                raise_info('Inject script name: ' + js_file_name + 'Android.js')
                script = process.create_script(process_name_var + open('Hook' + js_file_name + 'Android.js').read())
                script.on('message', on_message)
                raise_info('Load script name: Hook' + js_file_name + 'Android.js')
                script.load()
        raise_info('Waiting for scripts...')
        sys.stdin.read()
    except frida.InvalidArgumentError as e:
        raise_error('Invalid argument, maybe a JavaScript syntax error or device disconnected. Details: '+repr(e))
    except frida.ProcessNotFoundError as e:
        raise_error('Cannot find the target process, please check your application status. Details: '+repr(e))
    except frida.ServerNotRunningError as e:
        raise_error('Frida server is not running, please check if it is not start or crash. Details: '+repr(e))
    except Exception as e:
        raise_error('Unknown error or exception. Details: ' + repr(e))

四、Frida Hook Android Native方法

  • 到以上内容显示出Frida的优势只是不需要重启手机,比较方便。但是Frida还可以完成Xposed不能完成的任务,那就是来Hook Native方法,也就是so动态库中的C/C++方法。示例代码如下:
Interceptor.attach(Module.findExportByName("libc.so" , "open"), {
    onEnter: function(args) {
        log("open() called!")
    },
    onLeave:function(retval){
    }
});
  • 这里面和Java层Hook有些类似,使用Interceptor.attach来附加到动态库,使用Module.findExportByName来找到要Hook的函数,第一个参数是模块名称,第二个参数就是函数名。里面的代码和Java层Hook比较相似。

五、Frida CLI

  • 就像Scapy这样的框架一样,Frida也是可以通过CLI访问的,要完成上面Python脚本的内容,可以用以下的命令代替:
frida -U -l [JavaScript脚本] [进程名]
frida -H [主机名:端口] -l [JavaScript脚本]
# e.g.
frida -U -l ./hook.js com.huawei.xxx
frida -H 192.168.3.2:27042 -l ./hook.js com.huawei.xxx

六、使用Python连接远程frida server

  • frida使用的是TCP协议进行连接,也支持连接远程的frida server,在Python中有一个get_remote_device()方法,后来发现这个函数默认连接的是127.0.0.1:27042,而且不能更改。后来才发现应该用如下的方法:
host = '192.168.3.2:27042'
manager = frida.get_device_manager()
remote_device = manager.add_remote_device(host)
remote_device.get_process(process_name)
remote_process = remote_device.attach(process_name)

七、后续

  • 本文介绍了Frida在Android Hook上的应用,实际上Frida还可以Hook其他系统的代码,有时间会再撰写文章进行讲解。