静态方法和实例方法的hook 太喜欢原神辣!所以就拿newstar week2的原神题来操作一下
做例子hook了!
使用frida进行hook的时候,无论是有没有带 static 的方法都是可以进行调用的
那就hook这个whatme函数吧,hook出encode函数的data数据和CUSTOM_TABLE(码表)
这是一个base64加密,码表是经过rc4解密之后的结果,我们可以通过hook直接拿到码表,当然也可以通过修改rc4的返回值修改码表
1 2 3 4 5 6 7 8 9 10 11 12 Java .perform (function ( ) { var base64 =Java .use ("android.util.Base64" ); var whatme=Java .use ("com.genshin.impact.whatme" ); whatme.encode .implementation =function (a,b ){ console .log ("加密的参数是:" ,base64.encodeToString (a,0 )); console .log ("加密的码表是:" ,b); var secret=this .encode (a,b); console .log ("加密之后的结果是:" ,secret); return secret; } });
输入正确的账号,然后密码随便输入,得到的结果
这期间,由于加密之前的a直接输出无法输出,所以就主动调用base64编码把a编码一下就知道了,然后去解密网站解密一下就行
没错,我输入的是123456789,并且也得到了码表:BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqts.uxwzy1032547698/+
小故障 如果在hook的时候遇到这个问题,重启一下手机重新开始就行了
获得参数和修改返回值 然后我在中途修改了参数b,也就是码表,然后我们去修改的函数的加密过程也发生了改变
[MI 9 Transparent Edition::Genshin_impact]->
加密的参数是: MTIzNDU2Nzg5
加密的码表是: BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqts.uxwzy1032547698/+
加密的码表2是: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
加密之后的结果是: MTIzNDU2Nzg5
另外,这个题是一个
判断加密之后的结果是否和”VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8”这字符串是否一样,我灵机一变,加入我把返回值改成那个字符串那对比岂不是永远成立,那岂不是随便登录了???
[MI 9 Transparent Edition::Genshin_impact]-> 加密的参数是: MTIzNDU2Nzg5
加密的码表是: BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqts.uxwzy1032547698/+ 加密的码表2是: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 加密之后的结果是: MTIzNDU2Nzg5 加密之后的结果2是: VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8 成功了,这样的话,错误的密码也可以随便成功登录了!
完整源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Java .perform (function ( ) { var base64 =Java .use ("android.util.Base64" ); var whatme=Java .use ("com.genshin.impact.whatme" ); whatme.encode .implementation =function (a,b ){ console .log ("加密的参数是:" ,base64.encodeToString (a,0 )); console .log ("加密的码表是:" ,b); b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" console .log ("加密的码表2是:" ,b); var secret=this .encode (a,b); console .log ("加密之后的结果是:" ,secret); secret = "VVwPUWsYcXEPW0MpN35gW0FwW1RxandgJTE8" console .log ("加密之后的结果2是:" ,secret); return secret; } });
以下内容由于没有很好的示例apk(我自己也不会写)所以就用xiaojianbang老师的Hookdemo来学习了使用了,xiaojianbang老师太强啦!
首先分析一下,点击test之后会干啥
当点击 ID 为 Test
的按钮时,执行以下操作。
调用 logOutPut
方法,将当前线程的 ID 打印输出。
创建一个名为 money
的新对象,该对象是 Money
类的实例,构造函数接受两个参数:货币类型为 “人民币”,金额为 100。
创建一个名为 wallet
的新对象,该对象是 Wallet
类的实例,构造函数接受三个参数:持有人姓名为 “xiaojianbang”,品牌为 “lv”,初始金额为 100。
创建一个名为 bankCard
的新对象,该对象是 BankCard
类的实例,构造函数接受五个参数:持卡人姓名为 “xiaojianbang”,卡号为 “123456789”,发卡行为 “CBDA”,卡类型为 1,手机号为 “15900000000”。
将 bankCard
对象赋值给 this.bankCard
。
调用 wallet
对象的 addBankCard
方法,将 bankCard
添加到钱包中。
调用 wallet
对象的 deposit
方法,将 money
存入钱包。
综上所述,代码的功能是创建一个钱包对象,添加银行卡并存入一定金额的人民币。
差不多就是这样,然后现在就对下面进行hook
构造函数hook
在Money函数中有Money方法,这就叫构造函数,在对构造函数进行Hook的时候,要加上$init进行调用 所以就hook一下Money这个构造函数:
1 2 3 4 5 6 7 8 Java .perform (function ( ) { var money = Java .use ("com.xiaojianbang.hook.Money" ); money.$init .implementation = function (a, b ) { console .log ("money.$init param: " , a, b); return this .$init("美元" , 200 ); } });
得到了相应的结果,但是好像返回值有点问题
对于String的构造函数有点不同,是调用StringFactory函数
对象参数的构造与修改
amount是一个属性,我们可以直接通过调用来对他进行赋值
语句是:a.amount.value = 6666;】
再hook就会改变那里面的赋值
重载函数的hook 重载函数是指多次调用同一个函数,但是传入的参数的数量不同
需要overload函数进行传递指令类型
解决方法:可以根据报错,进行重载函数的调用
1 2 3 4 5 var utils = Java .use ("com.xiaojianbang.hook.Utils" );utils.getCalc .overload ('int' , 'int' ).implementation = function (a, b ) { console .log ("utils.getCalc param: " , a, b); return this .getCalc (a, b); }
补上一个模板(其实本质上就加了个overload进行区分罢了
主动调用 直接hook是不区分静态方法还是实例方法的,但是主动调用是区分的
静态方法 直接调用就行了
抓到类之后就直接当成本地函数就行了
假设调用一下getFlag,这里就设计一下首先是通过hook去修改一下getFlag的返回值为”WenWenJiang6666”,然后在通过主动调用去调用这个函数,去看看他的返回值是不是我自己定义的
代码:
1 2 3 4 5 6 7 8 9 Java .perform (function ( ) { var money = Java .use ("com.xiaojianbang.hook.Money" ); money.getFlag .implementation =function ( ){ var mm = "WenWenJiang6666" ; return mm; } var mmm = money.getFlag () console .log ("参数是:" ,mmm) });
成功,静态方法还是很简单的!
实例方法 Java.choose() 是frida提供的一个去内存中寻找某一对象属性的参数,并且可以进行打印或是对调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java .perform (function ( ) { var money = Java .use ("com.xiaojianbang.hook.Money" ); money.getFlag .implementation =function ( ){ var mm = "WenWenJiang6666" ; return mm; } Java .choose ("com.xiaojianbang.hook.Money" , { onMatch : function (sec ){ console .log (sec.amount .value ); console .log (sec.currency .value ); }, onComplete : function ( ){ console .log ("内存中的Money对象搜索完毕" ); } }); });
当然也可以自己创建对象,自己去调用,但是没啥意义
枚举已加载的所有java类 Java.enumerateLoadedClassesSync()
console.log(Java.enumerateLoadedClassesSync().join("\n"));
可以枚举出所有的已加载的类 1.枚举的类是已经加载的类 2.出现的类也有可能Hook不到,因为有类加载器进行加载,很有可能
枚举类的所有方法 通过反射来获得类以及类里的所有字段
1 2 3 4 5 6 7 8 Java .perform (function ( ) { var wallet = Java .use ("com.xiaojianbang.hook.Wallet" ); var methods = wallet.class .getDeclaredMethod (); var constructors = wallet.class .getDeclaredCinstructors (); var fields = wallet.class .getDeclaredFields (); var classes = wallet.class .getDeclaredClasses (); });
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 console .log (Java .enumerateLoadedClassesSync ().join ("\n" ));var wallet = Java .use ("com.xiaojianbang.hook.Wallet" );var methods = wallet.class .getDeclaredMethods ();var constructors = wallet.class .getDeclaredConstructors ();var fields = wallet.class .getDeclaredFields ();var classes = wallet.class .getDeclaredClasses ();for (var i = 0 ; i < methods.length ; i++) { console .log (methods[i].getName ()); } console .log ("============================" );for (var i = 0 ; i < constructors.length ; i++) { console .log (constructors[i].getName ()); } console .log ("============================" );for (var i = 0 ; i < fields.length ; i++) { console .log (fields[i].getName ()); } console .log ("============================" );for (var i = 0 ; i < classes.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 ()); } }
上面的方法可能太过于麻烦,所以可以直接去遍历每一个方法:
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 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 + " 被调用!参数为:" , params); return this [methodName].apply (this , arguments ); } } } var utils = Java .use ("com.xiaojianbang.hook.Utils" ); var methods = utils.class .getDeclaredMethods (); for (var i = 0 ; i < methods.length ; i++) { var methodName = methods[i].getName (); hookFunc (methodName); } });
这段代码在com.xiaojianbang.hook
包下的Utils
类上执行方法Hook。它定义了一个hookFunc
函数,该函数将每个方法的实现替换为一个自定义实现,该实现会打印方法名和参数。代码获取了Utils
类的引用,然后检索所有已声明的方法,并使用hookFunc
函数对每个方法进行Hook。
强的强的。算是一个全自动hook吧,挺好玩的,试试别的!
试了试之前的CTF题目,果然可以差不多起到一个一键分析的功能,还挺好用的嘞! (但是跟这个题目关系不大,而且这样hook也是起到了一个帮助分析的作用!)
调用自己写的函数 Java.registerClass 可以在原有的android代码的基础上进行自定义创建函数,然后自己去进行主动调用 但是需要写的是js代码 理论可行,但是没有具体的试验过,可以看一下官方文档:frida的官方JavaScriptAPI
https://frida.re/docs/javascript-api/ 其实有一个更好的方法是dex注入
dex注入 使用registerClass的方式自己去写执行代码的过程实在是比较麻烦一些,可以用android studio自己随便写一个apk的程序,然后新建一个类,写上自己需要的代码,在打包成apk之后。再通过hook去主动调用就行了。
(比自己写js代码方便多了)
以上两种方法都是可以通过一些方式来调用自己写的函数来方便分析,目前觉得没太大的卵用,以后需要用的时候再补上一个自己写的demo
Java.cast转型 之前在我去主动调用原神附件的有一些参数的时候,提示了我输出object,不能直接输出出来从而看不到数值,所以今天就可以拿Java.cast来进行转化从而得到我们想要的数值 先看一下原版的(使用的是枚举类的所有方法的函数)
whatme.encode的方法被调用了,但是参数不知道,所以就要用Java.cast转一下
终于是没有object了
但是怎么什么也没有了????
出了,参数出来了。没问题 问题应该是在于刚刚在计算完之后又定义了一次params
总之还是得到了想要的base64码表。太牛了,妈妈再也不用担心我hook出参数是[object]l了
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 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 = "" ; if (methodName.indexOf ("encode " )!= -1 ){ params=Java .cast (arguments [0 ],Java .use ("java.util.HashMap" )); }else { for (var k = 0 ; k < arguments .length ; k++) { params += arguments [k] + " " ; } } console .log ("whatme." + methodName + " 被调用!参数为:" , params); return this [methodName].apply (this , arguments ); } } } var utils = Java .use ("com.genshin.impact.whatme" ); var methods = utils.class .getDeclaredMethods (); for (var i = 0 ; i < methods.length ; i++) { var methodName = methods[i].getName (); hookFunc (methodName); } });
类加载器 同样的类,用不同的类加载器去加载,得到的实际上是两个类 在我们hook的时候经常会遇到 not find xxx class的报错,这个情况大概有可能是类加载器的问题 由于软件会使用各种的classloader,所以就导致明明这个类我们能在反编译的时候看到,并且在使用枚举已加载的所有类的时候也可以明确看到我们需要加载的类:
1 2 Java .enumerateLoadedClassesSync ()console .log (Java .enumerateLoadedClassesSync ().join ("\n" ));
但是去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 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 "hahahhahahaha" ; } } catch (e) { console .log (loader); } }, onComplete : function ( ) {} });
看到了不仅加载了全部的类加载器而且通过try catch的方法去寻找各个加载器加载的方法然后再进行hook调用和修改返回值的操作,一气呵成!
动态加载:
就是在需要的时候才会主动去加载那个类,这也是 类似于一种加密或者混淆,可以通过主动调用来进行加载,但是我到现在还没遇到过类似的apk,有空的话找一个玩玩
总结 以上就是一些常见的frida_api,在进行hook的时候可以根据上面的一些方法和代码进行调用