API 现在介绍的只是Java层的API
注意frida代码需要写在 Java.perform(function() {});
函数体中,下面省略不写了
静态方法和实例方法的hook
1 2 3 4 5 6 7 8 9 10 11 12 var xxx = Java .use ("com.xxxx.xxx" )xxx.(方法).implementation = function ( ) { var result = this .(方法) console .log ("xxx :" , result) return result; }
函数参数和返回值的修改 1 2 3 4 5 6 7 8 9 10 11 12 13 var money = Java .use ("com.hello.demo.money" )var str = Java .use ("java.lang.String" )money.getInfo .implementation = function (a ) { var result = this .getInfo ("123" ); console .log (result); return str.$new("hello world" ); }
构造方法的hook 1 2 3 4 5 6 7 8 9 10 11 12 var money = Java .use ("com.hello.demo.money" )money.$init .implementation = function ( ) { var result = this .$init ; return result; } var str = Java .use ("java.lang.StringFactory" )str.newStringFromString .implementation = function (a ) { }
对象参数的构造和修改 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 var wallet = Java .use ("" );var money = Java .use ("" );wallet.(方法).implementation = function (a ) { console .log (a); return this .方法(money.$new("你好" , 200 )) } var wallet = Java .use ("" );wallet.(方法).implementation = function (a ) { console .log (a.currency .value ); console .log (a.getCurrency ); a.amount .value = 3000 ; a.setAmount (3000 ); return this .方法(a); }
HashMap的打印 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 var utils = Java .use ("" );var stringBuilder = Java .use ("java.lang.StringBuilder" );utils.方法.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 (result.toString ()); var result = this .shufferMap (a); 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 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 z var str = Java .use ("java.lang.String" )str.getBytes .overload ().implementation = function ( ) { console .log ("getBytes() called" ); return "hello world" .getBytes (); }; }); var utils = Java .use ("com.xxx.xxx" );var overloadsArr = utils.方法.overloads ;for (var i = 0 ; i < overloadsArr.length ; i++) { overloadsArr[i].implementation = function ( ) { var params = "" ; for (var j = 0 ; j < arguments .length ; j++) { params += arguments [j] + "" ; } console .log (params); console .log (this ); return this .方法.apply (this , arguments ); } }
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 26 27 28 29 var money = Java .use ("com.xxx.xxxx" );console .log (money.方法())console .log (money.$new .方法())Java .choose ("com.xxx.xxxx" , { onMatch : function (obj ) { console .log (obj.方法()); }, onComplete : function ( ) { console .log ("END" ) } }); var current_application = Java .use ("android.app.ActivityThread" ).cuttrntApplication ();var content = current_application.getApplicationContext ();
获取和修改类的字段 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 var money = Java .use ("com.xxx.xxxx" );console .log (money.变量名.value );money.变量名.value = "hello world" ; console .log (money.变量名.value );var moneyObj = money.$new();console .log (moneyObj.变量名.value )money.变量名.value = "hello world" ; console .log (money.变量名.value );Java .choose ("com.xxx.xxxx" , { onMatch : function (obj ) { console .log (obj.变量名.value ); }, onComplete : function ( ) { } }); Java .choose ("com.xxx.xxxx" , { onMatch : function (obj ) { console .log (obj._accountName .value ); }, onComplete : function ( ) { } });
hook内部类和匿名类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 内部类简单来说就是class中的class 比如调用R文件中的内部类 public class tset { class a { public static int b = 1 ; } } com.xxx.xxx.R$id public static void main (String[] args) { System.out.println(new people ("张三" , 19 ) { @Override public String getInfo () { return getName() + "" + getAge(); } }.getInfo()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Java .choose ("com.xxx.xxxx$a" , { onMatch : function (obj ) { console .log (obj._accountName .value ); }, onComplete : function ( ) { } }); var people$1 = Java .use ("com.xxx.people$1); people$1.getInfo.implementation = function() { var result = this.getInfo(); return result; }; }); // 如果有匿名内部类就使用Java.choose进行嵌套即可,如果太过于复杂,可以直接遍历所有的类,直接去搜索
混淆函数的hook方法 1 2 3 4 5 6 7 8 9 10 11 12 13 var people = Java .use ("com.xxx.xxxx" );var base64 = Java .use ("android.util.Base64" );var str = Java .use ("java.lang.String" );var funcName = str.$new(base64.decode ("Z2V0SW5mbw==" , 0 ));console .log (funcName);people[funcName].implementation = function ( ) { var result = this .funcName (); console .log (result); return result; }
做一个小小的混淆,就是将类名、方法名进行base64加密
如果要更改dex中的类名,需要用到一个库dexlib2
输出结果:
混淆前后对比
面对混淆的名字,jadx可能会处理一部分,如果出现注释和名字不一样的,以注释为准进行hook
混淆代码
因为是使用Android开发的,所有目录在安卓端,运行比较慢,也可以使用Java写
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { File oldDexFile = new File ("/data/data/com.example.demo/classes.dex" ); File newDexFile = new File ("/data/data/com.example.demo/new_classes.dex" ); Log.d("demo" , "从assets中读取classes.dex" ); InputStream ins = getApplicationContext().getAssets().open("classes.dex" ); ByteArrayOutputStream baos = new ByteArrayOutputStream (); byte [] bytes = new byte [1024 ]; int index; while ((index = ins.read(bytes)) != -1 ) baos.write(bytes, 0 ,index); Log.d("demo" , "开始写出classes.dex到" + oldDexFile.toString() + "..." ); FileOutputStream fos = new FileOutputStream (oldDexFile); fos.write(baos.toByteArray()); ins.close(); fos.close(); baos.close(); Log.d("demo" , "开始使用dexlib2加载classes.dex..." ); Opcodes opcodes = Opcodes.forApi(29 ); DexFileFactory.writeDexFile(oldDexFile.toString(), DexFileFactory.loadDexFile(oldDexFile, opcodes)); DexBackedDexFile dexBackedDexFile = DexFileFactory.loadDexFile(oldDexFile, opcodes); Log.d("demo" , "dexFile:" + dexBackedDexFile.getClass().getName()); DexRewriter rewriter = new DexRewriter (new RewriterModule () { @Override public Rewriter<ClassDef> getClassDefRewriter (Rewriters rewriters) { return new ClassDefRewriter (rewriters) { @Override public ClassDef rewrite (ClassDef classDef) { String className = classDef.getType(); if (className.contains("com/dodonew" )) { return new RewrittenClassDef (classDef) { @Override public String getType () { String class_fullName = classDef.getType(); int start = class_fullName.lastIndexOf("/" ); int end = class_fullName.lastIndexOf("$" ); int length = class_fullName.length(); if (end < 0 ) { if (class_fullName.contains(";" )) { end = length - 1 ; } else { end = length; } } String class_name = class_fullName.substring(start + 1 , end); String class_name_enc = Base64.encodeToString(class_name.getBytes(), 0 ); Log.d("demo" , class_name + "" + class_name_enc); return class_fullName.replace(class_name, class_name_enc); } @Override public String getSourceFile () {return null ;} }; } return super .rewrite(classDef); } }; } @Override public Rewriter<Field> getFieldRewriter (Rewriters rewriters) { return new FieldRewriter (rewriters) { @Override public Field rewrite (Field field) { String className = field.getDefiningClass(); if (className.contains("com/dodonew" )) { return new RewrittenField (field) { @Override public String getName () { return Base64.encodeToString(field.getName().getBytes(), 0 ); } }; } return super .rewrite(field); } }; } @Override public Rewriter<Method> getMethodRewriter (Rewriters rewriters) { return new MethodRewriter (rewriters) { @Override public Method rewrite (Method value) { String className = value.getDefiningClass(); if (className.contains("com/dodonew" )) { return new RewrittenMethod (value) { @Override public String getName () { String methodName = Base64.encodeToString(value.getName().getBytes(), 0 ); Log.d("demo" , "methodName:" + methodName); return methodName; } }; } return super .rewrite(value); } }; } }); DexFile rewriteDexFile = new DexFileRewriter (rewriter).rewrite(dexBackedDexFile); DexPool dexPool = new DexPool (rewriteDexFile.getOpcodes()); for (ClassDef classDef : rewriteDexFile.getClasses()) { dexPool.internClass(classDef); } dexPool.writeTo(new FileDataStore (newDexFile)); Log.d("demo" , "new_classes.dex生成完毕,保存路径为:" + newDexFile.toString()); } catch (IOException e) { e.printStackTrace(); } } }
枚举所有已加载的类与枚举类的所有方法 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 console .log (Java .enumerateLoadedClassesSync ().join ("\n" ))var people = Java .use ("com.xxx.xxxx.People" );var methods = people.class .getDeclaredMethods ();var constructors = people.class .getDeclaredConstructors ();var fields = people.class .getDeclaredFields ();var classes = people.class .getDeclaredClasses ();for (let i = 0 ; i < methods.length ; i++) { console .log (methods[i].getName ()); } console .log ("============================" )for (let i = 0 ; i < fields.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 < fields.length ; i++) { console .log (classes[i].getName ()) var Wallet $InnerStructure = classes[i].getDeclaredFields (); for (var j = 0 ; j < Wallet $InnerStructure.length ; j++) { console .log (Wallet $InnerStructure[j].getName ()); } }
hook类的所有方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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.dodonew.online.DodonewOnlineApplication" );var methods = utils.class .getDeclaredMethods ();console .log ("1" )for (var i = 0 ; i < methods.length ; i++) { var methodName = methods[i].getName (); hookFunc (methodName); }
注入dex Java.registerClass 这个是使用JS语言写的,frida注入之后可能会出现bug
这个必须需要实现一个接口,这个还是比较麻烦的,接口内的东西还需要都实现
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 Java .perform (function ( ) { const MyWeirdTrustManager = Java .registerClass ({ name : "com.dodonew.online.MyWeirdTrustManager" , implements : [Java .use ("接口名" )], superClass : 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 called with " + str + " and " + num) } }], test2 (str, num ) { console .log ("test2 called with " + str + " and " + num) return null ; }, } }); var myObj = MyWeirdTrustManager .$new(); myObj.test1 (); myObj.test1 ("Hello" , 123 ); myObj.test2 ("World" , 456 ); myObj.limit .value = 100 ; console .log (myObj.limit .value ); });
frida注入dex 这个操作是自己写好一个dex文件,将其注入到app中去然后再调用其中的方法
先使用studio打包一个自己随便写的apk,然后取出classes.dex文件,将这个文件放到手机的 /data/local/tmp 路径下
在进行注入,这样的方法是写成Java类型的,容易纠错和编写
1 2 3 4 Java .openClassFile ("/data/local/tmp/patch.dex" ).load (); var use = Java .use ("com.example.demo.test" ); var obj = use.test (); console .log (obj);
通过这种方式就可以在hook其他app的时候主动调用自己写好的方法
hook枚举类 回忆一下枚举
hook枚举类没什么价值,可以直接看的
1 2 3 4 5 6 7 8 Java .choose ("com.xiaojianbang.app.Season" , { onMatch : function (obj ){ console .log (obj.ordinal ()); }, onComplete : function ( ) { } }) console .log (Java .use ("com.xiaojianbang.app.Season" ).values ())
firda写文件 frida是有API支持文件写入的
1 2 3 4 5 6 7 8 9 10 var ios = new File ("/data/data/com.xiaojianbang.app/test.txt" , 'w' ) ios.write ("Hello, world!\n" ) ios.flush () ios.close () 最好将文件路径写在当前包名下,否则容易没有写入权限 w 覆盖 a+ 追加 注意:frida只有写文件的API ,没有读文件API ,如果要读文件,要去hook libc.so 中的读文件的函数
转型 Java.cast 进行向上或者向下转型,应对那种不能 toString() 的情况
之前也使用过这个东西,注意使用 cast 转型要是本来就是这个类型的才能转型
例如这个参数传进来的时候就进行了一个向上转型,在hook代码中向下转型是没什么问题的
1 2 3 4 5 6 7 var utils = Java.use("com.xiaojianbang.hook.Utils"); 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); }
数组的构建 这样也算是创建了一个数组
frida对于创建数组是专门提供了API的,array就是将JS数组转化为Java数组传到方法中
1 2 3 4 5 6 7 var Utils = Java .use ("com.xiaojianbang.hook.Utils" );var strarr = Java .array ("java.lang.String" , ["Hello" , "world" , "!" ])var obj = Utils .myPrint (strarr)console .log (obj)
object数组的构建
1 2 3 4 5 6 7 8 9 可以看到传入的东西花里胡哨,有字符串,基本数据类型和对象 这个时候就麻爪了,原因就是在Java 中万物皆对象,基本数据类型put到数组时是会转换成包装类,而js中没有这个操作,所以就报错了 其实也是可以直接传的 给他手动转换成包装类不就是了 var money = Java .use ("com.xiaojianbang.hook.Money" ).$new("demoMoney" , 2000 ); var bool = Java .use ("java.lang.Boolean" ); var integer = Java .use ("java.lang.Integer" ); var obj = Utils .myPrint (["demo" , integer.$new(100 ), bool.$new(true ), money])
只需要记住 Java.array 可以创建一个Java数组即可
ArrayList主动调用 list是数组数据,map是键值对数据
原理和object一样的,都是基本数据类型要转换成包装类
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 Utils = Java .use ("com.xiaojianbang.hook.Utils" ); var money = Java .use ("com.xiaojianbang.hook.Money" ).$new("demoMoney" , 2000 ); var bool = Java .use ("java.lang.Boolean" ); var integer = Java .use ("java.lang.Integer" ); var obj = Utils .myPrint (["demo" , integer.$new(100 ), bool.$new(true ), money]) console .log (obj) console .log ("==============================" ) var arraylist = Java .use ("java.util.ArrayList" ).$new(); arraylist.add ("demo" ); arraylist.add (integer.$new(100 )); arraylist.add (bool.$new(true )); arraylist.add (money); var obj1 = Utils .myPrint (arraylist) console .log (obj1) console .log ("==============================" )
hook动态加载的dex 这个和枚举所有已加载的类长得很相似 Java.enumerateLoadedClassesSync()
Java.enumerateClassLoaders()
这个是动态加载类
提出问题 报错提示没有找到 com.xiaojianbang.app.Dynamic 这个类
但是可以看到 com.xiaojianbang.app.Dynamic 这个类是已经加载了的,这个就和类加载器有关了,回旋镖转回来了,因为类加载器不同,所以没有找到这个类
解决方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 sayHello called" ); return "demo" } } catch (error) { console .log (loader); } }, onComplete : function ( ) {} });
动态加载:也可以理解成按需加载,就是在代码执行过程中加载dex文件,就比如 dynamic类,它不是classes.dex文件中的类,是apk执行过程中动态加载的一个类。hook动态加载类的时候就容易碰到,枚举类中有这个方法,但是hook说找不到这个方法。如果这个dex文件加密了,jadx也不会反编译出来这个类。
Java.enumerateClassLoaders()这个API要安卓7以上才能使用
让hook只在指定函数内生效(了解) 如果hook系统函数,调用很频繁的话是hook不成功的,直接崩溃了。
但是如果已经确定了范围,就可以用这种方式,其实也可以理解为hook嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 var mainActivity = Java .use ("com.xiaojianbang.app.MainActivity" );var stringBuilder = Java .use ("java.lang.StringBuilder" )mainActivity.generateAESKey .implementation = function ( ) { console .log ("mainActivity generateAESKey called" ); stringBuilder.toString .implementation = function ( ) { var result = this .toString (); console .log ("generateAESKey result: " + result); return result; }; var result = this .generateAESKey .apply (this , arguments ); stringBuilder.toString .implementation = null ; return result; };
hook定位接口\抽象类的实现类 接口 如果定位代码找到接口了,单纯的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 26 27 28 var classes = Java .enumerateLoadedClassesSync (); for (const index in classes) { let className = classes[index]; if (className.indexOf ("com.xiaojianbang" ) === -1 ) continue ; try { let clazz = Java .use (className); let resultArr = clazz.class .getInterfaces (); console .log ("resultArr: " , resultArr); if (resultArr.length === 0 ) continue ; for (let i = 0 ; i < resultArr.length ; i++) { if (resultArr[i].toString ().indexOf ("com.xiaojianbang.app.TestRegisterClass" ) !== -1 ) { console .log (className, resultArr); } } } catch (error) { console .log ("Didn't find class: " + className) } }
出了一点小bug,但是问题不大,还是得到了接口的实现类,对于不在同一个dex中的直接 try-catch 处理算了
抽象类 和接口的方法极其相似,但是牢弟你要知道接口可以实现多个,但是抽象类只能实现一个,因此 getSuperclass() 获取到的不是一个数组,所以不需要循环了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var classes = Java .enumerateLoadedClassesSync (); for (const index in classes) { let className = classes[index]; if (className.indexOf ("com.xiaojianbang" ) === -1 ) continue ; try { let clazz = Java .use (className); let resultArr = clazz.class .getSuperclass (); console .log ("resultArr: " , className, resultArr); if (resultArr == null ) continue ; if (resultArr[i].toString ().indexOf ("com.xiaojianbang.app.TestRegisterClass" ) !== -1 ) { console .log (className, resultArr); } } catch (error) { } }
END