在日常分析安卓应用时,通常会有对应用进行hook的需求,用的比较多的hook框架有Xposed,frida,xhook等,正好最近接触Frida接触的较多,所以对Frida的一些常用操作做个记录,方便以后翻阅查询,同时也可以对学习frida的小伙伴有个参考的资料;
一般Frida逆向三阶段:
https://www.python.org/ 下载最新的直接安装
C:\Users\Administrator>pip install frida-tools
Collecting frida-tools
/...省略.../
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Running setup.py install for frida-tools ... done
Successfully installed colorama-0.4.0 frida-12.2.25 frida-tools-1.2.2 prompt-toolkit-1.0.15 pygments-2.2.0 six-1.11.0 wcwidth-0.1.7
frida-server下载需要注意的有两点:
1.安装的frida版本:需要下载与安装的frida版本对应的frida-server,否则会出错。
C:\Users\Administrator>frida --version
12.2.25
下载地址:https://github.com/frida/frida/releases
2.手机设备的架构:因为我的手机是arm64平台的所以选择的是android-arm64
frida-server-12.2.25-android-arm64.xz
下载完毕之后,解压后通过adb push到设备的临时目录下
adb push frida-server-12.2.25-android-arm64 /data/local/tmp/
adb shell进入设备shell环境,cd到临时目录下,给frida-server文件设置可执行权限使其可以运行
chmod 755 frida-server-12.2.25-android-arm64
之后运行frida服务文件,注意需要通过root权限去运行
./frida-server-12.2.25-android-arm64
然后 进行端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
完毕后就可以在windows上运行简单的frida命令测试我们是否安装成功了
/* -U 连接USB设备 */
/* frida-ps 列出正在运行的进程*/
C:\Users\Administrator>frida-ps -U
PID Name
---- --------------------------------
310 1:3
714 360sguard
723 360sguard
373 ATFWD-daemon
575 adbd
259 adsprpcd
[....]
3289 zygote
$ git clone git://github.com/oleavr/frida-agent-example.git
$ cd frida-agent-example/
$ npm install
$ frida -U -f com.example.android --no-pause -l _agent.js
首先来看一下frida的常用功能
λ frida-ps -U
PID Name
----- -------------------------------------------------------
6241 adbd
897 adsprpcd
749 android.hardware.audio@2.0-service
947 android.hardware.biometrics.fingerprint@2.1-service.fpc
750 android.hardware.bluetooth@1.0-service-qti
600 android.hardware.boot@1.0-service
752 android.hardware.camera.provider@2.4-service
753 android.hardware.cas@1.0-service
612 android.hardware.configstore@1.1-service
[...]
λ frida-ps -U | grep frida
31521 cn.gemini.k.fridatest
30073 frida-helper-32
λ frida-trace -i "open" -U "cn.gemini.k.fridatest"
Instrumenting...
open: Auto-generated handler at "E:\\Frida\\frida_work\\__handlers__\\libutils.so\\open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x7b3a */
16061 ms open()
/* TID 0x7b3c */
16114 ms open()
16114 ms open()
16114 ms open()
用的比较多的一般就是上面的两个功能,都是frida帮我们生成好了hook代码,直接拿来使用就行,简单方便,但有时候我们想自己定制些功能怎么办?
接下来看下如何通过编写js代码来实现对安卓APP中某些方法的hook。
在hook之前首先要熟悉我们需要hook的目标方法,应用包名,参数等基础信息。这里简单写了个demo,后面我们都通过这个demo来学习。
package cn.gemini.k.fridatest;
import android.util.Log;
public class FridaHook1 {
static String password = "88888888";
int cde = 20;
int abc = 10;
public int abc(){
return abc;
}
FridaHook1(){
Log.e("FridaHook1","一般方法 ret:"+func1_add(cde,abc)); // 一般方法
Log.e("FridaHook1","重载方法 ret:"+func2_add_overload(1,2)); // 重载方法
Log.e("FridaHook1","重载方法 ret:"+func2_add_overload(1,2,3)); // 重载方法
Log.e("FridaHook1","静态方法 ret:"+func3_verify_static("12345678")); // 静态方法
new inner_class().inner_class_func("内部类调用"); // 内部类
Log.e("匿名类方法 FridaHook1","ret:"+new anonymous_class(){ //匿名类
public String output(){
return "匿名类调用";
}
}.output());
}
public int func1_add(int a,int b){
Log.e("func1_add","arg1:"+a+" arg2:"+b);
return a+b;
}
public int func2_add_overload(int a,int b){
Log.e("func2_add_overload","arg1:"+a+" arg2:"+b);
return a+b;
}
public int func2_add_overload(int a,int b,int c){
Log.e("func2_add_overload","arg1:"+a+" arg2:"+b+" arg3:"+c);
return a+b+c;
}
public static int func3_verify_static(String str){
Log.e("func3_verify_static",str);
if(str.equals(password)){
Log.e("func3_verify_static","密码正确");
return 1;
}
Log.e("func3_verify_static","密码错误");
return 0;
}
static class inner_class{
inner_class(){
}
public void inner_class_func(String str){
Log.e("inner_class_func",str);
}
}
class anonymous_class{ }
}
整理一下hook时需要的信息:
目标应用的包名:“cn.gemini.k.fridatest”(一般在AndroidManifest.xml文件中可以找到)
目标方法所在类的类名:“cn.gemini.k.fridatest.FridaHook1”;
最后是目标方法名和参数:public int func1_add(int a,int b)
没有源码的情况下这些信息都可以通过反编译工具jadx或jeb获取。
接下来开始编写hook代码
load.py代码如下:
# -*- coding: UTF-8 -*-
import frida,sys
js_code = '''
Java.perform(function(){
console.log("Frida Test");
var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");
cls.func1_add.implementation = function(arg1,arg2){
console.log("hook arg1:",arg1," hook arg2:",arg2);
return this.func1_add(arg1,arg2);
}
});
'''
# 目标包名
appPacknName = "cn.gemini.k.fridatest"
scriptFile = "hook_script.js"
# 输出日志的回调方法
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
device = frida.get_usb_device()
# spawn模式,找到目标包名并重启,在启动前注入脚本
pid = device.spawn([appPacknName])
session = device.attach(pid)
# 注意这里需要将device.attach(pid)这句代码写在前面,这样执行才符合预期(启动时程序白屏,等待下面这行代码来恢复执行)
# 其实在https://www.jianshu.com/p/b833fba1bffe这篇文章中有提到
device.resume(pid)
# 方式一: 通过js文件创建hook代码
#with open(scriptFile, encoding='UTF-8') as f :
# script = session.create_script(f.read())
# 方式二: 直接将hook代码写在python文件中
script = session.create_script(js_code)
script.on('message', on_message)
script.load() #把js代码注入到目标应用中
# 避免结束
sys.stdin.read()
解释下上面的python代码,先通过frida.get_usb_device连接到usb设备,接着使用spawn将目标应用重启并挂起,之后再通过attach附加到目标应用,完成附加后开始唤起应用主线程,最后是构造需要注入的js代码,通过session.create_script生成js脚本,script.on用来注册一个回调方法监听目标进程的所有消息,script.load会把前面生成的js代码注入到目标应用中完成hook。这里hook代码只是简单的将函数的参数给输出了下。
运行结果如下:
// frida打印日志
λ python load.py
Frida Test
hook arg1: 1 hook arg2: 2
// APP程序执行打印日志
19493-19493/cn.gemini.k.fridatest E/func1_add: arg1:1 arg2:2
19493-19493/cn.gemini.k.fridatest E/FridaHook1: 一般方法 ret:3
hook_script.js代码如下
function main(){
Java.perform(function(){
console.log("Frida Test");
var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");
cls.func1_add.implementation = function(arg1,arg2){
console.log("hook arg1:",arg1," hook arg2:",arg2);
return this.func1_add(arg1,arg2);
}
});
}
setImmediate(main)
load2.py代码如下
# -*- coding: UTF-8 -*-
import frida,sys
# 目标包名
appPacknName = "cn.gemini.k.fridatest"
scriptFile = "hook_script.js"
# 输出日志的回调方法
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
device = frida.get_usb_device()
# spawn模式,找到目标包名并重启,在启动前注入脚本
pid = device.spawn([appPacknName])
session = device.attach(pid)
# 注意这里需要将device.attach(pid)这句代码写在前面,这样执行才符合预期(启动时程序白屏,等待下面这行代码来恢复执行)
# 其实在https://www.jianshu.com/p/b833fba1bffe这篇文章中有提到
device.resume(pid)
# 方式一: 通过js文件创建hook代码
with open(scriptFile, encoding='UTF-8') as f :
script = session.create_script(f.read())
# 方式二: 直接将hook代码写在python文件中
# script = session.create_script(js_code)
script.on("message", on_message)
script.load() #把js代码注入到目标应用中
# 避免结束
sys.stdin.read()
运行结果如下:
// frida打印日志
λ python load2.py
Frida Test
hook arg1: 1 hook arg2: 2
// APP程序执行打印日志
19493-19493/cn.gemini.k.fridatest E/func1_add: arg1:1 arg2:2
19493-19493/cn.gemini.k.fridatest E/FridaHook1: 一般方法 ret:3
上面这种方式主要是将js代码封装成了一个单独的js文件,不再是之前放在python文件中的形式,封装成一个js文件可以方便我们对代码进行模块化管理。同时这样做还有一个好处就是可以在命令行直接使用frida注入工具来注入js代码到指定进程,省去了用python写load的步骤。
λfrida -U -n "cn.gemini.k.fridatest" -l hook_script.js
____
/ _ | Frida 14.2.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://www.frida.re/docs/home/
Attaching...
Frida Test Hook
[AOSP on walleye::cn.gemini.k.fridatest]->
最后简单了解下frida的两种注入模式:
第一种:直接通过attach方法对已启动的目标应用进行注入,这种一般用在hook时机比较晚的场景。
第二种:先使用spawn方法以挂起模式重新启动目标应用后再通过attach方法进行注入,一般用在hook时机比较早的场景。
暂时先大概有所了解,后面会将具体的使用场景。
还是上面的hook demo那个例子,我们这次对传入的参数以及返回值尝试修改。
修改js文件中的代码
function Hook1(){
Java.perform(function(){
console.log("Frida Test Hook1");
var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");
cls.func1_add.implementation = function(arg1,arg2){
console.log("func1_add --> hook arg1:",arg1," hook arg2:",arg2); // 输出原始参数
arg1 = arg1 + 1; // 修改参数值加1
arg2 = arg2 + 1; // 修改参数值加1
var ret = this.func1_add(arg1,arg2); // 将修改后的参数传入给原始方法
return ret + 3; // 将方法的返回值加3,并返回给调用方法
}
});
}
上面的js代码就是我们hook要实现的功能,Java.use方法通过类名获取类的类型并返回一个相同类型的对象cls,之后通过cls对象对普通方法func1_add进行hook,成功后当func1_add方法被别的方法调用时就会走到我们设置的hook方法。
我们的hook方法会将传入的两个参数都分别加1,被hook方法的参数获取可以通过在hook方法中定义形参获取也可通过在hook方法内部利用arguments[*]数组获取。修改完参数后再传递给原始方法进行计算,原始方法将返回值返回给hook方法后我们再将返回值加3,最后返回给应用的调用方法。
运行结果如下:
λ python load2.py
Frida Test Hook1
func1_add --> hook arg1: 1 hook arg2: 2
// APP程序执行打印日志
6374-6374/cn.gemini.k.fridatest E/func1_add: arg1:2 arg2:3
6374-6374/cn.gemini.k.fridatest E/FridaHook1: 一般方法 ret:8
从上面的结果可以看到,不管是传入的参数,还是最后的返回值都被我们修改掉了。
如果需要hook的方法存在重载方法,那么就需要使用overload关键字来指明参数类型,否则frida会报错。
js代码:
function Hook2(){
Java.perform(function(){
console.log("Frida Test Hook2");
var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");
// 重载方法hook
cls.func2_add_overload.overload('int', 'int').implementation = function(arg1,arg2){
console.log("func2_add_overload --> hook arg1:",arg1," hook arg2:",arg2);
arg1 = arg1 + 1;
arg2 = arg2 + 1;
var ret = this.func2_add_overload(arg1,arg2);
return ret + 3;
}
cls.func2_add_overload.overload('int', 'int', 'int').implementation = function(arg1,arg2,arg3){
console.log("func2_add_overload --> hook arg1:",arg1," hook arg2:",arg2," hook arg3:",arg3);
arg1 = arg1 + 1;
arg2 = arg2 + 1;
arg3 = arg3 + 1;
var ret = this.func2_add_overload(arg1,arg2,arg3);
return ret + 3;
}
});
}
执行结果
λ python load2.py
// APP程序执行打印日志
6374-6374/cn.gemini.k.fridatest E/func2_add_overload: arg1:1 arg2:2
6374-6374/cn.gemini.k.fridatest E/FridaHook1: 重载方法 ret:6
6374-6374/cn.gemini.k.fridatest E/func2_add_overload: arg1:1 arg2:2 arg3:3
6374-6374/cn.gemini.k.fridatest E/FridaHook1: 重载方法 ret:9
如果重载方法比较少上面的方法还行,但是如果重载方法较多的话一个个去写重载就比较无趣了,frida还给我们提供了一种hook所有重载方法的方法。
js代码
console.log(cls.func2_add_overload.overloads.length);
for(var i = 0; i < cls.func2_add_overload.overloads.length; i++){
cls.func2_add_overload.overloads[i].implementation = function(){
if(arguments.length == 2){
var arg1 = arguments[0] + 1;
var arg2 = arguments[1] + 1;
console.log("func2_add_overload --> hook arg1:",arg1," hook arg2:",arg2);
return this.func2_add_overload.apply(this,arguments) + 3;
}else if(arguments.length == 3){
var arg1 = arguments[0] + 1;
var arg2 = arguments[1] + 1;
var arg3 = arguments[2] + 1;
console.log("func2_add_overload --> hook arg1:",arg1," hook arg2:",arg2," hook arg3:",arg3);
return this.func2_add_overload.apply(this,arguments) + 3;
}
}
}
如果我们想hook一个类的构造方法,那么使用固定的$init代替构造方法名:
function Hook3(){
Java.perform(function(){
console.log("Frida Test Hook3");
var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");
cls.$init.implementation = function(){
console.log("$init --> hook");
//this.$init();
}
});
}
执行结果
λ python load2.py
Frida Test Hook3
$init --> hook
// APP程序执行打印日志
因为hook方法只输出了"$init --> hook"并没有调用原始构造方法,所以程序并没有输出。
静态方法的hook实际上和一般方法的hook类似。
function Hook4(){
Java.perform(function(){
console.log("Frida Test Hook4");
var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");
cls.func3_verify_static.implementation = function(arg1){
console.log("func3_verify_static --> hook arg1:",arg1);
return this.func3_verify_static(arg1);
}
});
}
执行结果
λ python load2.py
Frida Test Hook4
func3_verify_static --> hook arg1: 12345678
// APP程序执行打印日志
6374-6374/cn.gemini.k.fridatest E/func3_verify_static: 12345678
6374-6374/cn.gemini.k.fridatest E/func3_verify_static: 密码错误
6374-6374/cn.gemini.k.fridatest E/FridaHook1: 静态方法 ret:0
对于内部类或匿名类方法的hook需要通过$+名字的方式来使用。
function Hook5(){
Java.perform(function(){
console.log("Frida Test Hook5");
// hook内部类方法
var innercls = Java.use("cn.gemini.k.fridatest.FridaHook1$inner_class");
innercls.inner_class_func.implementation = function(arg1){
console.log("inner_class_func arg1:",arg1);
return this.inner_class_func(arg1);
}
// hook匿名类方法
var innercls = Java.use("cn.gemini.k.fridatest.FridaHook1$1"); // 匿名类的类名一般使用反编译软件获取
innercls.output.implementation = function(){
console.log("output 匿名类方法调用");
return this.output();
}
});
}
执行结果
λ python load2.py
Frida Test Hook5
inner_class_func arg1: 内部类调用
output 匿名类方法调用
// APP程序执行打印日志
6374-6374/cn.gemini.k.fridatest E/inner_class_func: 内部类调用
6374-6374/cn.gemini.k.fridatest E/匿名类方法 FridaHook1: ret:匿名类调用
Java.enumerateLoadedClasses(callbacks)
:无返回值,参数是一个回调方法,功能是列出当前已经加载的类,用回调方法处理。
回调方法:
onMath:function(name){}
找到加载的每个类的时候被调用,参数就是类的名字,可以将name传入Java.use()来获得一个js类,还可以通过name对枚举的类进行过滤
onComplete:function(){}
枚举完所有类之后被调用,用来做一些完成后的收尾工作
Java.enumerateLoadedClassesSync()
:无参数,方法返回所有已经加载的类的数组。
function Hook6(){
Java.perform(function(){
console.log("Frida Test Hook6");
// 枚举所有类
console.log("枚举所有类");
Java.enumerateLoadedClasses({
onMatch: function(name){
console.log(name);
// 这里可以添加过滤逻辑用来过滤我们关注的类
//if(name.indexOf("cn.gemini.k.fridatest") != -1){
// console.log(name);
//}
},
onComplete: function(){
}
});
// 打印类中的所有方法
console.log("打印类中的所有方法");
var clszz = Java.use("cn.gemini.k.fridatest.FridaHook1");
var methods = clszz.class.getDeclaredMethods(); // 获取类中的所有方法可使用反射获得
console.log(methods);
});
}
运行结果
λ python load2.py
Frida Test Hook6
枚举所有类
[.....] // 一大堆的系统类,可以添加过滤逻辑过滤一下
[Landroid.content.pm.ProviderInfo;
androidx.core.app.CoreComponentFactory$CompatWrapped
androidx.core.app.CoreComponentFactory
打印类中的所有方法
public static int cn.gemini.k.fridatest.FridaHook1.func3_verify_static(java.lang.String),public int cn.gemini.k.fridatest.FridaHook1.abc(),public int cn.gemini.k.fridatest.FridaHook1.func1_add(int,int),public int cn.gemini.k.fridatest.FridaHook1.func2_add_overload(int,int),public int cn.gemini.k.fridatest.FridaHook1.func2_add_overload(int,int,int)
如果类中有静态方法,又有重载方法的话下面代码会报错,没有静态方法则可以hook类中的成员方法。
function Hook7(){
Java.perform(function(){
console.log("Frida Test Hook7");
var clszz = Java.use("cn.gemini.k.fridatest.FridaHook1");
// 先枚举类的所有方法
var methods = clszz.class.getDeclaredMethods();
for(var i = 0; i < methods.length; i++){
var methodName = methods[i].getName(); // 获取到每个方法的名字
console.log(methodName);
console.log(clszz[methodName].overloads.length);
// 重载方法的处理
for(var j = 0; j < clszz[methodName].overloads.length; j++){
clszz[methodName].overloads[j].implementation = function(){
for(var k = 0;k < arguments.length; k++){
console.log(this + " arg"+ k + ":" + arguments[k]);
}
return this[methodName].apply(this, arguments);
}
}
}
});
}
一个类的字段包括静态字段和非静态字段。
静态字段的访问可以直接通过 类.字段名.value = “XXX” 的方式进行修改。
非静态字段则需要先拿到对象实例才能修改,获取对象实例可使用Java.choose()。同时还需要注意非静态字段又分为有同名方法的字段和无同名方法的字段。
function Hook8(){
Java.perform(function(){
console.log("Frida Test Hook8");
var clazz = Java.use("cn.gemini.k.fridatest.FridaHook1");
// 修改类中的静态字段
console.log("修改前静态字段的值:" + clazz.password.value);
clazz.password.value = "9"; // 静态字段的修改
console.log("修改后静态字段的值:" + clazz.password.value);
// 实例化类对象
var newcls = clazz.$new(); // 通过$new方法对类进行实例化
console.log("实例化一个类对象"+newcls)
console.log("修改前的字段值: abc=="+newcls._abc.value+" cde=="+newcls.cde.value);
// 修改类中的非静态字段
Java.choose("cn.gemini.k.fridatest.FridaHook1",{
onMatch: function(obj){
obj.cde.value = 100; // 非静态字段修改方式
obj._abc.value = 200; // 非静态字段修改:这里需要注意因为类中存在一个同名的方法,所以访问该字段时需要加个下划线"_"
console.log("修改后的字段值: abc=="+obj._abc.value+" cde=="+obj.cde.value);
},
onComplete: function(){
}
});
});
}
执行结果
λ python load2.py
Frida Test Hook8
修改前静态字段的值:88888888
修改后静态字段的值:9
实例化一个类对象cn.gemini.k.fridatest.FridaHook1@a675c28
修改前的字段值: abc==10 cde==20
修改后的字段值: abc==200 cde==100
frida的方法主动调用,主要分以下几种情况
1.frida主动调用Java类中的静态方法,也就是使用static关键字声明的。
2.frida主动调用对象的Java成员方法,通过对象才能调用的方法,非static方法。
3.frida主动调用so中的方法。
对so方法的直接调用需要用到frida的NativeFunction方法,方法原型如下:
NativeFunction(address, returnType, argTypes[, abi])
1)address:要hook的方法地址
2)returnType:返回值类型
3)argTypes[, abi]: 参数类型 这里参数可以是多个
js代码
function Hook9(){
Java.perform(function(){
console.log("Frida Test Hook9");
// 主动调用类静态方法
var clszz = Java.use("cn.gemini.k.fridatest.FridaHook1");
clszz.func3_verify_static(">>>pwd<<<");
// 主动调用类成员方法
// 第一种方式:创建一个新对象完成主动调用
var obj = clszz.$new();
var ret = obj.func2_add_overload(11,22);
console.log("返回值: " + ret);
// 第二种方式:搜索内存中已有对象完成主动调用
Java.choose("cn.gemini.k.fridatest.FridaHook1",{
onMatch: function(instance){
console.log("found instance :"+ instance);
console.log("返回值: "+ instance.func2_add_overload(33,44));
},
onComplete: function(){
console.log("Search Completed!");
}
})
// 主动调用so的native方法
var str_name_so = "libnative-lib.so"; //要hook的so名
var str_name_func = "JNI_Frida_Test"; //要hook的方法名
// 获取方法地址
var addr_func = Module.findExportByName(str_name_so , str_name_func);
console.log("func addr is ---" + addr_func);
//定义NativeFunction 等下要调用
var func_JNI_Frida_Test = new NativeFunction(addr_func,"void",[]);
func_JNI_Frida_Test();
});
}
文章转载于: https://blog.csdn.net/weixin_46734340/article/details/117401345