Frida——Java层hook方法
Frdia框架——hook方法详解
Hook普通方法
普通方法包含静态方法、公开方法、私有方法。
Java.perform(function(){
var MainActivity=Java.use('com.example.frida_new1.MainActivity');//完整包名(注意大小写)
MainActivity.getCalc.implementation=function(a,b){
send('Hook start....');
send(arguments[0]);
send(b);//两种打印参数的方式
var num=this.getCalc(a,b);
send(num);
return num;
}
});
详解:arguments[n]在js中相当于类数组对象,在function(a,b)中参数既可以用a、b来表示,也可以用arguments来表示。虽然function指代getCalc方法,但仍然可以在function中调用包中的其他函数,只是在function中可以对getCalc的方法进行参数调整和改变。
hook构造方法
大致方法和普通方法相同。因为构造方法与报名相同,所以不需要Util.Util这种写法(我试过直接用构造方法名,直接报错了。。。)构造方法有特有的表示方法:
类名.$init //这里的$init就表示构造方法。
具体代码:
Java.perform(function(){
var money=Java.use('com.example.frida_new1.Money');
money.$init.implementation=function(a,b){
send('hook到Money构造函数');
send(arguments[0]);
send(b);
return this.$init(10000,"美元");//用this.方法名来表示需要调用的方法(直接用不明确,会报错)
}
});
修改函数时,注意参数类型。
hook重载方法
和普通方法不同的区别是在寻找方法时使用:
Utils.cz.overload("int").implementation=function(参数)
详解:cz为重载方法名,overload为注明此方法为重载方法并在括号中标明所要hook的重载方法中的参数,并传入。例如:需要hook的重载方法有一个int参数:overload(“int”)即可。但是String类型有点特殊,需要写为overload(“java.lang.String”)因为overload中需要标明的实际是类型所处的全路径。
Java.perform(function(){
var Utils=Java.use('com.example.frida_new1.Utils');
Utils.cz.overload("int","java.lang.String").implementation=function(a,b){
send('hook成功!');
send(arguments[0]);
send(b);
return this.cz();
}
});
hook方法的所有重载
获取一方法的所有重载方法,只需utils.test.overloads,其余操作和正常重载方法相同。
Java.perform(function(){
var utils=Java.use('com.example.frida_new1.Utils');
console.log(utils.test.overloads.length);//获取重载方法的个数
for(var i=0;i<utils.test.overloads.length;i++){
utils.test.overloads[i].implementation=function(){
console.log(arguments);//arguments是一个储存所有重载方法的参数
return this.test.apply(this,arguments);//只用apply将不同的重载方法返回对应的参数
//如果apply中参数有arguments,则直接返回。
//如果apply中只有this一个参数,则循环重载方法的个数次。
}
}
});
如果只想修改无参的重载方法,只需加上一个判断:
if(arguments.length==0){
return "hook到没有参数的重载方法";
}
hook自定义参数
自定义参数类型时,对于对象参数可以直接调用对象中的方法。如果需要更改参数时,需要重新实例化一个对象来返回:
var 变量名=对象类名.$new(构造参数) 如:var mon =money.$new(123456,"港币");
注意如果是重载方法中参数为某一对象,在overload中需要标明对象类 的全路径。例如:
Utils.method.overload("com.example.frida_new1.Money").implementation=function(参数)
Java.perform(function(){
var Utils=Java.use('com.example.frida_new1.Utils');
var money=Java.use('com.example.frida_new1.Money');
Utils.test.implementation=function(a){
send('hook到对象参数');
send(a.getInfo());
var mon =money.$new(123456,"港币");
send(mon.getInfo());
return mon.getInfo();//这里返回值可更改为this.test(mon),要注意返回值的参数类型!!
}
});
对象参数属性的修改
在获取某一实例对象的属性时不能直接通过”类名.变量名”的方法来直接获取。需要表示为“类名.变量名.value”
Java.perform(function(){
var Utils=Java.use('com.example.frida_new1.Utils');
var money=Java.use('com.example.frida_new1.Money');
Utils.test.overload("com.example.frida_new1.Money").implementation=function(a){
send('hook到对象参数');
var mon =money.$new(123456,"港币");
send(mon.getInfo());
mon.name.value="英镑";
send(mon.name.value);
return mon.getInfo();
}
});
java反射(对参数属性的修改)
使用反射的主要作用是可以让其他语言去调用java语言。基本思想:先找类,再找方法,再调用方法。
Java.perform(function(){
var Utils=Java.use('com.example.frida_new1.Utils');
var money=Java.use('com.example.frida_new1.Money');
var clazz=Java.use('java.lang.Class');//定义一个类的构造器
Utils.test.overload("com.example.frida_new1.Money").implementation=function(a){
var mon =money.$new(123456,"港币");
send(mon.getInfo());
var num=Java.cast(mon.getClass(),clazz).getDeclaredField('num');
//cast中第一个参数是getClass获取类id,(对象所处的类)
//getDeclaredField('')用来获取字段id,参数为变量名
var name=Java.cast(mon.getClass(),clazz).getDeclaredField('name');
name.setAccessible(true);
num.setAccessible(true);//设置属性,不修改app会崩溃
var Value=num.get(mon);//获取类传到该变量中
console.log(Value);
//这里直接使用send()无法成功打印
var v1=name.get(mon);
console.log(v1);
//send(Value);这里直接用send无法输出
num.setInt(mon,5000);
name.set(mon,"英镑");//修改变量使用set...()方法:setInt(),set()
send(mon.getInfo());
return mon.getInfo();
}
});
注:hook onCreat方法时应该在结尾继续执行该方法,因为onCreat在程序中是一直执行的。在末尾加上 this.onCreat()
hook内部类
内部类:在一个类中定义一个新的类。和直接引用不同的是需要在声明类的时候在主类和内部类中加“$”符号。其余写法完全相同。
Java.perform(function(){
var intheclass=Java.use('com.example.frida_new1.Utils$intheclass');
console.log(intheclass);
intheclass.$init.implementation=function(a,b){
send('Hook start....');
send(a);
send(b);
b=6666
a="iu"
send("修改后:"+a+" "+b);
return this.$init(a,b);
}
});
hook匿名类
匿名类:一个类中包含另一个类,且不需要任何类名直接实例化(没有类名)。主要用于创建一个对象来执行特定的任务,是代码更加简洁。一般匿名类只用一次。首先在smali代码中找到匿名类名(带有$),其余代码与普通方法类似
枚举所有的类和类的所有方法。
枚举所有的类在js中有一个固定的函数:enumerateLoadedClasses,具体使用方法如下:
Java.perform(function(){
//这样枚举会列举出该模拟器中正在运行的所有进程
Java.enumerateLoadedClasses({//枚举加载所有的类
onMatch:function(name,handle){//输出
console.log(name);
},
onComplete:function(){//枚举结束之后调用,该函数只会调用一次
}
});
});
如果在onMatch中进行选择,指定包中的类,则会筛选输出指定类名。
Java.perform(function(){
Java.enumerateLoadedClasses({//枚举加载所有的类
onMatch:function(name,handle){//输出
if(name.indexOf("com.example.frida_new1")!=-1){
console.log(name);
var clazz =Java.use(name);
console.log(clazz);
var methods=clazz.class.getDeclaredMethods();
for(var i=0;i<methods.length;i++){
console.log(methods[i]);
}
}
},
onComplete:function(){//枚举结束之后调用,该函数只会调用一次
}
});
});
"""
枚举所有的方法使用clazz.class.getDeclaredMethods();
var clazz =Java.use(name);
console.log(clazz);
var methods=clazz.class.getDeclaredMethods();
for(var i=0;i<methods.length;i++){
console.log(methods[i]);
}
Java层打印函数堆栈定位关键代码
在xposed中打印函数堆栈一般使用:
log.d("TAG","MSG",new Trowable());
在frida中一般不使用,而使用Log.getStackTranceString()方法。(该方法也可以打印信息)。
java.cast():强制类型转换。
java.openClassFile(); //注入dex文件
java.registerClass() //在Frida中注册一个类写进app,很少用到。
java.array();//构造任意类型的数组。
frida注入多dex文件
当hook比较复杂时(需要大量使用java函数,想为app添加大量函数),很多函数不方便引入js中使用,就需要正向的java函数来注入到frida中,此时引入注入dex文件这一方法,方便hook。首先需要获取dex文件,建议直接将所需要的函数在另一个app中实现,打包成apk分离出dex文件即可(分离出dex文件后需要把他放在adnroid的缓存区:“adb push 文件路径 、data/local/tmp/xxx.dex)。再使用openClassFile函数来打开这个文件。获取到这个包后可以直接调用包中 的函数。相当于正向调用。
Java.perform(function(){
Java.openClassFile("/data/local/tmp/fridadex.dex").load();
var frida=Java.use("简单实现函数的打包的apk的包名");//dex中的函数所在类名
var main=Java.use("com.example.frida_new1.Money");//开始hook
main.fridadex.implementation=function(a,b){
var cal=frida.xxx(a,b);
send(cal);
return cal;
}
});
Frida-server端口启动与非标准端口启动
启动Frida-server一般采用默认端口:
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:24043
为了防止被检测,会使用非标准端口:
1、启动frida-server时使用”./data/local/tmp/frida-server -l 127.0.0.1:9999 “
2、转发端口时”adb forward tcp:9999”
3、在frida中修改为:
process = frida.get_device_manager().add_remote_device('127.0.0.1:9999').attch('Frida_new1')