1 对多dex进行适配 先hook attach函数,可以解决多dex文件找不到类名的错误。对内存中有完整dex的加壳app也可以hook。
Java.perform(function(){
var application = Java.use("android.app.Application");
application.attach.overload('android.content.Context').implementation = function(context) {
var result = this.attach(context); // 先执行原来的attach方法
var classloader = context.getClassLoader(); // 获取classloader
Java.classFactory.loader = classloader;
var Utils = Java.classFactory.use("com.rytong.emp.tool.Utils");\
//do something
return result;
}
}
2 JAVA层hook 2.1 hook构造函数 hook构造方法是固定写法:$init,代码如下:
var message = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message");
message.$init.implementation = function(hash,aes,base){
console.log("hash "+hash+" aes "+aes+" base "+base);
this.$init("123","eqweq",base);
};
如何构造方法有多个重载方法,则使用overload(……)形式即可,参数和参数之间用逗号隔开即可,代码如下;
var message = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message");
message.$init.overload("java.lang.String","java.lang.String"
,"java.lang.String").implementation = function(hash,aes,base){
console.log("hash "+hash+" aes "+aes+" base "+base);
this.$init("123","eqweq",base);
};
修改构造方法的参数及返回值
//修改参数:
this.$init("123","eqweq","tsts");
//修改返回值
this.$init(hash,aes,base)+"test";
2.2 hook普通方法 在Xposed中,对于静态方法和非静态方法使用不同的hook函数,在frida中不做区分,hook函数相同。
代码如下:
var myCrypto = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyCrypto");
myCrypto.md5Base64.implementation = function(){
console.log("msg2 is "+arguments[0]);
return this.md5Base64(arguments[0]+"test");
};
如果有多个重载方法,使用overload(……)形式即可,代码如下:
var myCrypto = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyCrypto");
myCrypto.md5Base64.overload("java.lang.String")implementation = function(msg){
console.log("msg1 is "+msg);
return this.md5Base64(msg+"test");
};
修改方法的参数以及返回值和修改构造方法的相同
2.3 构造和修改自定义类型对象和属性 在hook的时会遇到参数或返回值不是基本类型而是自定义类型,此时如果想修改他的属性值或者调用他的
一个方法我们会使用反射来进行操作,而在返回值的时候,想构造一个自定义类型的对象也是直接用反射实例化
一个对象进行操作的。
构造返回值自定义对象,代码如下:
//构造一个实例对象固定写法为:$new,也可以使用call形式
var coinmoney = Java.use("com.ydscience.fridaandroidstudydemo.utils.CoinMoney");
var utils = Java.use("com.ydscience.fridaandroidstudydemo.utils.Utils");
utils.getCoin.implementation = function(){
var coinObj = coinmoney.$new(12,"yj");//直接构造
//使用call构造
//var coinObj = coinmoney.$new.overload("int","java.lang.String")
//.call(coinmoney,12,"yiyi");
coinObj.setMoney(24);
console.log("money set is "+coinObj.getMoney());
return coinObj;
};
构造自定义对象后,调用方法直接调用其对应的方法即可,而修改自定义对象的属性,只能通过反射去修改字段值。代码如下:
var clazz = Java.use("java.lang.Class");
utils.getCoinMoney.implementation = function(){
var coin = arguments[0];
//调用方法
var money = coin.getMoney();
console.log("money is "+money);
//修改属性
var moneyFiled = Java.cast(coin.getClass(),clazz).getDeclaredField("money");
moneyFiled.setAccessible(true);
console.log("money field is "+moneyFiled.get(coin));
moneyFiled.setInt(coin,32);
return coin.getMoney();
};
静态成员变量的修改(私有变量也同样修改)
代码如下:
var clazz = Java.use("java.lang.Class");
var myLog = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyLog");
var intance = myLog.$new();
var moneyFiled = Java.cast(intance.getClass(),clazz).getDeclaredField("IS_DEBUG");
moneyFiled.setAccessible(true);
console.log("IS_DEBUG modify before is "+moneyFiled.get(intance));
moneyFiled.setBoolean(intance,true);
console.log("IS_DEBUG moidify after is "+moneyFiled.get(intance));
更加通用的方式
hook已经加载到内存中的类的一个方法,从中获取this指针,该指针即为当前的对象,调用代码如下:
var main = Java.use("seclover.crackme.MainActivity");
main.getInfo.implementation = function(){
//通过该变量的value去赋值
this.id.value = 1;
return this.getInfo();
}
2.4 hook重载函数 重载函数常见的几种写法如下:
(1)应用arguments
MyClass.MyFunc.overload("java.util.List").implementation = function() {
this.MyFunc.overload("java.util.List").apply(this, arguments);
}
(2)argments下标
//参数arguments[0]对应的真实的第一个参数
MyClass.MyFunc.overload("java.util.List").implementation = function () {
this.MyFunc(arguments[0]);
};
(3)具体的参数变量
MyClass.MyFuncs.overload("int", "int").implementation = function (s1, s2) {
var ret = this.MyFuncs(s1, s2);
}
(4)字符串数组
hook.hookMeArray.overload("[Ljava. .String;").implementation = {}
(5)使用call方法
MyClass.MyFunc.overload("java.lang.String").implementation = {
this.MyFunc.overload("java.lang.String").call(this, args[1])
MyClass.MyFunc.overload("java.lang.String").call()
}
2.5 对象实例化
var coinmoney = Java.use("com.ydscience.fridaandroidstudydemo.utils.CoinMoney");
//方式一
var newhg = coinmoney .$new(11,12);
//方式二
var newhg1 = coinmoney .$new.overload(“int","int").call(coinmoney,11,12);
2.6 hook 匿名内部类 匿名内部类一般在主类后面以“$ 1”数字表现。
var noNameInnerClazz = Java.use("com.ydscience.fridaandroidstudydemo.utils$1");
noNameInnerClazz.getName.overload("java.lang.String").implementation = function(s)
{
console.log("param is "+s);
return this.getName(s);
}
2.7 hook 内部类 内部类在主类后表现形式为“$ 内部类名”
//hook内部类构造函数
var innerClazz = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message$InnerClass");
nnerClazz.$init.overload("java.lang.String").implementation = function(s){
console.log("init param is "+s);
return this.$init(s);
}
//hook内部类的方法
innerClazz.setNumber.implementation = function(num){
console.log("num is "+num);
//修改属性
var number = innerClazz.class.getDeclaredField("number");
number.setAccessible(true);
//查看值
console.log("value is "+number.get(this));
//修改方式一
this.number.value = 122;
//修改方式二
number.setInt(123);
return this.setNumber(num);
}
2.8 打印堆栈 java层打印
Java.perform(function() {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
native层打印
var openFile = new File("/sdcard/openFile.txt","a+");
function hookOpen(){
var openPtr = Module.findExportByName("libc.so", "open");
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
Interceptor.replace(openPtr, new NativeCallback(function (pathPtr, flags) {
var path = Memory.readUtf8String(pathPtr);
var fd = open(pathPtr, flags);
// console.log("Got fd: " + fd);
var trace = Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
if (trace.indexOf("libcrypt-lib.so") > 0){
console.log("trace:"+trace);
console.log("Opening '" + path + "'");
openFile.write(path+"\n");
openFile.flush();
}
return fd;
}, 'int', ['pointer', 'int']));
}
2.9 动态调用 (1)静态方法
静态方法的调用和Xposed一样,直接使用类名调用方法,代码如下:
var hexStr = myCrypto.sha1Hex("ueyeueu");
console.log("hex is "+hexStr);
(2)非静态方法
对于非静态方法而言,如果想调用方法,则必须找到该方法依赖于调用的类对象,即在内存中找到该方法
依赖类已经创建的实例对象,获取该实例对象后,就可以任意调用该类中的方法。案例如下:
message类代码
public class Message {
private String hash;
private String aes;
private String base;
public Message(String hash, String aes, String base) {
this.hash = hash;
this.aes = aes;
this.base = base;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
public String getAes() {
return aes;
}
public void setAes(String aes) {
this.aes = aes;
}
public String getBase() {
return base;
}
public void setBase(String base) {
this.base = base;
}
}
在加载调用时代码如下:
Message message = new Message(MyCrypto.md5Base64(msg), MyCrypto.sha1Base64(msg)
,MyCrypto.Base64Encode(msg.getBytes()));
message.setHash("ddweqwerd");
通过调用方法可以看出,可以通过构造函数和setHash方法得到Message类对象,代码如下:
//setHash方法得到类Message对象实现非静态方法的任意调用
var MainActivity = Java.use("com.ydscience.fridaandroidstudydemo.MainActivity");
var message = Java.use("com.ydscience.fridaandroidstudydemo.utils.Message");
message.setHash.implementation = function(){
var hash = this.getHash();
var aes = this.getAes();
console.log("hash is "+hash+" aes is "+aes);
//通过创建MainActivity对象调用MD5方法
var intance = MainActivity.$new();
var md5 = intance.md5(hash);
console.log("md5 is "+md5);
return this.setHash(arguments[0]);
}
//构造方法得到类Message对象实现非静态方法的任意调用
message.$init.implementation =function(){
//todo
}
(3)Java.choose模式
使用Java.choose方法时,调用的类必须已经加载到内存,使用代码如下
setImmediate(function() {
Java.perform(function () {
Java.choose("com.ydscience.fridaandroidstudydemo.MainActivity", {
"onMatch":function(instance){
console.log("Instance found");
var str = Java.use("java.lang.String");
var re = instance.md5(str.$new("erwer"));
console.log("call result:" + re);
},
"onComplete":function() {
console.log(" Finished heap search");
}
});
});
});
在某些机型手机上使用Java.choose可能会崩溃,选择合适的手机进行调试使用。 (4)静态变量的调用与赋值
var clazz = Java.use("com.ydscience.fridaandroidstudydemo.utils.Utils");
var CoinMoney = clazz.sCoinMoney.value;
console.log("CoinMoney "+CoinMoney)
var money = CoinMoney.getMoney();
console.log("money is "+money);
clazz.line.value = 1100;
2.10 java.registerClass的使用 以注册一个线程为例,代码如下:
var Thread = Java.use("java.lang.Thread");
var Runnable = Java.use("java.lang.Runnable");
var MyRunnable = Java.registerClass({
name: 'com.example.mythread',
implements : [Runnable],
methods: {
run : function () {
//todo
}
}
});
var runnable = MyRunnable.$new();
var postThread = Thread.$new(runnable);
postThread.start();
2.11 实现和Xposed中XC_MethodReplace取代方法内部执行逻辑
var hook = Java.use("seclover.crackme.MainActivity$2");
hook.run.implementation = function(){
console.log("unhook detected");
//不调用原来方法,不执行return this.run();在此处只会打印一个log
}
2.12 UI thread 注入
Java.perform(function() {
var Toast = Java.use('android.widget.Toast');
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();
Java.scheduleOnMainThread(function() {
Toast.makeText(context, "Hello World", Toast.LENGTH_LONG.value).show();
})
})
2.13 获取对象实例
Java.choose("android.view.View", {
"onMatch":function(instance){//This function will be called for every instance found by frida
console.log(" Instance found");
},
"onComplete":function() {
console.log(" Finished heap search")
}
});
2.14 创建Java数组
byte数组创建
var buffer = Java.array('byte', [ 13, 37, 42 ]);
3 Native层 3.1导出函数 hook 导出函数时,直接使用导出的函数名。
打印参数时,如果时Java层调用的native交互式方法,如“JAVA_COM_YDFSCIEN_XXX”,则真实有效的参数从第三个开始,前两个分别为
JNIENV 和Jobject,其他的则从第一个开始算起打印参数与返回值,格式如下:
int : toInt32()
String:
var String_java = Java.use('java.lang.String');
var args_4 = Java.cast(args[2], String_java);
char与指针类型
Memory.readCString() 和Memory.readUtf8String()
//以十六进制输出
function printData(address) {
var keyAry = Memory.readByteArray(address, 16);
var byteAry = new Uint8Array(keyAry);
dataStr = "";
for(var i=0; i<byteAry.length; i++){
dataStr += ("0x" + byteAry.toString(16) + ",");
}
return dataStr;
}
示例代码如下:
Interceptor.attach(Module.findExportByName("libnative-lib.so" , "jstringToChar"), {
onEnter: function(args) {
var String_java = Java.use('java.lang.String');
var args_4 = Java.cast(args[1], String_java);
console.log("open called! args[0]:"+args_4);
},
onLeave:function(retval){
//Memory.readCString也可以
console.log("leave ret is "+Memory.readUtf8String(retval));
}
});
3.2 hook未导出函数 在hook未导出函数时,需要计算地址。地址为:基地址+相对地址。
(1)手动计算。
查看基地址
查看进程pid
ps | grep com.kuaiduizuoye.scan
查看基地址
cat /proc/2275/maps |grep libanti_spam.so
相对地址直接用IDA打开so文件就可以查看。
(2)自动计算。
Module.findBaseAddress(“libanti_spam.so”).add(相对地址)
无论哪种方式最后得到的地址,对地址最后一位需要进行奇偶性判断,1是thumb,0是arm。
参考地址:http://www.520monkey.com/archives/1256,操作代码如下:
var nativePointer = new NativePointer(0x8949479);
Interceptor.attach(nativePointer ,
onEnter: function(args) {
//todo
},
onLeave:function(retval){
//todo
}
)
3.3加载so中导出函数
var exports = Module.enumerateExportsSync("libnative-lib.so");
for(var i=0; i<exports.length; i++){
console.log("name is "+ exports.name +" address "+ exports.address);
}
3.4 查找so的基址
//遍历模块找基址
Process.enumerateModules({
onMatch: function (exp) {
if (exp.name == 'libnative-lib.so') {
send('enumerateModules find');
send(exp.name + "|" + exp.base + "|" + exp.size + "|" + exp.path);
send(exp);
return 'stop';
}
},
onComplete: function () {
send('enumerateModules stop');
}
});
//通过模块名直接查找基址
var soAddr = Module.findBaseAddress("libnative-lib.so");
send("soAddr:" + soAddr);
3.5 修改参数及返回值 代码如下:
//int类型
Interceptor.attach(faddptr, {
onEnter: function (args) {
send("onEnter add()");
x = args[0];
y = args[1];
args[0] = ptr(x * 2);
args[1] = ptr(y * 2);
send("hook add()修改参数为原来的两倍 args[0]:" + args[0].toInt32() + " args[1]:" + args[1].toInt32());
},
onLeave: function (retval) {
send("onLeave add()");
retval.replace(678);
send("add()修改返回值为:"+retval.toInt32())
}
});
//String类型
Interceptor.attach(fsayptr, {
onEnter: function (args) {
send("onEnter say()");
},
onLeave: function (retval) {
send("onLeave say()");
var s = Java.cast(retval, str);
send("say() 原返回值:" + s);
var env = Java.vm.getEnv();
var jstring = env.newStringUtf("frida hook native");
retval.replace(ptr(jstring));
send("修改say()返回值:" + Java.cast(jstring, str));
}
});
详细见myfrida.js
4 RPC的使用 rpc模板代码如下:
rpc.exports= {
add:function(a,b){
return a+b;
},
sha1:function(msg){
var ret;
Java.perform(function() {
var myCrypto = Java.use("com.ydscience.fridaandroidstudydemo.utils.MyCrypto");
ret = myCrypto.sha1Hex(msg);
});
return ret;
},
}
使用方法一:
(1)下载注入程序https://github.com/frida/frida/releases,名称为frida-inject-*
(2)下载rpc模板,从动态调用列表的功能按钮中选择导出并保存
(3)下载资源文件web.html vue.min.js 放入 /data/local/tmp
(4)将注入程序和脚本上传至远端,执行frida-inject -n com.seclover.dhook -s inject.js
(5)打开http://remoteip:3000/即可调用rpc
使用方法二:
配合dhook使用,打开dhook后,在动态调用页面创建脚本,填写需要调用的类和方法,完成调用即可。