Frida操作java類的一些記錄

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

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章