这篇笔记主要记录在使用Frida
写一个测试框架中会需要的Api
的使用方法,包括了
为了保证能正常运行,需要使用如下方法执行以确保不会发生ClassNotFoundException
等异常。 code_01.js
Java.perform(function(){
//do something
//java类的操作需要在这个方法下执行,这样才能确保已经附加到了一个JVM的进程上
})
调用成功了Java.perform
函数,后续的java操作才能执行。
系统信息包括了手机的信息和pc的信息,使用如下方法获取
判断是否附加成功
Java.avaliable -->返回一个bool类型值,如果返回False则表示当前测试进程不能用
为了能获取到当前系统的版本信息,使用如下Api
Java.androidVersion
获取系统的架构信息可以使用如下APi
var arch=Process.arch //返回一个 arm arm64等
使用Process.platform
获取操作系统的信息
Process.platform
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;
}
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的所有类,如下 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);
获取所有加载的模块可以使用如下调用方式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"
},
...//省略更多
使用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"
},
...//省略更多
使用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"
},
...//省略更多
有时候不用通过枚举的方法类获取模块,利用别的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类似
如果要获取一个类的所有方法,在java中可以利用反射的方式获取,同样在这里也可以这样调用。如下
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
提供的APIscode_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"
]
},
//省略更多
使用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}
在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