avatar

Frida学习笔记

Frida与雷电模拟器兼容版本

  1. Frida版本:12.0.8
  2. 雷电模拟器版本:4.x或3.x
  3. frida-tools版本:1.1.0

常用命令

  • 获取模拟器所有进程

    frida-ps -U

  • 使用命令行加载程序(spawn)

    frida -U -f 包名

    • 使用%resume来启动程序
    • 使用%load js脚本位置来加载脚本
  • 直接启动Hook

    frida -U -l js脚本位置 包名

常用语法

Java层Hook

Java.perform

Java层的Hook都是从Java.perform开始的。相当于main()方法,下面的Java语法都是需要写在这个里面的。具体用法如下:

1
2
3
Java.perform(function(){
//在这里面写Hook的代码
})

Java.choose

用于查找堆中指定类的实例。获得实例后可以调用实例的函数。声明为:Java.choose(className,callbacks)

具体用法如下:

1
2
3
4
5
6
7
8
Java.choose("top.zyzling.User",{
onMatch:function(){
//onMatch回调会在找到类的实例后调用,也就是说内存中有多少实例,就会调用多少次
},
onComplete:function(){
//onComplete回调会在所有onMatch完成后调用
}
})

Java.available

确认当前进程的java虚拟机是否已经启动,虚拟机包括Dalbik或者ART等。虚拟机没有启动的情况下不要唤醒其他java的属性或者方法。返回值是一个boolean

Java.enumerateLoadedClasses

列出(枚举)当前已经加载的类,用回调函数处理。声明为:Java.enumerateLoadedClasses(callbacks).用法如下:

1
2
3
4
5
6
7
8
Java.enumerateLoadedClasses({
onMacth:function(className){ //className就是类的名称
//找到加载的每个类的时候被调用,参数就是类的名字,这个参数可以传给java.use()来获得一个js类包
},
onComplete:function(){
//查找完毕所有类之后调用,主要用于扫尾工作
}
})

Java.enumerateClassLoaders

主要用于列出Java JVM中存在的类加载器。声明为:Java.enumerateClassLoaders(callbacks)。用法如下:

1
2
3
4
5
6
7
8
Java.enumerateClassLoaders({
onMatch:function(loader){ //loader就是类加载器
//每找到一个就回调一次onMatch方法
},
onComplete:function(){
//在所有类加载器找出之后回调
}
})

Java.registerClass

用于注册一个类到内存,这个类可以是我们自己定义的,也就是说我们可以通过这个方式来自定义类加入到内存中,也可以是已经存在的类。声明为:Java.registerClass(callbacks)。用法如下:

1
2
3
4
5
6
7
var obj = Java.registerClass({
name:"top.zyzling.User", //类的全限定名.必传
superClass:'xxx', //父类的全限定类名,可选
implements:"xxx", //该类实现的接口全限定名,可选
fields:{属性名:"类型"}, //该类的属性集,可选
methods:{} //该类的方法集,可选
})

下面看看官方是怎么使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//获取目标进程的SomeBaseClass类
var SomeBaseClass = Java.use('com.example.SomeBaseClass');
//获取目标进程的X509TrustManager类
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');

var MyWeirdTrustManager = Java.registerClass({
//注册一个类是进程中的MyWeirdTrustManager类
name: 'com.example.MyWeirdTrustManager',
//父类是SomeBaseClass类
superClass: SomeBaseClass,
//实现了MyWeirdTrustManager接口类
implements: [X509TrustManager],
//类中的属性
fields: {
description: 'java.lang.String',
limit: 'int',
},
//定义的方法
methods: {
//类的构造函数
$init: function () {
console.log('Constructor called');
},
//X509TrustManager接口中方法之一,该方法作用是检查客户端的证书
checkClientTrusted: function (chain, authType) {
console.log('checkClientTrusted');
},
//该方法检查服务器的证书,不信任时。在这里通过自己实现该方法,可以使之信任我们指定的任何证书。在实现该方法时,也可以简单的不做任何处理,即一个空的函数体,由于不会抛出异常,它就会信任任何证书。
checkServerTrusted: [{
//返回值类型
returnType: 'void',
//参数列表
argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
//实现方法
implementation: function (chain, authType) {
//输出
console.log('checkServerTrusted A');
}
}, {
returnType: 'java.util.List',
argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
implementation: function (chain, authType, host) {
console.log('checkServerTrusted B');
//返回null会信任所有证书
return null;
}
}],
// 返回受信任的X509证书数组。
getAcceptedIssuers: function () {
console.log('getAcceptedIssuers');
return [];
},
}
});

通过类的全限定名加载类

在Frida中通过Java.use(类的全限定名)来加载类,相当于Java的Class.forName()。用法如下:

1
2
//加载String类
var jString = Java.use("java.lang.String");

加载类后,怎样创建一个对象

使用类.$new()来创建,例如创建一个String对象

1
2
var jStringClass = Java.use("java.lang.String");
var jString = jStringClass.$new("字符串");

数据类型相关

Frida中的基本类型定义
Frida中的基本类型全名 Frida中的基本类型缩写(定义数组时使用)
boolean Z
byte B
char C
double D
float F
int I
long J
short S
Frida中数组的定义

在Frida中用[表示数组。

例如是int类型的数组,写法为:[I

如果是String类型的数组,则写法为:[java.lang.String; 注意:后面还有个;

Frida创建任意类型的数组
  • 通过Java.array()。用法如下:

    Java.array('type',[value1,value2,value3]),例子如下:

    1
    2
    3
    4
    5
    6
    7
    //标准写法:
    var objClass = Java.use("java.lang.Object");
    var objArray = Java.array("Ljava.lang.Object;",[objClass.class]);
    //-------------以上就是相当于定义了一个Class[]数组,里面有一个Object.class元素。-------

    //偷懒写法
    var objArray = [objClass.class]

    假设我们需要定义int[]数组,并且需要往里面添加2个值。可以按照下面的写法写:

    1
    2
    3
    4
    5
    6
    7
    8
    var intClass = Java.use("java.lang.Integer");
    var num1 = intClass.$new(1);
    var num2 = intClass.$new(2);
    var intArray = Java.array("Ljava.lang.Object;",[num1,num2]);
    //-------------也可以如下写法-----------------
    var num1 = intClass.$new(3);
    var num2 = intClass.$new(4);
    var intArray = [num1,num2];

    那么怎么表示一个空数组呢?按照上面的例子,大概就是这样的

    1
    2
    3
    var emptyArray = Java.array("Ljava.lang.Object;",[]);
    //----------------也可以偷懒像下面这样写------------
    var emptyArray = [];

类型强转

在Frida中使用Java.cast()来强转类型。例如我想获取某个对象的Class,那么就可以如下写:

1
2
var clazz = Java.use("java.lang.Class");
var cls = Java.cast(obj.getClass(),clazz); //先获取obje的Class,然后再强转成Class类型。

获取Frida中[object Object]的值

假设console.log(data)打印出来的结果是[object Object],那么我们可以直接使用console.log(data.value)来打印他的值。

反射操作

获取方法

Frida也支持Java的反射操作。例子如下:

1
2
3
4
5
6
7
8
9
var clazz = Java.use("java.lang.Class");
//获取该类的所有公共方法(包含父类的),得到的是一个数组
var methods = jString.class.getMethods();
//获取该类所有的私有方法(不包含父类),得到的是一个数组
var methods = jString.class.getDeclaredMethods();
//通过名称获取该类的公共方法
var method = jString.class.getMethod("方法名",参数类型)
//通过名称获取该类的私有方法
var method = jString.class.getDeclaredMethod("方法名",参数类型)
执行方法

使用method.invoke(对象,参数)来执行方法,例如执行String.valueOf(1)方法,例子如下:

1
2
3
4
5
var jString = Java.use("java.lang.String");
var jInt = Java.use("java.lang.Integer");
var method = jString.class.getMethod("valueOf",[jInt.class]); //第二个参数为Class[]
//因为该方法是静态方法,所以第一个参数可以为null。如果不是静态方法,则需要创建实例。
var result = method.invoke(null,1);
获取属性

获取属性和方法差不多,就是把Method改为了``Field`

1
2
3
4
5
6
7
8
9
var jString = Java.use("java.lang.String");
//获取该类的所有公共属性(包含父类的),得到的是一个数组
var fields = jString.class.getFields();
//获取该类所有的私有属性(不包含父类),得到的是一个数组
var fields = jString.class.getDeclaredFields();
//通过名称获取该类的公共属性
var field = jString.class.getField("属性名")
//通过名称获取该类的私有属性
var field = jString.class.getDeclaredField("属性名")
获取属性值和设置属性值

和java一样,使用get(对象)来获取属性值,使用set(对象,值)来设置属性值。例子如下:

1
2
3
4
5
6
7
8
9
10
11
//假设我有个User类,里面有个私有name属性。
var userClass = Java.use("top.zyzling.User");
//获取属性值,因为方法不是静态的,所以要先存在一个实例,这里我们直接new一个
var user = userClass.$new();
var nameField = userClass.class.getDeclaredField("name");
//同java一样,最好是调用下setAccessible()方法,忽略检查.不然操作时可能会报错。
nameField.setAccessible(true)
//调用get()来获取值
var name = nameField.get(user); //意识也就是说,我获取的是user的name属性值。
//通过setXxx()来设置值
nameField.setString(user,"zyzling"); //意识是说,我要设置user的name属性值。

Firda Hook操作

基础说完了,现在我们来说说Frida具体是怎么用的。

Hook方法(没得重载的方法)

直接使用类.方法名.implementation =function(){}来对一个方法进行Hook。举个栗子,我们要HookUser类的getName()方法,让它返回zyzling

1
2
3
4
5
6
7
8
9
var userClass = Java.use("top.zyzling.User");
userClass.getName.implementation = function(){
//我们可以使用下面的方法来让它调用原方法
var result = this.getName();
//使用console.log()来打印日志
console.log("result:"+result);
//强制返回我们需要的,把原方法的返回值给丢弃
return "zyzling";
}
Hook重载方法

上面那种是没得重载的,如果有重载,就得使用下面这种方法来hook。

类.方法名.overload(参数).implementation =function(){}

还是举个栗子说明,假设我们在User类里有个setName()方法,但是他有重载方法,所谓重载方法就是在一个类中,方法名一样,但是方法的参数签名不一样。比如setName(String)setName(int)这两个方法就是重载方法,所以需要加上参数来分辨他们。例子如下:

1
2
3
4
5
6
7
8
var userClass = Java.use("top.zyzling.User");
//这里我们hook的是getName(String)这个方法。
userClass.getName.overload("java.lang.String").implementation = function(name){ //我们可以在这里用变量来接收传递过来的参数。
//这里的name就是调用该程序的地方,传过来的。
console.log("old name:"+name);
//我们可以使用下面的方法来让它调用原方法,并传递我们修改的值
var result = this.getName("zyzling");
}
Hook构造方法

上面对普通的方法进行Hook,那现在我们说说构造方法怎么hook。这里也分有参和无参,但是不管是有参还是无参,都是使用类名.$init来进行Hook的,如果有重载的构造方法,则也需要.overload。例子如下:

1
2
3
4
5
6
7
8
9
//我们还是以User类为例,假设他有个User(int i,String name)的构造方法。
var userClass = Java.use("top.zyzling.User");
userClass.$init.overload("int","java.lang.String").implementation=function(i,name){
//同hook方法一样,这里也可以使用变量来接收调用方传过来的值。
console.log("i:"+i);
console.log("name:"+name);
//最后调用原本的构造方法。
this.$init(i,name);
}
加固的类Hook

这里我们可以先HookApplicationContextattach()方法,然后在里面加载类。比方说下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
var application = Java.use("android.app.Application");
application.attach.overload("android.content.Context").implementation=function(context){
//先执行原来的attach方法
var reuslt = this.attach(context);
//使用context获取classloader
var classLoader = context.getClassLoader();
//设置Java.classFacotry.loader为我们上面获取到的classLoader
Java.classFactory.loader = classLoader;
//然后使用Java.classFactory.use()方法获取我们的对象
var obj = Java.classFactory.use(包名);
//然后下面就是我们具体的Hook逻辑了。
}

简单脚本(抄的)

枚举所有的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//定时调用
setTimeout(function (){
Java.perform(function (){
console.log("\n[*] enumerating classes...");
//使用Java.enumerateLoadedClasses进行查找
Java.enumerateLoadedClasses({
onMatch: function(_className){
console.log("[*] found instance of '"+_className+"'");
},
onComplete: function(){
console.log("[*] class enuemration complete");
}
});
});
});
打印堆栈信息
1
2
3
4
5
var showStacks = function () {
Java.perform(function () {
dmLogout(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new())); // 打印堆栈
});
}
混淆代码Hook

因为类名和方法名都混淆了(成了不可见字符),这时候我们只需要复制混淆的类名和方法名在百度上随便找一个有URLEncode功能的网站,先URLEncode一下。然后我们可以在js里面使用decodeURIComponent得到混淆的类名和方法名。这样Frida才能找得到。示例如下:

1
2
3
4
5
6
7
8
9
10
11
//假设我们URLEncode过后的混淆方法名为:%EE%A0%91,类名为:%EE%A0%94%EE%A0%94%EE%A0%94%EE%A0
//那我们要Hook这个方法,可以像以下写法
//取混淆类名的类
var obj = Java.use(decodeURIComponent("%EE%A0%94%EE%A0%94%EE%A0%94%EE%A0"));

//通过obj[]方式来定位到方法,在里面通过decodeURIComponent()解码我们混淆的方法名
obj[decodeURIComponent("%EE%A0%91")].implementation = function(){
//do hook
//如果我们想要调用该类另外一个混淆的方法%EE%A0%94%EE%A0%91%EE。那么可以如下操作:
this[decodeURIComponent("%EE%A0%94%EE%A0%91%EE")](参数);
}
调用隐藏方法
1
2
3
4
5
6
7
Java.choose("com.roysue.demo02.MainActivity" , {
onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次
console.log("Found instance: "+instance);
console.log("Result of secret func: " + instance.secret());
},
onComplete:function(){}
});

Native层Hook(待补充)

待补充

参考资料


评论