一、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其他系统的代码,有时间会再撰写文章进行讲解。