Frida操作java类的一些记录

魏君博
2023-12-01

Frida操作java类的一些记录

这篇笔记主要记录在使用Frida写一个测试框架中会需要的Api的使用方法,包括了

  • 何时执行
  • 获取当前系统的信息
  • 获取所有加载到当前进程的类信息
  • 获取app的所有加载的类
  • 获取app加载的所有so模块
  • 获取app类的方法
  • 获取加载模块的方法
  • 操作一个java类对象
  • 修改java的函数参数
  • 修改java的函数的返回值

何时执行

为了保证能正常运行,需要使用如下方法执行以确保不会发生ClassNotFoundException等异常。 code_01.js

Java.perform(function(){
    //do something
    //java类的操作需要在这个方法下执行,这样才能确保已经附加到了一个JVM的进程上
})

调用成功了Java.perform函数,后续的java操作才能执行。

系统信息

系统信息包括了手机的信息和pc的信息,使用如下方法获取

判断是否附加成功

Java.avaliable -->返回一个bool类型值,如果返回False则表示当前测试进程不能用

Android的系统版本

为了能获取到当前系统的版本信息,使用如下Api

Java.androidVersion

获取系统的架构信息

获取系统的架构信息可以使用如下APi

var arch=Process.arch //返回一个 arm arm64等

PC的OS

使用Process.platform获取操作系统的信息

Process.platform

Java类的操作

获取所有的加载类

Frida提供了两个版本的方法来获取已经加载的类,注意,这里是指已经加载的类,就是当前进程加载的全部类,包括了系统、第三方和自身app的类型,方法有两种

  • 方法1

    Java.enumerateLoadedCalsses({
        onMatch:function(name,handle){
            //这里边可以获取类,name就是枚举出来的类信息 每次获取到一个类就回调这个函数
            //这个类里可以针对性的对特定类做类型处理或者转换,这里可以操作类
        }
        onComplete:function(){
            //执行完成调用函数
        }
    })
    

    如下是例子

    
    
  • 方法2

    使用同步方法获取所有的加载类有点麻烦,因此提供了一个异步版本,只需要调用异常就能获取到所有的类

    //该方法只能获取到所有的类,要操作的时候还要再次遍历 
    var allLoadedClasses = Java.enumerateLoadedClassesSync() //这个方法能获取所有的加载类
    

    例子

    // Script Inspired by https://github.com/0xdea/frida-scripts/tree/master/android-snippets
    function enumAllClasses() {
        var allClasses = [];
        var classes = Java.enumerateLoadedClassesSync();
    
        classes.forEach(function(aClass) {
            try {
                var className = aClass.replace(/\//g, ".");
            } catch (err) {}
            allClasses.push(className);
        });
    
        return allClasses;
    }
    

获取一个Java对象

Frida提供了一些API来操作一个java的类,如下

  • 获取一个Java类

    var MainActivity = Java.use("com.demo.activity.MainActivity")
    var StringBuffer=Java.use("java.lang.util.StringBuffer")
    
  • 新建一个Java类

    在获取到类之后,创建类包括了两个方法

    • new一个对象

      新建一个对象在Frida中需要使用clazz.$new(...)方法才能创建一个对象

    • 调用对象的构造函数

      要调用一个对象的构造函数,则使用clazz.$init()方法

  • 销毁一个Java类

    对象的销毁使用clazz.$dispose()函数完成

例如

//构造一个BaseDexClassLoader对象
var BaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader");
//获取一个DexFile对象
var DexFile= Java.use("dalvik.system.DexFile");
var dexfile = DexFile.$new("app_path");//执行app的路径,类似Android内的DexFile dexfile = new DexFile(app_path);一样的功能


获取app的所有加载的类

为了能获取到一个app的所有类,如下 code_02.js

//获取一个DexFile对象
var DexFile= Java.use("dalvik.system.DexFile");
var dexfile = DexFile.$new("app_path");//执行app的路径,类似Android内的DexFile dexfile = new DexFile(app_path);一样的功能
var entries = dexfile.entries();//获取所有的加载类,这个方法获取所有的加载类
var loaedClasses =[];
while(entries.hasMoreElements()){
    loadedClasses.push(entries.nextElement());//保存加载的类型
}
console.log("all dexfileLoadedClasses"+loadedClasses);

获取app加载的所有so模块

获取所有加载的模块可以使用如下调用方式code_03.js

'use strict;'

setTimeout(function () {

    Java.perform(function () {
       //
        console.log("Enumerate all loaded module ");
        var allModdules = Process.enumerateModules();
        console.log("  --> all loaded module" );
        var str = JSON.stringify(allModdules,null,2);
        console.log(""+str)
        
    });
},0);
//输出内容
[
  {
    "name": "app_process64",
    "base": "0x5569af1000",
    "size": 32768,
    "path": "/system/bin/app_process64"
  },
  {
    "name": "libandroid_runtime.so",
    "base": "0x714a699000",
    "size": 1982464,
    "path": "/system/lib64/libandroid_runtime.so"
  },
   ...//省略更多

获取指定模块的所有Imports

使用module.enumerateImports() -->返回一个数组

code_04.js

'use strict;'

setTimeout(function () {

    Java.perform(function () {
       //
        console.log("Enumerate all loaded module ");
        var allModdules = Process.enumerateModules();
        var module  = allModdules[0]
        // imports
        var imports_ = module.enumerateImports()
        console.log(" --> Imports --")
        var imports_str = JSON.stringify(imports_,null,2)
        console.log(imports_str)


    });
},0);
//输出结果
 --> Imports --
[
  {
    "type": "function",
    "name": "__libc_init",
    "module": "/system/lib64/libc.so",
    "address": "0x714a2bcc20"
  },
  {
    "type": "function",
    "name": "__register_atfork",
    "module": "/system/lib64/libc.so",
    "address": "0x714a281834"
  },
 ...//省略更多   

获取指定模块的所有Exports

使用module.enumerateExports() --> 返回一个数组

code_05.js

'use strict;'

setTimeout(function () {

    Java.perform(function () {
       //
        console.log("Enumerate all loaded module ");
        var allModdules = Process.enumerateModules();
        var module  = allModdules[0]
        // imports
        var exportes = module.enumerateExports()
        console.log(" --> Imports --")
        var exportes_str = JSON.stringify(exportes,null,2)
        console.log(exportes_str)


    });
},0);

//输出
 --> Exports --
[
  {
    "type": "function",
    "name": "AddSpecialSignalHandlerFn",
    "address": "0x5569af4418"
  },
  {
    "type": "function",
    "name": "sigaction",
    "address": "0x5569af4074"
  },
 ...//省略更多

获取模块的handle常用方法

有时候不用通过枚举的方法类获取模块,利用别的Api同样可以完成

var base1 = Process.findModuleByName("moduleName");
var base2 = Process.findModuleByAddress(address);//这里的模块地址可以是通过枚举获取,这里是module输出的base地址
var base3 = Process.getModuleByAddress(address);//同find类似
var base4 = Process.getModuleByName(moduelName);//痛find类似

获取app类的方法

如果要获取一个类的所有方法,在java中可以利用反射的方式获取,同样在这里也可以这样调用。如下

方法1 利用反射

code_06.js

'use strict;'

setTimeout(function () {

    Java.perform(function () {

        //获取一个类的所有方法1
        var Activity = Java.use("android.app.Activity");
        var methods  = Activity.class.getDeclaredMethods();//利用反射获取
        //释放掉创建的对象
        Activity.$dispose
        // var method_json = JSON.stringify(methods,null,2);
        console.log(" ---> all declaredMethods <----")
        methods.forEach(function (method) {
            console.log(String(method));// 转为字符串

        })
        // console.log(method_json)
    });
},0);

//输出
 ---> all declaredMethods <----
static void android.app.Activity.-wrap0(android.app.Activity,android.content.IntentSender,java.lang.String,int,android.content.Intent,int,int,android.os.Bundle)
private void android.app.Activity.cancelInputsAndStartExitTransition(android.os.Bundle)
private android.app.Dialog android.app.Activity.createDialog(java.lang.Integer,android.os.Bundle,android.os.Bundle)
private void android.app.Activity.dispatchRequestPermissionsResult(int,android.content.Intent)
private void android.app.Activity.dispatchRequestPermissionsResultToFragment(int,android.content.Intent,android.app.Fragment)
private void android.app.Activity.ensureSearchManager()
private void android.app.Activity.finish(int)
...//省略更多

利用Frida提供的APIs

code_07.js

利用Java.enumerateMethod("pattern")来获取指定类的方法,这里的pattern格式如下

className!methodName
# 参考官方的 https://frida.re/docs/javascript-api/#java 

例如要查找Activity下的所有以 on开头的函数,可以适应如下的pattern,每个method都可以包括一个后缀

# 格式
clazz!method/[i|s|u]
# i 大小写敏感 s 要求写完整方法签名 例如 *Activity!onCreate -->即可列出指定的方法

如下

# 大小写敏感
clazz!method/i
# 指定完整方法签名
clazz!method/s --> android.app.Activity!onCreate(android.os.Bundle):void
# 查找用户定义的类方法
clazz!methodu ---> *Activity*!on*/u

实际使用的时候没起效果,如下是测试程序

'use strict;'
setTimeout(function () {

    Java.perform(function () {

        if (Java.available === false) {
            console.log(" -- Can't use java < --")
            return ;
        }

        //列举出用户的类方法
        console.log(" ---> enumerate methods by pattern mode <----")
        var methods = Java.enumerateMethods("*Activity!onCreate(android.os.Bundle)/s");
        console.log(JSON.stringify(methods,null,2));

    });
},0);
//返回空
//---------另一个直接匹配方法名---------
'use strict;'
setTimeout(function () {

    Java.perform(function () {

        if (Java.available === false) {
            console.log(" -- Can't use java < --")
            return ;
        }

        //列举出用户的类方法
        console.log(" ---> enumerate methods by pattern mode <----")
        var methods = Java.enumerateMethods("*Activity!onCreate");
        console.log(JSON.stringify(methods,null,2));

    });
},0);
//输出如下 ---> enumerate methods by pattern mode <----
[
  {
    "loader": null,
    "classes": [
      {
        "name": "android.app.NativeActivity",
        "methods": [
          "onCreate"
        ]
      },
//省略更多

Java相关操作

函数Hook

使用Frida执行函数的hook利用了overload的机制,如下是一个函数的hook流程

#  执行hook的流程
获取一个类对象 -->获取所有的方法数组-->hook指定方法

例如java代码如下

 public void testInt(int a,int b){

        int c =a +b;
        LogW("Int"+ "a= "+a+" b="+b +" results "+c);

    }

如下是hook的程序代码

code_08.js

'use strict;'

setTimeout(function () {
    Java.perform(function () {

        // 指定类名
         var clzz_name="com.loopher.fridademoapp.TestFridaUtils";
        //获取类
        var clazz_hook = Java.use(clzz_name);
        //指定类方法
        var method = clazz_hook.testInt;
        //执行hook
        method.implementation=function () {
            //执行Hook 内的逻辑
            var args = arguments; //todo javascript 的机制,函数参数作为数组保存在arguments
            // var arg1 = args[0]; //获取参数值
            // var arg2 = args[1]; //获取参数值

            console.log(" ---> Hook method args <-----")
            for(var i =0;i<args.length;++i)
            console.log(" arg "+String(i)+ " value "+args[i] +" type:"+typeof(args[i]))
        }

    });

},0);

最后输出结果为

 ---> Hook method args <-----
 arg 0 value 1 type:number
 arg 1 value 2 type:number

函数参数修改

函数参数包括了基本类型,复杂类型。其中基本类型中的long类型在javascript中是不存在的,因此这个对象在进入到javascript的时候会被解析为一个object对象,复杂类型的数据包括了class,String 等

修改基础类型

记录类型的修改包括了如下

char,byte,short,int, float,double

这些数据在函数的参数中可以直接修改不需要做过多的修改,例如如下的java方法

  public void testInt(int a,int b){

        int c =a +b;
        LogW("Int"+ "a= "+a+" b="+b +" results "+c);
    }

code_09.js

'use strict;'

setTimeout(function () {
    Java.perform(function () {

        // 指定类名
        var clzz_name="com.loopher.fridademoapp.TestFridaUtils";
        //获取类
        var clazz_hook = Java.use(clzz_name);
        //指定类方法
        var method = clazz_hook.testInt;
        //执行hook
        method.implementation=function () {
            //执行Hook 内的逻辑
            var args = arguments; //todo javascript 的机制,函数参数作为数组保存在arguments
            // 再次调用       
            console.log(" ---> replace method args <-----")
           
            arguments[0]=200

            console.log(" "+JSON.stringify(arguments))
            //todo 再次执行
            var retval=null;
            try{
                retval = eval("this.testInt.apply(this,arguments)")
            }catch (e) {
                retval="error"
                console.log(" error "+String(e))
            }
            console.log("retval "+retval)
        }

    });

},0);

输出结果

 ---> replace method args <-----
 {"0":200,"1":2}
retval undefined
# 在AndroidStudio内的输出为
# 原始调用为
testFridaUtils.testInt(1, 2);
# 修改参数前
2020-07-01 18:28:55.995 18760-18760/com.loopher.fridademoapp W/FRIDA:  <++> Inta= 1 b=2 results 3
# 修改参数后
2020-07-01 18:27:22.869 18760-18760/com.loopher.fridademoapp W/FRIDA:  <++> Inta= 200 b=2 results 202
修改复杂类型

在这里复杂类型主要有long和对象,在javascript中没有long这个数据类型,该类型被统一为了object数据。

关于long类型数据的修改,放在后面来说

当我们需要修改一类对象的数据时,例如如下的java函数调用

//调用

User user = new User("testFrida", 202);
testFridaUtils.testObject(user);
//函数原型
public void testObject(User a){

        LogW(" testObject "+a.toString());
    }

正常会输出如下

2020-07-02 09:45:21.002 29023-29023/com.loopher.fridademoapp W/FRIDA:  <++>  testObject User{name='testFrida', age=202}

如果此时要修改User.name=PythonJavaScriptor,可以用如下方式

code_10.js

//观察一下对象数据并且注意变量的出现顺序
'use strict;'

function inspectObject(clzz){
    //todo 分析一个类对象的属性值,这里返回一个对象的属性列表
    var tmp_clzz = clzz
   var  clazz = Java.use("java.lang.Class");
   var  fields = Java.cast(tmp_clzz.getClass(),clazz).getDeclaredFields();
    return fields
}

setTimeout(function () {
    Java.perform(function () {

        var TestFridaUtils = Java.use("com.loopher.fridademoapp.TestFridaUtils");
        var testOject = TestFridaUtils.testObject;//获取这个方法
        testOject.implementation = function () {
            var User = arguments[0];//这里可以得到函数参数是一个,因此取出第一个即可
            //分析这个对象数据
            var fields = inspectObject(User);
            console.log(" >> "+fields);
        }

    });

},0);

//输出结果
 >> private int com.loopher.fridademoapp.User.age,private java.lang.String com.loopher.fridademoapp.User.name

从输出中可以看到变脸数组的第一个值是age,第二个是name,这里采用反射调用的方式,不过变量的索引修改参数类似了,我们修改这个对象数据如下

code_11.js

'use strict;'

// 参考 http://www.threetails.xyz/2019/07/14/Frida%20Hook%20Java/
function inspectObject(clzz){
    //todo 分析一个类对象的属性值,这里返回一个对象的属性列表
    var tmp_clzz = clzz
   var  clazz = Java.use("java.lang.Class");
   var  fields = Java.cast(tmp_clzz.getClass(),clazz).getDeclaredFields();
    return fields
}

setTimeout(function () {
    Java.perform(function () {

        var TestFridaUtils = Java.use("com.loopher.fridademoapp.TestFridaUtils");
        var testOject = TestFridaUtils.testObject;//获取这个方法
        testOject.implementation = function () {
            var User = arguments[0];//这里可以得到函数参数是一个,因此取出第一个即可
            //分析这个对象数据
            var fields = inspectObject(User);
            //参数类型一致访问后报异常,访问私有变量
            console.log(" >> "+fields +" User "+JSON.stringify(User));
            // arguments[0].age.value=1089; //参数修改方式1 
            fields[1].setAccessible(true); //参数修改方式2  反射调用,需要修改访问的权限
            // fields[1].set(arguments[0],111);
            fields[1].set(arguments[0],String("PythonJavaScriptor"))
            try{
                var retvalue = this.testObject.apply(this,arguments);//再次尝试运行
            }catch (e) {
                console.error("error "+e)
            }

        }

    });

},0);
//输出结果
2020-07-02 11:43:34.888 19162-19162/com.loopher.fridademoapp W/FRIDA:  <++>  testObject User{name='PythonJavaScriptor', age=202}
long类型参数修改

在Java中的long类型参数在javascript中是没有这个类型的,这个类型被当做object使用,在javascript中要想表示超过了int类型的数字,则可以使用BigInt来完成,不过这个库在Frida内是不存在的。如果要修改一个参数是long类型的参数,可以使用Int64 UInt64这两个API。如下

原始的java函数调用

  
  testFridaUtils.testLong(1L, 2L);

public void testLong(long a,long b){
        long c =a +b;
        LogW("Long "+ "a= "+a+" b="+b +" results "+c);

    }

执行hook修改的程序

code_13.js

'use strict;'

setTimeout(function () {

    Java.perform(function () {

        var method_name="";
        var clazz_name="com.loopher.fridademoapp.TestFridaUtils";
        var hook_clazz = Java.use(clazz_name);
        var hook_method = hook_clazz.testLong;
        hook_method.overload("long","long").implementation = function () {
            //修改参数
            console.log(" ---> before modified value "+ JSON.stringify(arguments));
            arguments[0] = new Int64(100);//修改第一个参数为 100
            // 同样支持字符串数字  var long =new Int64("1000")
            try{
                 var retval =this.testLong.apply(this,arguments);
                 console.log(" ---> execute successed ");
            }catch (e) {
                console.log("  errro "+ e);
            }
            console.log(" done");

        }
    });
},0);
//输出
2020-07-02 14:43:32.550 19162-19162/com.loopher.fridademoapp W/FRIDA:  <++> Long a= 100 b=2 results 102

返回值修改

返回值类型的修改一般只针对简单的类型,例如boolean int等,太复杂的类型需要做的工作会很多,没必要,如果确实有必要则再自己根据上述修改复杂类型的方法来做处理。如下是一个简单类型的数据修改

java调用

			if (isShow(1,2))
                    Toast.makeText(this,"Debugging Frida Hook And Interceptor function in Java @_@",Toast.LENGTH_LONG).show();
                else
                    Toast.makeText(this,"Retry again ",Toast.LENGTH_LONG).show();

//isShow
private boolean isShow(int a,int b){

        return (a>b)? true:false;
    }

该函数永远返回的false,执行hook后修改返回值

'use strict;'

setTimeout(function () {

    Java.perform(function () {

        var clazz_name = "com.loopher.fridademoapp.MainActivity";
        var hook_clazz = Java.use(clazz_name);
        var hook_method = hook_clazz.isShow;

        hook_method.overload("int","int").implementation = function () {

            console.log(" --> args "+JSON.stringify(arguments));
            //todo 默认直接返回 true
            return true;
        }

    });
},0);
//输出结果 能弹出 Debugging Frida Hook And Interceptor function in Java @_@ 的消息
后续有用到别的在继续补充
2020-07-02

参考

 类似资料: