52的frida讲完了,看看。
Frida
简单基础指令
frida-ps -U
查看当前手机运行的进程
frida-ps --help
两种操作模式
CLI(命令行)模式 |
通过命令行直接将JavaScript脚本注入进程中,对进程进行操作 |
便于直接注入和操作 |
在较小规模的操作或者需求比较简单的场景中使用 |
RPC模式 |
使用Python进行JavaScript脚本的注入工作,实际对进程进行操作的还是JavaScript脚本,可以通过RPC传输给Python脚本来进行复杂数据的处理 |
在对复杂数据的处理上可以通过RPC传输给Python脚本来进行,有利于减少被注入进程的性能损耗 |
在大规模调用中更加普遍,特别是对于复杂数据处理的需求 |
自己倒是常用第一种
注入模式
Spawn模式 |
将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App |
在CLI模式中,Frida通过加上 -f 参数指定包名以spawn模式操作App |
适合于需要在App启动时即进行注入的场景,可以在App启动时即捕获其行为 |
当需要监控App从启动开始的所有行为时使用 |
Attach模式 |
在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作 |
在CLI模式中,如果不添加 -f 参数,则默认会通过attach模式注入App |
适合于已经运行的App,不会重新启动App,对用户体验影响较小 |
在App已经启动,或者我们只关心特定时刻或特定功能的行为时使用 |
Spawn模式
1
| frida -U -f 进程名 -l hook.js
|
attach模式 :
就是有没有-f
的区别
frida_server
可以通过执行命令添加-l 0.0.0.0:[port]
参数,从而实现使用自定义端口,但是不要忘记adb forward tcp:[port] tcp:[port]
转发
frida脚本
一些基础语法(正己)
Java.use(className) |
获取指定的Java类并使其在JavaScript代码中可用。 |
Java.perform(callback) |
确保回调函数在Java的主线程上执行。 |
Java.choose(className, callbacks) |
在内存中扫描 Java 堆,枚举 Java 对象(className)实例。比如可以使用 java.lang.String 扫描内存中的字符串。callbacks 提供两个参数:onMatch(instance) 和 onComplete ,分别是找到匹配对象和扫描完成调用。 |
Java.cast(obj, cls) |
将一个Java对象转换成另一个Java类的实例。 |
Java.enumerateLoadedClasses(callbacks) |
枚举进程中已经加载的所有Java类。 |
Java.enumerateClassLoaders(callbacks) |
枚举进程中存在的所有Java类加载器。 |
Java.enumerateMethods(targetClassMethod) |
枚举指定类的所有方法。 |
官方API
[官方文档][https://frida.re/docs/javascript-api/]
这里列举几个:
Java.choose(className, callback)
在内存中扫描 Java 堆,枚举 Java 对象(className)实例。比如可以使用 java.lang.String 扫描内存中的字符串。callbacks 提供两个参数:onMatch(instance) 和 onComplete,分别是找到匹配对象和扫描完成调用。
例子可以看后面的主动调用部分
Java.scheduleOnMainThread(fn)
在 VM 主线程(UI 线程)执行回调函数。Android 中操作 UI 元素需要在主线程中执行代码,scheduleOnMainThread 的作用就是用来在主线程中执行函数。此函数需要使用 Java.perform 包裹。
1 2 3 4 5 6 7 8 9 10 11
| 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 frida!", Toast.LENGTH_LONG.value).show(); }); });
|
enumerateLoadedClasses(callbacks)
枚举当前已加载的类。callbacks 参数是一个对象,需要提供两个回调函数—— onMatch(className) 和 onComplete。每次找到一个类就会调用一次 onMatch,全部找完之后,调用 onComplete。
日志输出
console.log() |
使用JavaScript直接进行日志打印 |
多用于在CLI模式中,console.log() 直接输出到命令行界面,使用户可以实时查看。在RPC模式中,console.log() 同样输出在命令行,但可能被Python脚本的输出内容掩盖。 |
send() |
Frida的专有方法,用于发送数据或日志到外部Python脚本 |
多用于RPC模式中,它允许JavaScript脚本发送数据到Python脚本,Python脚本可以进一步处理或记录这些数据。 |
来点栗子
简单模板
1 2 3 4 5 6
| function main(){ Java.perform(function(){ hookTest1(); }); } setImmediate(main);
|
1.Hook普通方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function hookTest1(){ var utils = Java.use("类名"); utils.method.implementation = function(a, b){ a = 123; b = 456; var retval = this.method(a, b); console.log(a, b, retval); return retval; } }
|
2.Hook重载参数
1 2 3 4 5 6 7 8 9 10 11 12
|
function hookTest2(){ var utils = Java.use("com.zj.wuaipojie.Demo"); utils.Inner.overload('com.zj.wuaipojie.Demo$Animal','java.lang.String').implementation = function(a,b){ b = "aaaaaaaaaa"; this.Inner(a,b); console.log(b); } }
|
3.Hook构造函数
类的构造函数在frida中使用$init
表示:
1 2 3 4 5 6 7 8 9
| function hookTest3(){ var utils = Java.use("com.zj.wuaipojie.Demo"); utils.$init.overload('java.lang.String').implementation = function(str){ console.log(str); str = "52"; this.$init(str); } }
|
4.Hook字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function hookTest5(){ Java.perform(function(){ var utils = Java.use("com.zj.wuaipojie.Demo"); utils.staticField.value = "我是被修改的静态变量"; console.log(utils.staticField.value); Java.choose("com.zj.wuaipojie.Demo", { onMatch: function(obj){ obj._privateInt.value = "123456"; obj.privateInt.value = 9999; }, onComplete: function(){
} }); }); }
|
5.Hook内部类
1 2 3 4 5 6 7 8 9 10 11
| function hookTest6(){ Java.perform(function(){ var innerClass = Java.use("com.zj.wuaipojie.Demo$innerClass"); console.log(innerClass); innerClass.$init.implementation = function(){ console.log("eeeeeeee"); } }); }
|
6.枚举所有的类与类的所有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function hookTest7(){ Java.perform(function(){ Java.enumerateLoadedClasses({ onMatch: function(name,handle){ if(name.indexOf("com.zj.wuaipojie.Demo") !=-1){ console.log(name); var clazz =Java.use(name); console.log(clazz); var methods = clazz.class.getDeclaredMethods(); console.log(methods); } }, onComplete: function(){} }) }) }
|
7.枚举类的所有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function hookTest8(){ Java.perform(function(){ var Demo = Java.use("com.zj.wuaipojie.Demo"); var methods =Demo.class.getDeclaredMethods(); for(var j=0; j < methods.length; j++){ var methodName = methods[j].getName(); console.log(methodName); for(var k=0; k<Demo[methodName].overloads.length;k++){ Demo[methodName].overloads[k].implementation = function(){ for(var i=0;i<arguments.length;i++){ console.log(arguments[i]); } return this[methodName].apply(this,arguments); } } } }) }
|
8.主动调用
对于静态方法和动态方法,动态方法需要这个实例有创建才能hook调用,静态则是已经存在的方法。
1 2
| var ClassName=Java.use("com.zj.wuaipojie.Demo"); ClassName.privateFunc("传参");
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| var ret = null; Java.perform(function () { Java.choose("com.zj.wuaipojie.Demo",{ onMatch:function(instance){ ret=instance.privateFunc("aaaaaaa"); }, onComplete:function(){ } }); })
|