文章目录
- 简介
- 插桩技术
- 环境需求
- 环境配置
- windows安装frida客户端:
- Android安装frida服务端
- 问题1:模拟器与真机
- 问题2:版本一致
- 实战案例:反编译nice app,找到hook点
- 分析源码
- 编写脚本
- 运行脚本
- frida(反调试)被检测
- 客户端使用方式
- frida相关用法
- 获取进程
- hook脚本
- Python脚本运行通用样例
- js脚本用法示例
- 基本使用
- 可能遇到的问题
简介
Frida是一款基于python + javascript的hook框架,可运行在android、ios、linux、windows等各平台,主要使用动态二进制插桩技术;由于是基于脚本(javascript)的交互,因此相比xposed和substrace cydia更加便捷
官网地址:https://frida.re/
官方文档:https://frida.re/docs/home/
github地址:https://github.com/frida/frida
https://github.com/frida/frida-python
其它GitHub文档:
https://github.com/WooyunDota/DroidSSLUnpinning https://github.com/WooyunDota/DroidDrops/blob/master/2018/Frida.Android.Practice.md
插桩技术
插桩技术是指将额外的代码注入程序中以收集运行时的信息,可分为两种:
- 源代码插桩[Source Code Instrumentation(SCI)]:额外代码注入到程序源代码中。
- 二进制插桩(Binary Instrumentation):额外代码注入到二进制可执行文件中。
- 静态二进制插桩[Static Binary Instrumentation(SBI)]:在程序执行前插入额外的代码和数据,生成一个永久改变的可执行文件。
- 动态二进制插桩[Dynamic Binary Instrumentation(DBI)]:在程序运行时实时地插入额外代码和数据,对可执行文件没有任何永久改变。
环境需求
- python使用版本python3.7:https://www.python.org/
- mumu android模拟器:http://mumu.163.com/baidu/
- adb驱动:https://adb.clockworkmod.com/
- jadx-gui(apk反编译):http://www.pc6.com/softview/SoftView_579787.html
- apkhelper v3.0(应用包名等查看):http://www.pc6.com/softview/SoftView_472196.html
- vscode编辑器(主要用于源码分析):https://code.visualstudio.com/
- windows10 64位
- nice app(实战使用的app):https://www.wandoujia.com/apps/2604261
环境配置
Frida的安装很简单,需要在windows安装frida客户端和在安卓安装frida服务端。
windows安装frida客户端:
pip install frida-tools
pip install frida
其它版本下载:https://pypi.doubanio.com/simple/frida/
Android安装frida服务端
下载 : https://github.com/frida/frida/releases相应的版本
我用的是mumu模拟器所以下载的是frida-server-12.8.6-android-x86.xz
下载后解压(注意下载是压缩包,要解压一下)
使用adb连接mumu模拟器(注意打开root权限及usb调试模式)
adb connect 127.0.0.1:7555
关闭指令为
adb disconnect 127.0.0.1:7555
将上面的解压得到的文件传送至模拟器上到/data/local/tmp
目录,并命名为frida-server
adb push frida-server-12.8.6-android-x86 /data/local/tmp/frida-server
进入adb shell并设置frida-server的权限
使用./frida-server
启动服务
之后另外开启一个命令窗口运行命令:frida-ps -U
(查看运行的进程),使用frida-ps -U | grep frida
可以看到手机端有无frida程序运行 (windows系统下使用 frida-ps -U | findstr frida
)
出现上图说明服务起成功
如果看到下面这样frida-ps 不是内部或外部命令,需要安装frida-tools,命令:pip install frida-tools
最后将手机端的端口转发到PC端进行通信
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
指定手机端口转发,97dc2d9
为手机driver名称
os.system("adb -s 97dc2d9 forward tcp:27042 tcp:27042")
os.system("adb -s 97dc2d9 forward tcp:27043 tcp:27043")
问题1:模拟器与真机
如果目标设备是真机,那么请提前把设备root。如果是模拟器,先把root权限打开。
打开终端,使用命令 adb devices 查看设备是否被adb检查到了
如果没找到,先执行这条命令 adb kill-server,再去查找一般都能找到。
查看手机架构,adb shell cat /proc/cpuinfo
,可以看到我是64位的
或者可以用 adb shell getprop ro.product.cpu.abi
获取cpu处理器位数:. armeabi-v7a
(32位ARM设备). arm64-v8a
(64位ARM设备)
查看内核
一个小坑,真机的arm架构和模拟器的x86差异,如果是真机需要下载-arm64.xz文件,模拟器则是-x86.xz,下载解压后,使用adb连接机器,使用push将frida-server放到手机目录/data/local/tmp
,然后修改属性为可执行,以root权限启动。
可以看到如果模拟器使用的是arm类型文件,则会报错:
Not executable: 64-bit ELF file
换成-x86 server成功运行:
问题2:版本一致
frida-server版本要和frida版本一致,否则调用js脚本会报错,pip安装客户端一般是最新版本,在GitHub上下载server版本也要选择最新版本。
实战案例:反编译nice app,找到hook点
使用jdax-gui工具打开nice-main-5.4.29-release.apk
(反编译时间过长,耐心等待…),反编译完点击jdax-gui的File—> Save as gradle project
将源码保存在本地,并使用vscode打开本次实战hook点是使用已有账登录,输入账号密码后,点登录获取账号密码信息
Fiddler代理设置请自行百度
通过Fiddlert查看,每获次登录的请求响应如下
由上图片得出请求路径:/account/login
分析源码
vscode中全文搜索/account/login
,在bil.java中a方法中找到
由此得见,我们从a方法就是我们的hook点,a方法的参数jsonObject str即是我们要获取的内容
编写脚本
import frida
import sys
# hook逻辑脚本
jscode = """
Java.perform(function () {
//获取bli类型,使用js将其包状成代理对象
var bil = Java.use('bil');
var function_a = bil.a;
//重写方法a
function_a.overload("org.json.JSONObject","java.lang.String").implementation = function (obj,str) {
// Show a message to know that the function got called
send('function_a');
send('obj'+obj);
send('str'+str);
// Call the original onClick handler
//调用实际的a方法(即包装之前的a方法,类似于装饰器工能)
return this.a( obj,str);
};
});
"""
# 通过usb连接方法
# process = frida.get_usb_device(timeout=60).attach('美团')
# 通过usb连接,指定手机
# process = frida.get_device(id='97dc2d9').attach('美团')
# 注入进程,attach传入进程名称(字符串)或者进程号(整数)
rdev = frida.get_remote_device()
session = rdev.attach("com.nice.main")
script = session.create_script(jscode)
#int()函数把字符串表示的16进制数转换成整数
#上面的jscode % int(sys.argv[1], 16)是python格式化字符串的语法
# 接收脚本信息的回调函数
# message是一个对象,type属性为send则表示send函数发送的信息,其内容在payload里
# 下面这个on_message函数可以做固定用法,一般无需改动,当然也可直接打印message看看里边的内容
def on_message(message, data):
if message['type'] == 'send':
print(message['payload'])
elif message['type'] == 'error':
print(message['stack'])
# 应该是设置message事件的回调函数
script.on('message', on_message)
# 加载hook脚本
script.load()
# 保持主线程不结束(也可以使用time.sleep循环)
sys.stdin.read()
在反编译的文件中有些类没有包名如:
package defpackage;
说明没有包名,在使用Java.use();
方法时,直接使用类名就可以了如Java.use('bil');
运行脚本
运行脚本前一定要先在模拟器中将nice app运行起来,然后在cmd中运行
python my_script.py
然后输入账号密码即可获取到账号与密码(此处密码是加密后的)等相关内容
成功hook。
参考:
https://cloud.tencent.com/developer/article/1662286
https://www.52pojie.cn/forum.php?mod=viewthread&tid=931872
问题参考:
frida(反调试)被检测
在使用frida过程中发现有些app会对27042 /27043端口或frida-server服务名称进行检测,导制一运行frida注入app就闪退,解决方法如下:
1、重命名服务frida-server为fs或其他名字
2、frida服务默认启动端口为27042可指定端口端启动如:
chmod 777 fs64
fs64 -l 0.0.0.0:1234
然后转发
adb forward tcp:1234 tcp:1234
客户端使用方式
frida-cli的使用自定端口的方式:
frida -H 127.0.0.1:1234 package_name -l hook.js
python脚本使用自定义端口的方式:
host = '127.0.0.1:1234'
manager = frida.get_device_manager()
device= manager.add_remote_device(host)
备注:get_remote_device()方法,后来发现这个函数默认连接的是127.0.0.1:27042,使用上面代码代替即可
参考:https://bbs.pediy.com/thread-254974.htm
https://wrlu.cn/cyber-security/mobile-security/android-frida/
frida相关用法
Frida Hook所使用的JS脚本该如何编写(官方教程:JavaScript API)
获取进程
# 枚举手机上的所有进程 & 前台进程
import frida
# 获取设备信息
rdev = frida.get_remote_device()
print(rdev)
# 枚举所有的进程
processes = rdev.enumerate_processes()
for process in processes:
print(process)
# 获取在前台运行的APP
front_app = rdev.get_frontmost_application()
print(front_app)
查看所有安装的应用
# python
applications = device.enumerate_applications()
for application in applications:
print(application)
获取顶层应用进程
# python
front_app = remote_device.get_frontmost_application()
print(front_app)
-------------------------------------------------------------------------
Application(identifier="com.android.settings", name="设置", pid=6683)
hook脚本
①:附加
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
# Hook手机上的那个APP(app的包名字)
session = rdev.attach("名称")
scr = """
Java.perform(function () {
// 包.类
var UserModel = Java.use("hook的类名");
// Hook,替换
UserModel.方法名.implementation = function(str,str2,str3,callback){
console.log(str,str2,str3);
// 执行原来的方法
this.方法(str,str2,str3,callback);
}
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
sys.stdin.read()
②:spawn模式
import frida
import sys
# 连接手机设备
rdev = frida.get_remote_device()
# Hook手机上的那个APP(app的包名字)
pid = rdev.spawn(["包名"])
session = rdev.attach(pid)
scr = """
Java.perform(function () {
// 包.类
var UserModel = Java.use("hook的类名");
// Hook,替换
UserModel.方法名.implementation = function(str,str2,str3,callback){
console.log(str,str2,str3);
// 执行原来的方法
this.方法(str,str2,str3,callback);
}
});
"""
script = session.create_script(scr)
def on_message(message, data):
print(message, data)
script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()
使用js文件执行hook
- hook_update.js
Java.perform(function () {
var UpdateDialog = Java.use('com.azhon.appupdate.dialog.UpdateDialog');
UpdateDialog.show.implementation = function(ctx){
//this.show();
}
});
spawn模式启动js文件: frida -Uf com.app.name -l hook_update.js
attach模式启动js文件: frida -UF -l hook_update.js
Python脚本运行通用样例
# -*- coding: utf-8 -*-
import frida
import sys
def on_message(message, data):
if message['type'] == 'send':
print("*****[frida hook]***** : {0}".format(message['payload']))
else:
print("*****[frida hook]***** : " + str(message))
def get_javascript(filepath):
code = ''
with open(filepath, 'r') as file:
code = code + file.read()
return code
# 连接远端设备
device = frida.get_remote_device()
# 附加到进程
session = device.attach(package_name)
# 1、直接写入 javascript 代码
javascript = """
<javascript code>
"""
# 2、从文件中加载 javascript 脚本代码
javascript = get_javascript(javascript_file)
# 基于脚本内容创建运行脚本对象
script = session.create_script(javascript)
script.on('message', on_message)
# 加载脚本并执行
script.load()
sys.stdin.read()
js脚本用法示例
// javascript
// 将线程附加到Java虚拟机,成功会回调function()
Java.perform(function() {});
// 由于js代码注入存在超时,通常在外面再包装一层
setImmediate(function() {
...
});
// 动态获取一个js包装的Java类
var clazz = Java.use(className);
// $new()调用类构造方法,$dispose()调用析构清空js对象
// 获取到Java类之后,直接通过<wrapper>.<method>获取方法
// 有些重载的方法需要通过.overload()传入参数类型指定方法
// 通过方法的.call(args)来调用函数,通过方法的.implementations = function(){}来实现hook
clazz.method.implementation = function(args) {
......
}
clazz.method.overload(argumentsType[]).call(args);
// 将js包装的类实例转换成另一种js包装类
Java.cast(clazz1, Clazz2)
基本使用
Frida提供了一些工具直接操作
比如查看连接的设备
# bash
$ frida-ls-devices
Id Type Name
---------------- ------ ------------
local local Local System
0105a575959b9fa9 usb LGE Nexus 5X
tcp remote Local TCP
使用python脚本获取的也是这样
# python
import frida
print(frida.get_local_device())
print(frida.get_usb_device())
print(frida.get_remote_device())
-------------------------------------------------------------------------
Device(id="local", name="Local System", type='local')
Device(id="0105a575959b9fa9", name="LGE Nexus 5X", type='usb')
Device(id="tcp", name="Local TCP", type='remote')
其中,local device对应的就是PC本机,usb device对应连接的Android设备,remote device也是经过端口转发的Android设备
附加指定进程,frida-trace追踪指定的函数的调用
$ frida-trace -i open -U <package_name/pid>
获取加载的类
// javascript
Java.enumerateLoadedClasses({
onMatch:function(_className){
log("found instance of '" + _className + "'");
},
onComplete: function(){
log("enumerating completed !!!");
}
});
获取类声明的函数
// javascript
var clazz = Java.use(className);
var methods = clazz.class.getDeclaredMethods();
if(methods.length > 0){
log("getDeclaredMethods of class '" + className + "':");
methods.forEach(function(method){
log(method);
});
}
获取调用堆栈
// javascript
var stack = Java.use("java.lang.Thread").$new().currentThread().getStackTrace();
for(var i = 2; i < stack.length; i++){
log("getStackTrace[" + (i-2) + "] : " + stack[i].toString());
}
获取加载的所有模块
// javascript
Process.enumerateModules({
onMatch: function(module) {
log(module.name + " : " + module.base + "\t" + module.size + "\t" + module.path);
},
onComplete: function() {
log("enumerating completed !!!");
}
});
获取所有的导出符号
// javascript
var symbols = Module.enumerateExportsSync(moduleName);
symbols.forEach(function(symbol){
log(symbol.name + " address = " + symbol.address);
});
return symbols;
获取JNI注册的函数信息
// javascript
var RegisterNativesAddr = getSymbolAddr("libart.so", "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi");
if(RegisterNativesAddr != null){
log("find symbol 'RegisterNatives' in libart.so, address = " + RegisterNativesAddr);
Interceptor.attach(RegisterNativesAddr, {
onEnter: function(args) {
var class_name = Java.vm.getEnv().getClassName(args[1]);
var methods_ptr = ptr(args[2]);
var module = Process.findModuleByAddress(Memory.readPointer(methods_ptr));
log("RegisterNativeMethod class = " + class_name + ", module = " + module.name + ", base = " + module.base);
var method_count = parseInt(args[3]);
log("registered methods count = " + method_count);
// get registered native method info
var offset = Process.pointerSize;
for (var i = 0; i < method_count; i++) {
var name = Memory.readCString(Memory.readPointer(methods_ptr.add(offset*3*i)));
var sig = Memory.readCString(Memory.readPointer(methods_ptr.add(offset*3*i+offset)));
var address = Memory.readPointer(methods_ptr.add(offset*(3*i+2)));
log("methods name = " + name + ", sig = " + sig + ", address = " + ptr(address) + ", offset = " + ptr(address).sub(module.base));
}
},
onLeave: function() {}
});
}
导出远程方法,注意不支持大写字母和下划线
// javascript
rpc.exports = {
<exportfuncname1>: function(args) {
......
},
<exportfuncname2>: function(args) {
......
}
};
远程调用
# python
script.exports.exportfuncname1(args)
查看手机的进程
frida-ps -U
将脚本注入android进程
frida -U -l myhook.js com.xxx.xxxx
参数解释:
- U 指定对USB设备操作
- l 指定加载一个Javascript脚本
- 最后指定一个进程名,如果想指定进程pid,用-p选项。正在运行的进程可以用
frida-ps -U
命令查看
frida运行过程中,执行%resume重新注入,执行%reload来重新加载脚本;执行exit结束脚本注入
下面是frida客户端命令行的参数解释,看一下就好
参考:
https://www.jianshu.com/p/aa466d198a20
https://www.jianshu.com/p/00641e9f9854
可能遇到的问题
1、 提示:frida.ServerNotRunningError: unable to connect to remote frida-server
解决1:没有打开 frida-server,按照上面的教程打开 server
解决2:端口没有转发,执行一下这个命令:adb forward tcp:27042 tcp:27042
2、 提示:frida.ProcessNotFoundError: unable to find process with name ‘xxx’
解决:这是因为app的包名填错了,先用:frida-ps -U
找到想操作的包名,然后再填入到这里:frida.get_remote_device().attach(‘京东’)
3、如果出现以下错误:
可以通过以下方式关闭SELinux,在adb shell中执行:
echo 0 > /sys/fs/selinux/enforce
或者
setenforce 0
确认是否设置成功
4、问题描述:修改frida-server默认启动端口后,frida连接报错
执行frida -U -f 包名
,报错如下:
Failed to spawn:need Gadget to attach on jailed Android;its default location is xxx
问题原因:frida-server其实在后头已经正常启动,但是因为修改了默认启动端口,frida客户端找不到frida-server
解决方案:使用-H参数,不使用-U
frida -H 127.0.0.1:12345 -f 包名
5、Frida 注入js过一会自动停止Process terminated
错误信息:Process terminated
frida -U --no-pause -f [应用包名]
-
-f
选项表示强制启动一个应用程序 -
--no-pause
选项表示不中断应用程序的启动,如果不使用这个选项总是会遇到 在强制启动应用程序2秒左右后程序自动退出 -
-U
指定对USB设备操作 -
-l
指定加载一个Javascript脚本
正确命令:
frida -u -l frida.js --no-pause -f [应用包名]
参考:
https://www.jianshu.com/p/146ad356390d