frida安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 pip install frida pip install frida-tools(frida-tools里有frida) frida --version 版本对应 frida12.3.6 Android5-6 python3.7 frida12.8.0 Android7-8 python3.8 frida14+ Android9+ python3.8 安装指定版本frida(虚拟环境中可以安装最新的,不一定能装指定版本的) 先切换系统环境变量,把对应版本的python环境变量设置好,再安装指定版本的frida 再装指定版本的frida-tools(注意先后顺序,一个tools对应多个frida版本) frida-tools版本查看 https://github.com/frida/frida/releases里面有frida的版本,每个下面有frida-tools的版本号 装好之后在python的目录下有frida相关的东西,把这些文件复制到虚拟环境中的目录下 (直接进虚拟环境的python,pip装不上包) 最后再进虚拟环境,运行pip install frida-tools (实际上虚拟环境这个不一定得配) frida代码提示的配置 npm i @types/frida-gum frida-server的配置 https://github.com/frida/frida/releases里面有frida的版本,每个下面有frida-server的版本号,frida-server版本与frida版本要匹配 adb push frida-server-14.2.18-android-arm64 /data/local/tmp/ adb shell进去,然后./运行(root权限运行),cmd中运行frida-ps -U如果能查看手机上的进程,就说明没问题
静态方法和实例方法的Hook 不需要管修饰符(共有、私有、受保护的、没有修饰符的、默认修饰符的),不需要区分静态和实例方法,都是使用Java.use
以下类源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.xxx.hook;public class Money { private static String flag; private int amount; private String currency; public void setCurrency (String str) { } public Money (String str, int i) { this .currency = str; this .amount = i; } public int getAmount () { return this .amount; } public void setAmount (int i) { this .amount = i; } public static String getFlag () { return flag; } public static void setFlag (String str) { flag = str; } public String getCurrency () { return this .currency; } public String getInfo () { return this .currency + ": " + this .amount + ": " + flag; } }
首先Hook的逻辑要在perform的匿名函数内部
1 2 3 Java .perform (function ( ){ Java .use .... });
1 2 3 4 5 6 7 8 9 10 11 var money = Java .use ("com.xxx.hook.Money" );money.getInfo .implementation = function ( ) { var result = this .getInfo (); console .log ("money.getInfo result: " , result) return result; } money.setFlag .implementation = function (a ) { console .log ("money.setFlag param: " , a); return this .setFlag (a); }
至于返回值的话,目标函数的返回值是什么就返回什么,void的话可返回可不返回,一般是返回一下
函数参数和返回值的修改 实际上就是在return的时候修改值
1 2 3 4 5 6 7 8 9 10 11 12 13 var money = Java .use ("com.xxx.hook.Money" );money.getInfo .implementation = function ( ) { var result = this .getInfo (); console .log ("money.getInfo result: " , result) return "这是修改后的返回值" ; } money.setFlag .implementation = function (a ) { console .log ("money.setFlag param: " , a); return this .setFlag ("这是我新设置的参数" ); }
构造方法的hook 先获取类,再调用类名.$init
1 2 3 4 5 6 var money = Java .use ("com.xxx.hook.Money" );money.$init .implementation = function (a, b ) { console .log ("money.$init param: " , a, b); return this .$init("美元" , 200 ); }
对象参数的修改 也就是函数的参数是一个对象,需要用到$new
语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Wallet { private static String flag; private int balance; private String brand; private InnerStructure innerStructure = new InnerStructure (); private String name; public Wallet (String str, String str2, int i) { this .name = str; this .brand = str2; this .balance = i; } public boolean deposit (Money money) { if (money == null || money.getAmount() <= 0 ) { return false ; } this .balance += money.getAmount(); return true ; } ...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var wallet = Java .use ("com.xxx.hook.Wallet" );var money = Java .use ("com.xxx.hook.Money" );wallet.deposit .implementation = function (a ) { console .log ("wallet.deposit param: " , a.getInfo ()); return this .deposit (money.$new("美元" , 200 )); } var wallet = Java .use ("com.xxx.hook.Wallet" );wallet.deposit .implementation = function (a ) { a.setAmount (2000 ); console .log ("wallet.deposit param: " , a.getInfo ()); return this .deposit (a); }
HashMap的打印 上一个里面,在打印篡改后的值还有封装好的函数getInfo()
,而且类里面也没有重写toString
方法(不重写toString
方法的话在打印toString
的时候会输出类名@xxx
这种东西),那么没有这种封装函数的时候怎么办呢?
Utils.shufferMap方法如下(不是系统自带的,是自写的)
1 2 3 4 5 6 7 public static String shufferMap (HashMap<String, String> hashMap) { StringBuilder sb = new StringBuilder (); for (String str : hashMap.keySet()) { sb.append(hashMap.get(str)); } return sb.toString(); }
现在要Hook这个方法,取里面的参数,把HashMap参数打印出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var utils = Java .use ("com.xxx.hook.Utils" );var stringBuilder = Java .use ("java.lang.StringBuilder" );utils.shufferMap .implementation = function (a ) { var key = a.keySet (); var it = key.iterator (); var result = stringBuilder.$new(); while (it.hasNext ()){ var keystr = it.next (); var valuestr = a.get (keystr); result.append (valuestr); } console .log ("utils.shufferMap param: " , result.toString ()); var result = this .shufferMap (a); console .log ("utils.shufferMap result: " , result); return result; }
从Java里扣代码进js运行的时候,有些类型js里是没有的,需要改,比如string
、set
、Iterator
这种类型在js都是var
类型,js里数据类型只有var或者let,且不需要强转,要注意哪些参数是js类型,哪些是java类型
那为什么之前在hook函数的内部 return "aaaa"
能顺利返回?这里"aaaa"
是js的类型,为什么直接传递给java就没事,而不需要去用stringBuilder.$new("aaaa")
来转换为java类型呢?因为frida在获取参数和返回参数这里帮我们封装好了,比如前面的function(a)
,这里的a参数frida就会自己去识别是什么java类型,然后我们就可以直接调用它里面有的方法,总之分清楚哪些是js的类型哪些是java类型的意义就是有的方法只在js有,有的方法只在java有,具体还得测,这个是比较迷
重载方法的hook .overload()
语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var utils = Java .use ("com.xxx.hook.Utils" );utils.getCalc .overload ('int' , 'int' ).implementation = function (a, b ) { console .log ("utils.getCalc param: " , a, b); return this .getCalc (a, b); } utils.getCalc .overload ('int' , 'int' , 'int' ).implementation = function (a, b, c ) { console .log ("utils.getCalc param: " , a, b, c); return this .getCalc (a, b, c); } utils.getCalc .overload ('int' , 'int' , 'int' , 'int' ).implementation = function (a, b, c, d ) { console .log ("utils.getCalc param: " , a, b, c, d); return this .getCalc (a, b, c, d); }
hook所有重载方法
方法名.overloads
会返回所有重载函数,方法名.length
会返回重载函数的个数,方法名.overloads[int i]
会直接访问到该函数
js里arguments
会返回当前函数的参数数组(动态的,给几个参数,arguments就有多长),arguments[int i]
直接访问到对应序号的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var utils = Java .use ("com.xxxx.hook.Utils" );var overloadsArr = utils.getCalc .overloads ;for (var i = 0 ; i < overloadsArr.length ; i++) { overloadsArr[i].implementation = function ( ) { showStack (); var params = "" ; for (var j = 0 ; j < arguments .length ; j++) { params += arguments [j] + " " ; } console .log ("utils.getCalc is called! params is: " , params); console .log (this ); return this .getCalc .apply (this , arguments ); } }
js里的this,如果this在函数体内部,谁调用了这个函数谁就是this
1 2 3 4 utils.getCalc .overload ('int' , 'int' , 'int' , 'int' ).implementation = function (a, b, c, d ) { console .log ("utils.getCalc param: " , a, b, c, d); return this .getCalc (a, b, c, d); }
上面的代码里的this
就是utils
这个类,因为是utils
类调用了getCalc
方法
主动调用Java函数 区分静态方法和实例方法,静态方法直接拿到类之后调用类名.方法名
,实例方法需要先实例化对象再调用
实例方法的调用又区分是否需要新建对象,还是说直接在内存中搜索对应的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function newcall ( ){ Java .perform (function ( ){ var money = Java .use ("com.xxx.hook.Money" ); money.setFlag ("test" ); var moneyObj = money.$new("卢布" , 1000 ); console .log (moneyObj.getInfo ()); Java .choose ("com.xxx.hook.Money" , { onMatch : function (obj ){ console .log (obj.getInfo ()); }, onComplete : function ( ){ console .log ("内存中的Money对象搜索完毕" ); } }); }) }
然后就可以在frida命令开始之后直接在终端命令行调用newcall
函数
函数调用栈的打印 主动调用,打印堆栈,用的是android.util.Log.getStackTraceString
方法
1 Log.getStackTraceString(new Throwable())
1 2 3 4 5 function showStack ( ){ Java .perform (function ( ){ console .log (Java .use ("android.util.Log" ).getStackTraceString (Java .use ("java.lang.Throwable" ).$new())); }); }
获取和修改类的字段 访问静态字段使用 类名.属性名.value
来访问或者修改
访问实例字段需要先创建对象,然后使用 对象名.属性名.value
来访问或者修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function test ( ){ Java .perform (function ( ) { var money = Java .use ("com.xxx.hook.Money" ); console .log (money.flag .value ); money.flag .value = "tt" ; console .log (money.flag .value ); var moneyObj = money.$new("欧元" , 2000 ); console .log (moneyObj.currency .value ); moneyObj.currency .value = "tt currency" ; console .log (moneyObj.currency .value ); Java .choose ("com.xxx.hook.Money" , { onMatch : function (obj ) { console .log ("Java.choose Money: " , obj.currency .value ); }, onComplete : function ( ) { } }); Java .choose ("com.xxx.hook.BankCard" , { onMatch : function (obj ) { console .log ("Java.choose BankCard: " , obj._accountName .value ); }, onComplete : function ( ) { } }); }); }
内部类与匿名类的hook demo如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Wallet { private static String flag; private int balance; private String brand; private InnerStructure innerStructure = new InnerStructure (); private String name; public Wallet (String str, String str2, int i) { this .name = str; this .brand = str2; this .balance = i; } public boolean deposit (Money money) { ... } public Money withdraw (String str, int i) { ... } public boolean addBankCard (BankCard bankCard) { ... } public class InnerStructure { private ArrayList<BankCard> bankCardsList = new ArrayList <>(); public InnerStructure () { } public String toString () { return this .bankCardsList.toString(); } } ... }
访问 Wallet
类的InnerStructure
内部类,需要使用$来访问
1 var Wallet $InnerStructure = Java .use ("com.xxx.hook.Wallet$InnerStructure" );
现在要获取InnerStructure
内部类的bankCardsList
,这里就应该搜索内存去寻找这个类,而不是自己去新建
匿名内部类就是在方法内部直接new对象
1 2 3 4 5 6 logOutPut(new Money ("欧元" , ItemTouchHelper.Callback.DEFAULT_DRAG_ANIMATION_DURATION) { @Override public String getInfo () { return getCurrency() + " " + getAmount() + " 这是匿名内部类" ; } }.getInfo());
匿名内部类的访问就是$1或者$2…以此类推,序号可以看smali代码或者枚举所有已经加载的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function test1 ( ){ Java .perform (function ( ) { Java .choose ("com.xxx.hook.Wallet$InnerStructure" , { onMatch : function (obj ) { console .log ("Java.choose Wallet$InnerStructure: " , obj.bankCardsList .value ); }, onComplete : function ( ) { } }); var money$1 = Java .use ("com.xxx.app.MainActivity$1" ); money$1.getInfo .implementation = function ( ) { var result = this .getInfo (); console .log ("money.getInfo result: " , result); return result; } }); }
枚举所有已加载的类与枚举类的所有方法 枚举所有已加载类实际上就是执行enumerateLoadedClassesSync()
,命令行里面直接执行也可以
1 2 Java .enumerateLoadedClassesSync ();console .log (Java .enumerateLoadedClassesSync ().join ("\n" ));
枚举类的所有方法需要用到反射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function test2 ( ){ Java .perform (function ( ){ var wallet = Java .use ("com.xxx.hook.Wallet" ); var methods = wallet.class .getDeclaredMethods (); var constructors = wallet.class .getDeclaredConstructors (); var fields = wallet.class .getDeclaredFields (); var classes = wallet.class .getDeclaredClasses (); for (let i = 0 ; i < methods.length ; i++) { console .log (methods[i].getName ()); } console .log ("============================" ); for (let i = 0 ; i < constructors.length ; i++) { console .log (constructors[i].getName ()); } console .log ("============================" ); for (let i = 0 ; i < fields.length ; i++) { console .log (fields[i].getName ()); } console .log ("============================" ); for (let i = 0 ; i < classes.length ; i++) { console .log (classes[i].getName ()); var Wallet $InnerStructure = classes[i].getDeclaredFields (); for (let j = 0 ; j < Wallet $InnerStructure.length ; j++) { console .log (Wallet $InnerStructure[j].getName ()); } } }); }
hook类的所有方法 实际上就是把上面的结合起来,先获取类,再枚举这个类下面的所有方法,再对每个方法,去Hook方法的所有重载
这里要注意的一点
1 2 3 4 5 6 7 8 9 10 var methodName = methods[i].getName ();var overloads = utils.methodName .overloads ;这样是访问不到这个方法下面的所有重载的,跟js的语法有关系,这里utils.methodName 会被当初属性去处理而不是字符串 需要使用utils[methodName]去访问该方法 就和 var obj = {a :"aaaaa" ,b :"bbbbb" };obj.a ; var methodName = 'a' ;obj.methodName ; obj[methodName];
代码实现循环的时候要注意作用域,var定义的变量和let定义的变量的作用域不一样,有时候循环会出问题(超出methods的范围),这个具体调试的时候再去改,js语法的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Java .perform (function ( ) { function hookFunc (methodName ) { console .log (methodName); var overloadsArr = utils[methodName].overloads ; for (var j = 0 ; j < overloadsArr.length ; j++) { overloadsArr[j].implementation = function ( ) { var params = "" ; for (var k = 0 ; k < arguments .length ; k++) { params += arguments [k] + " " ; } console .log ("utils." + methodName + " is called! params is: " , params); return this [methodName].apply (this , arguments ); } } } var utils = Java .use ("com.xxx.hook.Utils" ); var methods = utils.class .getDeclaredMethods (); for (var i = 0 ; i < methods.length ; i++) { var methodName = methods[i].getName (); hookFunc (methodName); } });
Java.registerClass 用来给app注入新的类
实际上这个使用有点麻烦,要求app中有一个未实现的接口,然后我们实现这个接口,去注入实现接口的类
了解即可,一般使用注入dex文件的方法来注入类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const MyWeirdTrustManager = Java .registerClass ({ name : 'com.xxx.app.MyRegisterClass' , implements : [Java .use ("com.xxx.app.TestRegisterClass" )], fields : { description : 'java.lang.String' , limit : 'int' , }, methods : { $init() { console .log ('Constructor called' ); }, test1 : [{ returnType : 'void' , argumentTypes : [], implementation ( ) { console .log ('test1 called' ); } }, { returnType : 'void' , argumentTypes : ['java.lang.String' , 'int' ], implementation (str, num ) { console .log ('test1(str, num) called' , str, num); } }], test2 (str, num ) { console .log ('test2(str, num) called' , str, num); return null ; }, } }); var myObj = MyWeirdTrustManager .$new();myObj.test1 (); myObj.test1 ("test1" , 100 ); myObj.test2 ("test2" , 200 ); myObj.limit .value = 10000 ; console .log (myObj.limit .value );
frida注入dex文件 怎么生成一个自己的dex去注入呢?第一种方法肯定是IDEA自己写个Java代码,然后编译成.class
文件(AS中也一样,可以找到编译之后生成的.class
文件),然后用dx工具将.class
文件编译成.dex
文件
dx工具打包dex文件 AS编译,选Build->Make project
然后在build
目录的outputs
文件夹下有apk文件,在intermediates
文件夹下的javac
文件夹下可以找到.class
文件
dx工具的路径在 SDK\build-tools\版本号
目录里面的dx.bat
dx打包.class文件为.dex文件
1 2 3 4 dx --dex --output=patch.dex class文件所在目录,可以是打包所有class也可以指定class dx --dex --output=patch.dex com\xxx\new\* dx --dex --output=patch.dex com\xxx\new\Test.class 这里目录不能是绝对路径,要是相对路径,最好就是包名路径
使用baksmali与smali打包dex文件 1 2 3 4 源码 https://github.com/JesusFreke/smali jar https://bitbucket.org/JesusFreke/smali/downloads/ 反编译dex java -jar baksmali-2.5.2.jar d classes.dex 回编译ex java -jar smali-2.5.2.jar a smali
baksmali是把dex编译生smali文件,smali是把smali反编译为dex文件
把apk作为压缩包打开,拖出来里面的dex文件,运行上面的命令,生成一个out文件夹,里面有包名,包名目录下的就有.smali文件,一般是每个类对应一个.smali文件,然后筛选一下只保留我们需要的smali文件,其他的都删了
回编译的时候参数为目录名 dex java -jar smali-2.5.2.jar a out
然后会生成一个out.dex
文件
使用apktool将apk文件解包提取出dex文件
1 2 3 apktool d demo.apk -o app 会输出一个app目录,里面有smali文件和dex文件 apktool d demo.apk -r -s app 不解密源代码和资源文件 apktool b app -o demo2.apk 指定目录,输出为一个apk文件,但是这个apk文件中没有META-INF文件夹,也就是缺少签名
apksigner的使用 对apk进行签名,进入Android SDK/build-tools/SDK版本,输入命令
1 apksigner sign --ks myname.jks app.apk
若密钥库中有多个密钥对,则必须指定密钥别名
1 apksigner sign --ks myname.jks --ks-key-alias myname app.apk
默认同时使用v1和v2签名,若想禁用v2签名
1 2 3 apksigner sign -v2-signing-enabled false --ks myname.jks app.apk -v2-signing-enabled 是否开启v2签名,默认开启 -v1-signing-enabled 是否开启v1签名,默认开启
密钥库生成方法,AS中 build-> generate signed bundle or apk -> 选apk next -> create new
1 2 3 4 Key store path随意,下面的password随意,这个密码是密钥库的密码 Key部分 Alias是密钥的别名,随意,password随意,这个密码是密钥的密码 Certificate里面的信息随意
完成之后点击OK
dex注入 语法
1 Java.openClassFile("/data/local/tmp/mydemo.dex" ).load();
然后再在Hook.js里使用dex里面的类
1 2 3 4 5 6 7 8 9 10 Java .perform (function ( ) { Java .openClassFile ("/data/local/tmp/patch.dex" ).load (); var test = Java .use ("com.xxx.myapplication.Test" ); var utils = Java .use ("com.xxx.hook.Utils" ); utils.shufferMap .implementation = function (map ) { var result = test.print (map); console .log (result); return result; } });
Hook枚举类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 枚举类enum概念,枚举类是一种特殊的类,里面包含一组有限的只读对象,枚举是一组常量的集合 比如季节,一共4个,这种应该是只读 自定义枚举类 1.构造器私有化 2.定义public static fianl修饰的属性(比如spring summer autumn winter) 3.枚举对象名(常量)通常全部大写,多个单词下划线分割 4.创建对象赋值给这些属性 5.定义toString方法 enum类 1.使用关键字enum代替class 2.public static final Season SPRING = new Season("春天");可以简化成 SPRING("春天") 3.枚举对象必须放在枚举类的第一行 4.有多个枚举对象,使用逗号间隔,最后一个分号结尾 5.使用无参构造器创建枚举对象,则小括号可以忽略 6.枚举对象可以有多个属性 7.enum类重写了toString方法
1 2 3 4 5 6 7 8 9 10 package com.xxx.app;public enum Season { SPRING, SUMMER, AUTUMN, WINTER } 不写明toString方法,实际上在enum 类中默认已经重写了toString方法,可以直接输出对象
枚举类一旦被加载,所有的对象都被创建,需要搜索内存去找到对象
1 2 3 4 5 6 7 8 9 10 11 12 Java .perform (function ( ) { Java .choose ("com.xxx.app.Season" , { onMatch : function (obj ) { console .log (obj.ordinal ()); }, onComplete : function ( ) { } }) console .log (Java .use ("com.xxx.app.Season" ).values ()); });
frida写文件 frida只提供了写文件的api,读文件的话需要Hook so文件,这个是后话
1 2 3 4 var ios = new File ("/sdcard/test.txt" ,"w" );ios.write ("test new" ); ios.flush (); ios.close ();
正常运行会报错,没权限,即使app被赋予了存储空间的权限也写不进去,这是高版本Android的设置
SD卡分为共有存储空间与私有存储空间
1 2 /data/data/包名 app的私有目录 /sdcard/Android/data/包名 app的私有目录
可以在app的私有目录下写
获取私有目录的方法
1 2 3 4 5 6 Environment.getRootDirectory().toString(); Environment.getDataDirectory().toString(); Environment.getDownloadCacheDirectory().toString(); Environment.getExternalStorageDirectory().toString(); Environment.getExternalStorageState().toString(); getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();
以Hook getExternalFilesDir
方法为例,该方法是非静态方法,需要先实例化对象,getExternalFilesDir
方法来自于类android.content.ContextWrapper
,所以需要实例化android.content.ContextWrapper
类,而android.content.ContextWrapper
的构造方法需要参数context,就需要获取context
1 2 3 4 5 6 7 8 9 10 11 Java .perform (function ( ) { var current_application = Java .use ('android.app.ActivityThread' ).currentApplication (); var context = current_application.getApplicationContext (); var path = Java .use ("android.content.ContextWrapper" ).$new(context).getExternalFilesDir ("Download" ).toString () console .log (path); var ios = new File (path + "/xxx.txt" , "w" ); ios.write ("xiaojianbang is very very good!!!\n" ); ios.flush (); ios.close (); });
类型强转Java.cast
主要用于解决向上转型的时候,比如Hashmap
对象赋值给了map
类这种,在调用toString
方法无法得到结果(会输出 [object…]),类似的还有List
类,ArrayList
对象赋值给了List
类也无法直接打印
需要再强行转为子类
1 2 3 4 5 6 7 8 9 10 11 var utils = Java .use ("com.xxx.hook.Utils" );utils.shufferMap .implementation = function (hashmap ) { console .log ("hashmap: " , hashmap); return this .shufferMap (hashmap); } utils.shufferMap2 .implementation = function (map ) { console .log ("map: " , map); var result = Java .cast (map, Java .use ("java.util.HashMap" )); console .log ("map: " , result); return this .shufferMap2 (result); }
Java.array处理对象数组 有的情况下,想去Hook参数为数组对象的函数
1 2 3 4 5 6 7 8 public static String myPrint (String[] strArr) { StringBuilder sb = new StringBuilder (); for (String str : strArr) { sb.append(str); sb.append("|" ); } return sb.toString(); }
1 2 3 4 5 var utils = Java .use ("com.xxxx.hook.Utils" );var strarr = Java .array ("Ljava.lang.String;" , ["name" , "jack" , "harry" , "tom" ]);console .log (utils.myPrint (strarr));
Object数组的构建及可变数组 1 2 3 4 5 6 7 8 public static String myPrint (Object... objArr) { StringBuilder sb = new StringBuilder (); for (Object obj : objArr) { sb.append(obj); sb.append("|" ); } return sb.toString(); }
1 2 3 4 5 6 7 var utils = Java .use ("com.xxx.hook.Utils" );var bankCard = Java .use ("com.xxx.hook.BankCard" );var bankCardObj = bankCard.$new("jack" , "123456789" , "CBDA" , 1 , "15900000000" );var integer = Java .use ("java.lang.Integer" );var boolean = Java .use ("java.lang.Boolean" );console .log (utils.myPrint (["xxx" , integer.$new(30 ), boolean.$new(true ), bankCardObj]));
这里就不能用 console.log(utils.myPrint(["xxx", 30, true, bankCardObj]));
方式,因为类型不同,必须得用Java.array
处理,而且int
、boolean
类型不能被frida自动转换为对象,只有字符串可以,所以得new出来
Arraylist的主动调用 arrayList
的add方法,接受的参数也是object,和上面一样,只有字符串不需要进行转换,其他的都需要在js中进行封装为对象之后再传给add方法
1 2 3 4 5 6 7 8 9 10 11 var arrayList = Java .use ("java.util.ArrayList" ).$new();var integer = Java .use ("java.lang.Integer" );var boolean = Java .use ("java.lang.Boolean" );var bankCard = Java .use ("com.xxx.hook.BankCard" );var bankCardObj = bankCard.$new("jack" , "123456789" , "CBDA" , 1 , "15900000000" );arrayList.add ("xiaojianbang" ); arrayList.add (integer.$new(30 )); arrayList.add (boolean.$new(true )); arrayList.add (bankCardObj); var utils = Java .use ("com.xxx.hook.Utils" );console .log (utils.myPrint (arrayList));
Java.enumerateClassLoaders Hook动态加载的dex
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public void dynamic () { String str = getDir("shell" , 0 ).getAbsolutePath() + File.separator + "xxx.dex" ; Context applicationContext = getApplicationContext(); try { InputStream open = applicationContext.getAssets().open("xxx.dex" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); byte [] bArr = new byte [1024 ]; while (true ) { int read = open.read(bArr); if (read != -1 ) { byteArrayOutputStream.write(bArr, 0 , read); } else { byte [] decrypt = decrypt(byteArrayOutputStream.toByteArray()); open.close(); byteArrayOutputStream.close(); FileOutputStream fileOutputStream = new FileOutputStream (new File (str)); fileOutputStream.write(decrypt); fileOutputStream.close(); DexClassLoader dexClassLoader = new DexClassLoader (str, applicationContext.getCacheDir().getAbsolutePath(), getApplicationInfo().nativeLibraryDir, getClassLoader()); new File (str).delete(); Class loadClass = dexClassLoader.loadClass("com.xxx.app.Dynamic" ); Toast.makeText(this , (String) loadClass.getDeclaredMethod("sayHello" , new Class [0 ]).invoke(loadClass.newInstance(), new Object [0 ]), 1 ).show(); return ; } } } catch (Exception e) { e.printStackTrace(); } }
xxx.dex文件里面就一个sayHello
方法,这里decrypt()
方法未解密,只是表示一下这个逻辑,dex文件是未加密的
1 2 3 4 5 6 7 8 package com.xxx.app;public class Dynamic { public String sayHello () { return "Dynamic is called" ; } }
此时调用Java.enumerateLoadedClassesSync()
,如果不触发事件的话,是不会加载dex文件的,也就搜索不到这个类
而且就算内存中枚举出这个类,也无法使用 Java.use("com.xxx.app.Dynamic")
来获取类,因为frida里面认为两个类是同一个类的依据不仅仅是类名,还有类的加载方式,这里的动态加载类是通过dexClassLoader
来加载的,使用默认的loader无法识别,而且每次调用dexClassLoader
也会被认为是不同的loader(这就导致hook代码有时候需要在触发dex加载操作的之前或者之后再跑,反正多跑几遍,但是呢主动调用没问题,只是hook在这种情况下会比较迷)
使用如下语法,遍历所有的classLoader,每个loader都加载一遍,直到成功加载
1 Java.enumerateClassLoaders();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Java .perform (function ( ) { Java .enumerateClassLoaders ({ onMatch : function (loader ){ try { Java .classFactory .loader = loader; var dynamic = Java .use ("com.xiaojianbang.app.Dynamic" ); console .log ("dynamic: " , dynamic); dynamic.sayHello .implementation = function ( ) { console .log ("hook dynamic.sayHello is run!" ); return "xiaojianbang" ; } }catch (e) { console .log (loader); } }, onComplete : function ( ) { } }); });
DexClassLoader的hook 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var dexClassLoader = Java .use ("dalvik.system.DexClassLoader" );dexClassLoader.loadClass .overload ('java.lang.String' ).implementation = function (className ) { var result = this .loadClass (className); if ("com.xxx.app.Dynamic" === className){ Java .classFactory .loader = this ; var dynamic = Java .use ("com.xxx.app.Dynamic" ); console .log ("dynamic: " , dynamic); dynamic.sayHello .implementation = function ( ) { console .log ("dynamic.sayHello is called" ); return "test" ; } console .log (dynamic.$new().sayHello ()); } return result; }
上面的代码在Hook的时候也会比较迷,有时候会在第一次触发的时候hook到,后面再触发就不能hook到了
让Hook只在指定函数内生效 hook某个函数或者某个类,这个类使用很频繁,如果直接hook,可能导致app崩溃,现在比如MainActivity
里的generateAESKey()
方法调用了new StringBuilder()
,只想hook StringBuilder()
的逻辑只在generateAESKey()
被执行的时候生效,执行其他方法内的StringBuilder()
则不生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Java .perform (function ( ) { function showStacks ( ) { console .log ( Java .use ("android.util.Log" ) .getStackTraceString ( Java .use ("java.lang.Throwable" ).$new() ) ); } var mainActivity = Java .use ("com.xiaojianbang.app.MainActivity" ); var stringBuilder = Java .use ('java.lang.StringBuilder' ); mainActivity.generateAESKey .implementation = function ( ) { console .log ("mainActivity.generateAESKey is called!" ); stringBuilder.toString .implementation = function ( ) { var result = this .toString (); console .log (result); return result; }; var result = this .generateAESKey .apply (this , arguments ); stringBuilder.toString .implementation = null ; return result; }; });
其他命令 命令行 %resume
可以重启app的主进程,用来hook启动过程中的函数
-p 可以指定pid进行注入,因为有时候一个app会有两个进程,这个时候指定包名注入不进去
连接多设备多端口,frida server指定监听端口和地址
1 firdaserver -l 0.0.0.0:9000
连接的时候需要指定IP和端口,这里-H参数和-U参数不能同时存在,因为-U是指定USB设备
1 frida -H <ip>:9000 -f ....
frida的python库使用 包名attach 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import frida, sysjsCode = """ Java.perform(function(){ ... }); """ process = frida.get_usb_device().attach('com.dodonew.online' ) script = process.create_script(jsCode) script.load() print ("开始运行" )sys.stdin.read()
pid attach 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import frida, sysjsCode = """ Java.perform(function(){ ... }); """ process = frida.get_usb_device().attach(9999 ) script = process.create_script(jsCode) script.load() print ("开始运行" )sys.stdin.read()
spawn启动hook 类似于--no-pause
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import frida, sysjsCode = """ Java.perform(function(){ ... }); """ device = frida.get_usb_device() print ("device: " , device)pid = device.spawn(["com.dodonew.online" ]) print ("pid: " , pid)process = device.attach(pid) print ("process: " , process)script = process.create_script(jsCode) script.load() device.resume(pid) print ("开始运行" )sys.stdin.read()
连接非标准端口和多个设备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import frida, sysjsCode = """ Java.perform(function(){ ... }); """ process = frida.get_device_manager().add_remote_device('192.168.3.68:8888' ).attach('com.dodonew.online' ) script = process.create_script(jsCode) script.load() print ("开始运行" )sys.stdin.read()
send 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import frida, sysjsCode = """ Java.perform(function(){ var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil'); RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){ console.log('data: ', a); console.log('desKey: ', b); console.log('desIV: ', c); var retval = this.encodeDesMap(a, b, c); console.log('retval: ', retval); return retval; } var Utils = Java.use('com.dodonew.online.util.Utils'); Utils.md5.implementation = function(a){ console.log('MD5 string: ', a); var retval = this.md5(a); send(retval); return retval; } }); """ def messageFunc (message, data ): print (message) if message["type" ] == 'send' : print (u"[*] {0}" .format (message['payload' ])) else : print (message) process = frida.get_usb_device().attach('com.dodonew.online' ) script = process.create_script(jsCode) script.on('message' , messageFunc) script.load() print ("开始运行" )sys.stdin.read()
recv recv一般用来处理python传递给js的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import frida, sysimport timejsCode = """ Java.perform(function(){ var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil'); RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){ console.log('data: ', a); console.log('desKey: ', b); console.log('desIV: ', c); var retval = this.encodeDesMap(a, b, c); console.log('retval: ', retval); return retval; } var Utils = Java.use('com.dodonew.online.util.Utils'); Utils.md5.implementation = function(a){ console.log('MD5 string: ', a); var retval = this.md5(a); send(retval); // recv.wait()会阻塞js进程,等待python返回的数据 recv(function(obj){ console.log(JSON.stringify(obj)); console.log("Python:", obj.data); retval = obj.data; }).wait(); return retval; } }); """ def messageFunc (message, data ): print (message) if message["type" ] == 'send' : print (u"[*] {0}" .format (message['payload' ])) time.sleep(10 ) script.post({"data" : "0e8315152843b943563031945032e957" }) else : print (message) process = frida.get_usb_device().attach('com.dodonew.online' ) script = process.create_script(jsCode) script.on('message' , messageFunc) script.load() print ("开始运行" )sys.stdin.read()
frida的rpc调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import frida, sysjsCode = """ Java.perform(function(){ var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil'); RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){ console.log('data: ', a); console.log('desKey: ', b); console.log('desIV: ', c); var retval = this.encodeDesMap(a, b, c); console.log('retval: ', retval); return retval; } var Utils = Java.use('com.dodonew.online.util.Utils'); Utils.md5.implementation = function(a){ console.log('MD5 string: ', a); var retval = this.md5(a); console.log('retval: ', retval); return retval; } }); function test(data){ var result = ""; Java.perform(function(){ result = Java.use('com.dodonew.online.util.Utils').md5(data); }); return result; } // 需要在js里先用rpc.exports定义一个类似于接口rpc.exports = {xx:xxx} // 在python里使用script.rpc.xx()去调用对应的方法 // 类似于主动调用,只不过是在python里主动调用 rpc.exports = { rpcfunc: test }; """ device = frida.get_usb_device() print ("device: " , device)pid = device.spawn(["com.dodonew.online" ]) print ("pid: " , pid)process = device.attach(pid) print ("process: " , process)script = process.create_script(jsCode) script.load() device.resume(pid) result = script.exports.rpcFUnc('thisisafunc' ) print (result)print ("开始运行" )sys.stdin.read()