学习目标:熟练使用frida对Java层代码进行hook

静态方法和实例方法的hook

  • 不需要管修饰符,不需要区分静态和实例方法,都是使用Java.use
Java.perform(function(){
var money = Java.use("com.xiaojianbang.hook.Money");
money.getInfo.implementation = function () {
var result = this.getInfo();
console.log("money.getInfo result: ", result)
return result;
}
money.setFlag.implementation = function (a) {
console.log("money.setFlag param: ", a);
return this.setFlag(a);
}
});

函数参数和返回值的修改

Java.perform(function(){
var money = Java.use("com.xiaojianbang.hook.Money");
money.getInfo.implementation = function () {
var result = this.getInfo();
console.log("money.getInfo result: ", result)
return "这是修改后的返回值";
}
money.setFlag.implementation = function (a) {
console.log("money.setFlag param: ", a);
return this.setFlag("这是我新设置的参数");
}
});

构造方法的hook $init

Java.perform(function(){
var money = Java.use("com.xiaojianbang.hook.Money");
money.$init.implementation = function (a, b) {
console.log("money.$init param: ", a, b);
return this.$init("美元", 200);
}
});

对象参数的修改 $new

Java.perform(function(){
var wallet = Java.use("com.xiaojianbang.hook.Wallet");
var money = Java.use("com.xiaojianbang.hook.Money");
wallet.deposit.implementation = function (a) {
console.log("wallet.deposit param: ", a.getInfo());
return this.deposit(money.$new("美元", 200));
}
});

HashMap的打印

这里很奇怪,使用spawn启动时,不会输出结果,不知道为什么

Java.perform(function(){
var utils = Java.use("com.xiaojianbang.hook.Utils");
var stringBuilder = Java.use("java.lang.StringBuilder");
utils.shufferMap.implementation = function (a) {
var key = a.keySet();
var it = key.iterator();
var result = stringBuilder.$new();
while(it.hasNext()){
var keystr = it.next();
var valuestr = a.get(keystr);
result.append(valuestr);
}
console.log("utils.shufferMap param: ", result.toString());
var result = this.shufferMap(a);
console.log("utils.shufferMap result: ", result);
return result;
}
});

重载方法的hook

Java.perform(function(){
var utils = Java.use("com.xiaojianbang.hook.Utils");
utils.getCalc.overload('int', 'int').implementation = function (a, b) {
console.log("utils.getCalc param: ", a, b);
return this.getCalc(a, b);
}
utils.getCalc.overload('int', 'int', 'int').implementation = function (a, b, c) {
console.log("utils.getCalc param: ", a, b, c);
return this.getCalc(a, b, c);
}
utils.getCalc.overload('int', 'int', 'int', 'int').implementation = function (a, b, c, d) {
console.log("utils.getCalc param: ", a, b, c, d);
return this.getCalc(a, b, c, d);
}
});

hook方法的所有重载

Java.perform(function(){
var utils = Java.use("com.xiaojianbang.hook.Utils");
var overloadsArr = utils.getCalc.overloads;
for(var i =0; i<overloadsArr.length; i++){
var overload = overloadsArr[i];
overload.implementation = function (){
var param ="";
for(var j=0; j<arguments.length; j++){
param+=arguments[j]+" ";
}
console.log(param);
return this.getCalc.apply(this, arguments);
}
}
});

主动调用

function activeCall(){
Java.perform(function(){
var money = Java.use("com.xiaojianbang.hook.Money");
//主动调用静态方法
money.setFlag("hyq");
//主动调用实例方法,通过创建一个对象
var moneyObj = money.$new("hyq", 222);
console.log(moneyObj.getInfo());
//主动调用实例方法,通过使用Java.choose
Java.choose("com.xiaojianbang.hook.Money",{
onMatch:function(obj){
console.log(obj.getInfo());
},onComplete:function(){
console.log("搜索完毕");
}
});
});
}

函数堆栈的打印

function showStack(){
var log = Java.use("android.util.Log");
var throwable = Java.use("java.lang.Throwable");
console.log(log.getStackTraceString(throwable.$new()));
}

获取和修改类的字段

function test(){
Java.perform(function () {
//静态字段,记得加上.value
var money = Java.use("com.xiaojianbang.hook.Money");
console.log(money.flag.value);
money.flag.value = "wys";
console.log(money.flag.value);
//实例字段,通过创建新对象
var moneyObj = money.$new("欧元", 200);
console.log(moneyObj.currency.value);
moneyObj.currency.value = "hyq currency";
console.log(moneyObj.currency.value);
//实例字段,通过使用Java.choose()
Java.choose("com.xiaojianbang.hook.Money",{
onMatch: function(obj){
console.log("Java.choose: ", obj.currency.value);
obj.currency.value = "wys currency";
console.log("Java.choose: ", obj.currency.value);
},onComplete: function(){
console.log("搜索完毕");
}
});
//如果字段名和方法名一样,字段名需要加下划线前缀
Java.choose("com.xiaojianbang.hook.BankCard", {
onMatch: function (obj) {
console.log("Java.choose BankCard: ", obj._accountName.value);
}, onComplete: function () {

}
});
});
}

Hook内部类与匿名类

function test(){
Java.perform(function () {
//Hook内部类使用$
Java.choose("com.xiaojianbang.hook.Wallet$InnerStructure", {
onMatch: function (obj) {
console.log(obj.bankCardsList.value);
},onComplete:function () {
console.log("搜索完毕");
}
});
//Hook匿名类使用$1
var money$1 = Java.use("com.xiaojianbang.app.MainActivity$1");
money$1.getInfo.implementation = function (){
var result = this.getInfo();
console.log("moeny.getInfo result: ", result);
return result;
}
});
}

枚举所有已加载的类

function test(){
Java.perform(function () {
console.log(Java.enumerateLoadedClassesSync());
});
}

枚举类的所有方法

function test(){
Java.perform(function () {
var wallet = Java.use("com.xiaojianbang.hook.Wallet");
var methods = wallet.class.getDeclaredMethods();
var constructors = wallet.class.getDeclaredConstructors();
var fields = wallet.class.getDeclaredFields();
var classes = wallet.class.getDeclaredClasses();

for(let i=0; i<methods.length; i++) {
console.log(methods[i].getName());
}
console.log("=============================================");
for(let i=0; i<constructors.length; i++) {
console.log(constructors[i].getName());
}
console.log("=============================================");
for(let i=0; i<fields.length; i++) {
console.log(fields[i].getName());
}
console.log("=============================================");
for(let i=0 ; i<classes.length; i++) {
console.log(classes[i].getName());
//classes[i] 这里得到的已经是类的字节码,不需要再.calss
var Wallet$InnerStructure = classes[i].getDeclaredFields();
for(let j=0 ; j<Wallet$InnerStructure.length;j++){
console.log(Wallet$InnerStructure[j].getName());
}

}
});
}

Hook类的所有方法

function test(){
Java.perform(function () {
var utils = Java.use("com.xiaojianbang.hook.Utils");
var methods = utils.class.getDeclaredMethods();
for(let i =0; i < methods.length; i++){
var methodName = methods[i].getName();
console.log(methodName);
var overloadsArr = utils[methodName].overloads;
for(let j =0;j < overloadsArr.length; j++){
overloadsArr[j].implementation = function (){
var params = "";
for(let k=0; k<arguments.length; k++){
params += arguments[k] + " ";
}
console.log("utils." + methodName + " is called! params is: ", params);
return this[methodName].apply(this, arguments);
}
}
}
});
}

这一段代码这样写会报错,为什么呢?因为这样写相当于在内存中开辟了一整块空间,这些空间中的每一段分别存放了一个函数的hook代码写法,在第一遍注入时已将每一个函数的hook代码给写好放在数组里面了,而如果使用var 来定义methodName,这相当于在每一次循环时,都将之前写好的函数的hook代码又写了一遍,修改了它们原本应该使用的返回值名称,而是用let就相当于在每一次循环时,这个methodName都是独立的,不会修改到之前已经写好的函数的hook代码

所以这样写就好了

function test(){
Java.perform(function () {
var utils = Java.use("com.xiaojianbang.hook.Utils");
let methods = utils.class.getDeclaredMethods();
for(let i =0; i < methods.length; i++){
var methodName = methods[i].getName();
console.log(methodName);
var overloadsArr = utils[methodName].overloads;
for(let j =0;j < overloadsArr.length; j++){
overloadsArr[j].implementation = function (){
var params = "";
for(let k=0; k<arguments.length; k++){
params += arguments[k] + " ";
}
console.log("utils." + methodName + " is called! params is: ", params);
return this[methodName].apply(this, arguments);
}
}
}
});
}

或者是封装成函数

Java.perform(function () {
function hookFunc(methodName){
console.log(methodName);
var ovarloadsArr = utils[methodName].overloads;
for(let i=0; i<ovarloadsArr.length; i++){
ovarloadsArr[i].implementation = function (){
var params = "";
for(let j=0; j<arguments.length; j++){
params+=arguments[j]+" ";
}
console.log(params);
return this[methodName].apply(this, arguments);
}
}

}
var utils = Java.use("com.xiaojianbang.hook.Utils");
var methods = utils.class.getDeclaredMethods();
for(let i=0; i<methods.length; i++) {
var methodName = methods[i].getName();
hookFunc(methodName);
}
});

bksmali与smali的使用

源码:https://github.com/JesusFreke/smali

下载:https://bitbucket.org/JesusFreke/smali/downloads/

反编译dex

java -jar baksmali-2.5.2.jar d clesses.dex

回编译dex

java -jar smali-2.5.2.jar a smali(smali要改成目录名字)

frida注入dex文件

Java.openClassFile("/data/local/tmp/hyq.dex").load()
Java.perform(function () {
Java.openClassFile("/data/local/tmp/patch.dex").load();
var test = Java.use("com.xiaojianbang.myapplication.Test");
var utils = Java.use("com.xiaojianbang.hook.Utils");
utils.shufferMap.implementation = function (map) {
var result = test.print(map);
console.log(result);
return result;
}
});

frida写文件

SD卡的一些知识

在 Android 设备的 shell 环境中执行df -h命令后得到的文件系统磁盘空间使用情况的输出结果
df -h

SD卡权限需要app申请

没有申请权限,也可以通过设置给他权限

Environment.getRootDirectory().toString();
Environment.getDataDirectory().toString();
Environment.getDownloadCacheDirectory().toString();
Environment.getExternalStorageDirectory().toString();
Environment.getExternalStorageState();
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();

SD卡也分为共有存储空间与私有存储空间

以下两个目录app都有写入权限

/data/data/xxx.xxx	给app的私有目录
/sdcard/Android/data/xxx.xxx 给app的私有目录,空间较大

得到context,为什么要得到context,因为调用getExternalFilesDir需要实例一个对象,实例这个对象需要用到context参数

var current_application = Java.use('android.app.ActivityThread').currentApplication();
var context = current_application.getApplicationContext();
Java.perform(function () {
var current_application = Java.use('android.app.ActivityThread').currentApplication();
var context = current_application.getApplicationContext();
var path = Java.use("android.content.ContextWrapper").$new(context).getExternalFilesDir("Download").toString();
console.log(path);
var ios = new File(path + "/hyq.txt", "w");
ios.write("hyq is very very very good!!!\n");
ios.flush();
ios.close();
});

Java.cast

向上转型的,不能用toString直接得到结果,比如Map、List类型的打印

Java.perform(function () {
var utils = Java.use("com.xiaojianbang.hook.Utils");
utils.shufferMap.implementation = function (hashMap){
console.log("shufferMap: ", hashMap);
return this.shufferMap(hashMap);
}
utils.shufferMap2.implementation = function(map){
console.log("map: ", map);
var result = Java.cast(map, Java.use("java.util.HashMap"));
console.log("result: ", result);
return this.shufferMap2(map);
}
});

Java.array

有两个参数,第一个参数传数组元素的类型,第二个参数传数组

Java.perform(function () {
var utils = Java.use("com.xiaojianbang.hook.Utils");
//Stirng类型的数组
var strArr = Java.array("Ljava.lang.String;", ["xiaojianbang", "QQ:24358757", "VX:xiaojianbang8888", "公众号:非攻code"]);
console.log(utils.myPrint(strArr));
//也可以直接使用
console.log(utils.myPrint(["xiaojianbang", "QQ:24358757", "VX:xiaojianbang8888", "公众号:非攻code"]));
//Object类型的数组,有些类型frida不会帮助我们变成对象,比如int需要我们自己处理
var utils = Java.use("com.xiaojianbang.hook.Utils");
var bankCard = Java.use("com.xiaojianbang.hook.BankCard");
var bankCardObj = bankCard.$new("xiaojianbang", "123456789", "CBDA", 1, "15900000000");
var integer = Java.use("java.lang.Integer");
var boolean = Java.use("java.lang.Boolean");
var objArr = Java.array("Ljava.lang.Object;", ["xiaojianbang", integer.$new(30), boolean.$new(true), bankCardObj]);
console.log(utils.myPrint(objArr));
//也可以直接使用
console.log(utils.myPrint(["xiaojianbang", integer.$new(30), boolean.$new(true), bankCardObj]));

});

ArrayList的主动调用

Java.perform(function () {
var arrayList = Java.use("java.util.ArrayList").$new();
var integer = Java.use("java.lang.Integer");
var boolean = Java.use("java.lang.Boolean");
var bankCard = Java.use("com.xiaojianbang.hook.BankCard");
var bankCardObj = bankCard.$new("xiaojianbang", "123456789", "CBDA", 1, "15900000000");
arrayList.add("xiaojianbang");
arrayList.add(integer.$new(30));
arrayList.add(boolean.$new(true));
arrayList.add(bankCardObj);
var utils = Java.use("com.xiaojianbang.hook.Utils");
console.log(utils.myPrint(arrayList));
});

让hook只在某一个指定函数内生效

Java.perform(function () {
var mainActivity = Java.use("com.xiaojianbang.app.MainActivity");
var stringBuilder = Java.use("java.lang.StringBuilder");
mainActivity.generateAESKey.implementation = function (){
console.log("mainActivity.generateAESKey is called!");
stringBuilder.toString.implementation = function (){
var result = this.toString();
console.log(result);
return result;
}
var result = this.generateAESKey.apply(this, arguments);
stringBuilder.toString.implementation = null;
return result;
}
});

hook定位接口的实现类

Java.perform(function () {
var classes = Java.enumerateLoadedClassesSync();
for (var index in classes) {
let className = classes[index];
if(className.indexOf("com.xiaojianbang") === -1) continue;
let clazz = Java.use(className);
let resultArr = clazz.class.getInterfaces();
if(resultArr.length === 0) continue;
for (let i = 0; i < resultArr.length; i++) {
if(resultArr[i].toString().indexOf("com.xiaojianbang.app.TestRegisterClass") !== -1){
console.log("className: ", className);
console.log("resultArr: ", resultArr[i]);
}
}
}
});

hook定位抽象类的实现类

Java.perform(function () {
var classes = Java.enumerateLoadedClassesSync();
for (const index in classes) {
let className = classes[index];
if(className.indexOf("com.xiaojianbang") === -1) continue;
let clazz = Java.use(className);
let resultClass = clazz.class.getSuperclass();
if(resultClass == null) continue;
if(resultClass.toString().indexOf("com.xiaojianbang.app.TestAbstract") !== -1){
console.log(className, resultClass);
}
}
});

frida的各种选项介绍

frida为什么会打印两次

frida检测到js文件修改,注入了两次

退出,重新注入,没有动态修改的的时候就没有问题

frida.exe的使用

frida attach

  • 包名、pid、前端进程注入

  • frida -U -n xxx -l hyq.js
    frida -U -p xxx -l hyq.js
    frida -U -F -l hyq.js
    

    frida spawn

    frida.exe的选项介绍

    - -U 连接远程的USB设备(-U和-H只能选择一个)
    - -H 通过ip和端口连接,可以连接多台设备(netstat -tulnp查看端口)
    - -F 附加到最前这个app
    - -l 后面指明需要加载的JS脚本
    - -o 把信息输出到指定文件中

    ## frida连接多设备

    指定frida-server监听的端口

    ./fsarm64 -l 0.0.0.0:9000

    连接时使用-H

    frida -H 10.194.0.241:9000 -f com.xiaojianbang.app -l api.js