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')