1 对多dex进行适配先hook attach函数,可以解决多dex文件找不到类名的错误。对内存中有完整dex的加壳app也可以hook。

Java.perform(function(){

  var application = Java.use("android.app.Application");
  application.attach.overload('android.content.Context').implementation =  
  function(context) {
  var result = this.attach(context); // 先执行原来的attach方法
  var classloader = context.getClassLoader(); // 获取classloader
  Java.classFactory.loader = classloader;
  var Utils = Java.classFactory.use("com.rytong.emp.tool.Utils");\
                //do something
              return result;

     }
}

2 JAVA层hook2.1 hook构造函数hook构造方法是固定写法:$init,代码如下:

var message = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message");
message.$init.implementation =  function(hash,aes,base){
   console.log("hash "+hash+" aes "+aes+" base "+base);
   this.$init("123","eqweq",base);
  };

如何构造方法有多个重载方法,则使用overload(……)形式即可,参数和参数之间用逗号隔开即可,代码如下;

var message = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message");
  message.$init.overload("java.lang.String","java.lang.String"
,"java.lang.String").implementation = function(hash,aes,base){
   console.log("hash "+hash+" aes "+aes+" base "+base);
   this.$init("123","eqweq",base);
  };

修改构造方法的参数及返回值

//修改参数:
this.$init("123","eqweq","tsts");
//修改返回值
this.$init(hash,aes,base)+"test";

2.2 hook普通方法在Xposed中,对于静态方法和非静态方法使用不同的hook函数,在frida中不做区分,hook函数相同。 
代码如下:

var myCrypto = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyCrypto");
myCrypto.md5Base64.implementation = function(){
   console.log("msg2 is "+arguments[0]);
   return this.md5Base64(arguments[0]+"test");
  };

如果有多个重载方法,使用overload(……)形式即可,代码如下:    

var myCrypto = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyCrypto");
  myCrypto.md5Base64.overload("java.lang.String")implementation = function(msg){
   console.log("msg1 is "+msg);
   return this.md5Base64(msg+"test");
  };

修改方法的参数以及返回值和修改构造方法的相同
2.3 构造和修改自定义类型对象和属性在hook的时会遇到参数或返回值不是基本类型而是自定义类型,此时如果想修改他的属性值或者调用他的 
一个方法我们会使用反射来进行操作,而在返回值的时候,想构造一个自定义类型的对象也是直接用反射实例化 
一个对象进行操作的。 
构造返回值自定义对象,代码如下:

//构造一个实例对象固定写法为:$new,也可以使用call形式

var coinmoney = Java.use("com.ydscience.fridaandroidstudydemo.utils.CoinMoney");
  var utils = Java.use("com.ydscience.fridaandroidstudydemo.utils.Utils");
  utils.getCoin.implementation = function(){
   var coinObj = coinmoney.$new(12,"yj");//直接构造
   //使用call构造
   //var coinObj = coinmoney.$new.overload("int","java.lang.String")
            //.call(coinmoney,12,"yiyi");
   coinObj.setMoney(24);
   console.log("money set is "+coinObj.getMoney());
   return coinObj;
};

构造自定义对象后,调用方法直接调用其对应的方法即可,而修改自定义对象的属性,只能通过反射去修改字段值。代码如下:

var clazz = Java.use("java.lang.Class");
  utils.getCoinMoney.implementation = function(){
   var coin =  arguments[0];
   //调用方法
   var money = coin.getMoney();
   console.log("money is "+money);
   //修改属性
   var moneyFiled = Java.cast(coin.getClass(),clazz).getDeclaredField("money");
   moneyFiled.setAccessible(true);
   console.log("money field is "+moneyFiled.get(coin));
   moneyFiled.setInt(coin,32);
   return coin.getMoney();
  };

静态成员变量的修改(私有变量也同样修改) 
代码如下:

var clazz = Java.use("java.lang.Class");
   var myLog = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyLog");
   var intance =  myLog.$new();
   var moneyFiled = Java.cast(intance.getClass(),clazz).getDeclaredField("IS_DEBUG");
      moneyFiled.setAccessible(true);
   console.log("IS_DEBUG modify before is "+moneyFiled.get(intance));
   moneyFiled.setBoolean(intance,true);
   console.log("IS_DEBUG moidify after is "+moneyFiled.get(intance));

更加通用的方式 
hook已经加载到内存中的类的一个方法,从中获取this指针,该指针即为当前的对象,调用代码如下:

var main = Java.use("seclover.crackme.MainActivity");
main.getInfo.implementation = function(){
//通过该变量的value去赋值
  this.id.value = 1;
  return this.getInfo();
}

2.4 hook重载函数重载函数常见的几种写法如下: 
(1)应用arguments

MyClass.MyFunc.overload("java.util.List").implementation = function() {
    this.MyFunc.overload("java.util.List").apply(this, arguments);
}

(2)argments下标

//参数arguments[0]对应的真实的第一个参数
MyClass.MyFunc.overload("java.util.List").implementation = function () {
this.MyFunc(arguments[0]);
};

(3)具体的参数变量

MyClass.MyFuncs.overload("int", "int").implementation = function (s1, s2) {
    var ret = this.MyFuncs(s1, s2);
}

(4)字符串数组

hook.hookMeArray.overload("[Ljava. .String;").implementation = {}

(5)使用call方法

MyClass.MyFunc.overload("java.lang.String").implementation = {
     this.MyFunc.overload("java.lang.String").call(this, args[1])
     MyClass.MyFunc.overload("java.lang.String").call()
}

2.5 对象实例化

var coinmoney = Java.use("com.ydscience.fridaandroidstudydemo.utils.CoinMoney");
//方式一
var newhg = coinmoney .$new(11,12);
//方式二
var newhg1 = coinmoney .$new.overload(“int","int").call(coinmoney,11,12);

2.6 hook 匿名内部类匿名内部类一般在主类后面以“$ 1”数字表现。

var noNameInnerClazz = Java.use("com.ydscience.fridaandroidstudydemo.utils$1");
noNameInnerClazz.getName.overload("java.lang.String").implementation = function(s)
{
  console.log("param is "+s);
  return this.getName(s);
}

2.7 hook 内部类内部类在主类后表现形式为“$ 内部类名” 

//hook内部类构造函数
var innerClazz = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message$InnerClass");
nnerClazz.$init.overload("java.lang.String").implementation = function(s){
  console.log("init  param is "+s);
  return this.$init(s);
}
//hook内部类的方法
innerClazz.setNumber.implementation =  function(num){
                console.log("num is "+num);
  //修改属性
  var number = innerClazz.class.getDeclaredField("number");
  number.setAccessible(true);
  //查看值
  console.log("value  is "+number.get(this));
  //修改方式一
  this.number.value = 122;
  //修改方式二
  number.setInt(123);
  return this.setNumber(num);
}

2.8 打印堆栈java层打印

Java.perform(function() {
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
    });

native层打印

var openFile = new File("/sdcard/openFile.txt","a+");
function hookOpen(){
    var openPtr = Module.findExportByName("libc.so", "open");
    var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
    Interceptor.replace(openPtr, new NativeCallback(function (pathPtr, flags) {
    var path = Memory.readUtf8String(pathPtr);

    var fd = open(pathPtr, flags);
    // console.log("Got fd: " + fd);

    var trace = Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
    if (trace.indexOf("libcrypt-lib.so") > 0){
        console.log("trace:"+trace);
        console.log("Opening '" + path + "'");

        openFile.write(path+"\n");
        openFile.flush();
    }

    return fd;
}, 'int', ['pointer', 'int']));
}

2.9 动态调用(1)静态方法 
     静态方法的调用和Xposed一样,直接使用类名调用方法,代码如下:

var hexStr =  myCrypto.sha1Hex("ueyeueu");
console.log("hex is "+hexStr);

(2)非静态方法 
        对于非静态方法而言,如果想调用方法,则必须找到该方法依赖于调用的类对象,即在内存中找到该方法 
依赖类已经创建的实例对象,获取该实例对象后,就可以任意调用该类中的方法。案例如下: 
message类代码

public class Message {
    private String hash;
    private String aes;
    private String base;

    public Message(String hash, String aes, String base) {
        this.hash = hash;
        this.aes = aes;
        this.base = base;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public String getAes() {
        return aes;
    }

    public void setAes(String aes) {
        this.aes = aes;
    }

    public String getBase() {
        return base;
    }

    public void setBase(String base) {
        this.base = base;
    }
}

在加载调用时代码如下:

Message message = new Message(MyCrypto.md5Base64(msg), MyCrypto.sha1Base64(msg)
            ,MyCrypto.Base64Encode(msg.getBytes()));
        message.setHash("ddweqwerd");

通过调用方法可以看出,可以通过构造函数和setHash方法得到Message类对象,代码如下:

//setHash方法得到类Message对象实现非静态方法的任意调用
var MainActivity =  Java.use("com.ydscience.fridaandroidstudydemo.MainActivity");
var message = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message");
   message.setHash.implementation = function(){
    var hash = this.getHash();
    var aes = this.getAes();
    console.log("hash is "+hash+" aes is "+aes);
                            //通过创建MainActivity对象调用MD5方法
    var intance = MainActivity.$new();
    var md5 =  intance.md5(hash);
    console.log("md5 is "+md5);
    return this.setHash(arguments[0]);
}
//构造方法得到类Message对象实现非静态方法的任意调用
message.$init.implementation =function(){
   //todo
}

(3)Java.choose模式 
使用Java.choose方法时,调用的类必须已经加载到内存,使用代码如下

setImmediate(function() {
    Java.perform(function () {
        Java.choose("com.ydscience.fridaandroidstudydemo.MainActivity", { 
             "onMatch":function(instance){
                  console.log("Instance found");
                  var str = Java.use("java.lang.String");
                  var re = instance.md5(str.$new("erwer"));
                  console.log("call result:" + re);
             },
             "onComplete":function() {
                  console.log(" Finished heap search");
             }
        });
    });
});

在某些机型手机上使用Java.choose可能会崩溃,选择合适的手机进行调试使用。

(4)静态变量的调用与赋值

var clazz = Java.use("com.ydscience.fridaandroidstudydemo.utils.Utils");
var CoinMoney = clazz.sCoinMoney.value;
console.log("CoinMoney "+CoinMoney)
var money = CoinMoney.getMoney();
console.log("money is "+money);
   clazz.line.value = 1100;

2.10 java.registerClass的使用以注册一个线程为例,代码如下:

var Thread = Java.use("java.lang.Thread");
var Runnable = Java.use("java.lang.Runnable");
var MyRunnable = Java.registerClass({
  name: 'com.example.mythread',
  implements  : [Runnable],
  methods: {
     run : function () {
                //todo
                }
         }
    });
var runnable = MyRunnable.$new();
var postThread = Thread.$new(runnable);
postThread.start();

2.11 实现和Xposed中XC_MethodReplace取代方法内部执行逻辑

var hook = Java.use("seclover.crackme.MainActivity$2");
hook.run.implementation = function(){
  console.log("unhook detected");
        //不调用原来方法,不执行return this.run();在此处只会打印一个log
}

2.12 UI thread 注入

Java.perform(function() {
  var Toast = Java.use('android.widget.Toast');
  var currentApplication = Java.use('android.app.ActivityThread').currentApplication(); 
  var context = currentApplication.getApplicationContext();

  Java.scheduleOnMainThread(function() {
    Toast.makeText(context, "Hello World", Toast.LENGTH_LONG.value).show();
  })
})

2.13 获取对象实例

Java.choose("android.view.View", { 
             "onMatch":function(instance){//This function will be called for every instance found by frida
                  console.log("
Instance found");
             },
             "onComplete":function() {
                  console.log("
Finished heap search")
             }
        });

2.14 创建Java数组

byte数组创建
var buffer = Java.array('byte', [ 13, 37, 42 ]);

3 Native层3.1导出函数hook 导出函数时,直接使用导出的函数名。 
打印参数时,如果时Java层调用的native交互式方法,如“JAVA_COM_YDFSCIEN_XXX”,则真实有效的参数从第三个开始,前两个分别为 
JNIENV 和Jobject,其他的则从第一个开始算起打印参数与返回值,格式如下:

int : toInt32()
String:
var String_java = Java.use('java.lang.String');
var args_4 = Java.cast(args[2], String_java); 
char与指针类型
Memory.readCString() 和Memory.readUtf8String()
//以十六进制输出
function printData(address) {
    var keyAry = Memory.readByteArray(address, 16);
    var byteAry = new Uint8Array(keyAry);
    dataStr = "";
    for(var i=0; i<byteAry.length; i++){
        dataStr += ("0x" + byteAry.toString(16) + ","); 
    }
    return dataStr;
}

示例代码如下:

Interceptor.attach(Module.findExportByName("libnative-lib.so" , "jstringToChar"), {
    onEnter: function(args) {
     var String_java = Java.use('java.lang.String');
     var args_4 = Java.cast(args[1], String_java); 
     console.log("open called! args[0]:"+args_4);
    },
    onLeave:function(retval){
     //Memory.readCString也可以
     console.log("leave ret is "+Memory.readUtf8String(retval));
    }
   });

3.2 hook未导出函数在hook未导出函数时,需要计算地址。地址为:基地址+相对地址。 
(1)手动计算。

查看基地址
查看进程pid
ps | grep com.kuaiduizuoye.scan
查看基地址
cat /proc/2275/maps |grep libanti_spam.so
   相对地址直接用IDA打开so文件就可以查看。

(2)自动计算。

Module.findBaseAddress(“libanti_spam.so”).add(相对地址)

无论哪种方式最后得到的地址,对地址最后一位需要进行奇偶性判断,1是thumb,0是arm。 
操作代码如下:

var nativePointer = new NativePointer(0x8949479);
Interceptor.attach(nativePointer ,
        onEnter: function(args) {
          //todo
    },
    onLeave:function(retval){
             //todo
         }
)

3.3加载so中导出函数

var exports = Module.enumerateExportsSync("libnative-lib.so"); 
for(var i=0; i<exports.length; i++){  
    console.log("name is  "+ exports.name +" address "+ exports.address); 
}

3.4 查找so的基址

//遍历模块找基址
        Process.enumerateModules({
            onMatch: function (exp) {
                if (exp.name == 'libnative-lib.so') {
                    send('enumerateModules find');
                    send(exp.name + "|" + exp.base + "|" + exp.size + "|" + exp.path);
                    send(exp);
                    return 'stop';
                }
            },
            onComplete: function () {
                send('enumerateModules stop');
            }
        });

        //通过模块名直接查找基址
        var soAddr = Module.findBaseAddress("libnative-lib.so");
        send("soAddr:" + soAddr);

3.5 修改参数及返回值代码如下:

//int类型
Interceptor.attach(faddptr, {
            onEnter: function (args) {
                send("onEnter add()");
                x = args[0];
                y = args[1];
                args[0] = ptr(x * 2);
                args[1] = ptr(y * 2);
                send("hook add()修改参数为原来的两倍 args[0]:" + args[0].toInt32() + "  args[1]:" + args[1].toInt32());
            },
            onLeave: function (retval) {
                send("onLeave  add()");
                retval.replace(678);
                send("add()修改返回值为:"+retval.toInt32())
            }
        });
//String类型
Interceptor.attach(fsayptr, {
            onEnter: function (args) {
                send("onEnter say()");
            },
            onLeave: function (retval) {
                send("onLeave say()");
                var s = Java.cast(retval, str);
                send("say() 原返回值:" + s);
                var env = Java.vm.getEnv();
                var jstring = env.newStringUtf("frida hook native");
                retval.replace(ptr(jstring));
                send("修改say()返回值:" + Java.cast(jstring, str));
            }
        });

4 RPC的使用rpc模板代码如下:

rpc.exports= {
    add:function(a,b){
        return a+b;
    },
    sha1:function(msg){
        var ret;
        Java.perform(function() {
             var myCrypto = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyCrypto");
             ret =  myCrypto.sha1Hex(msg);
        });
        return ret;
    },
}

使用方法一:

  (1)下载注入程序https://github.com/frida/frida/releases,名称为frida-inject-*
  (2)下载rpc模板,从动态调用列表的功能按钮中选择导出并保存
  (3)下载资源文件web.html vue.min.js 放入 /data/local/tmp
  (4)将注入程序和脚本上传至远端,执行frida-inject -n com.seclover.dhook -s inject.js
   (5)打开http://remoteip:3000/即可调用rpc

使用方法二: 
配合dhook使用,打开dhook后,在动态调用页面创建脚本,填写需要调用的类和方法,完成调用即可。