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