关键代码快速定位 1 2 3 4 5 搜索不太靠谱,需要更精准更通用的方式来定位关键代码 Java:hook系统函数、沙盒 C:hook系统函数 通过hook一些底层的方法来定位
HashMap的应用场景一般是在提交参数的时候使用
ArrayList的应用场景是需要对数据进行排序的时候
hook系统函数 一、HashMap.put 1 2 3 4 5 6 7 8 Java .perform (function ( ) { var HashMap = Java .use ('java.util.HashMap' ); HashMap .put .implementation = function (a, b ) { console .log ("put: " , a, b); return this .put (a, b); } });
只要代码中使用了HashMap.put添加数据,就把数据打印出来
包有关键信息的湿鸽
打印一下函数栈,看看是哪个函数调用的HashMap.put
利用 logOutPut(Log.getStackTraceString(new Throwable()));
打印函数栈
注意:因为有很多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 Java .perform (function ( ) { function showStacks ( ) { console .log ( Java .use ("android.util.Log" ) .getStackTraceString ( Java .use ("java.lang.Throwable" ).$new() ) ); } var HashMap = Java .use ('java.util.HashMap' ); HashMap .put .implementation = function (a, b ) { if (a.equals ("username" )) { showStacks (); console .log ("hashMap.put :" , a, b); } console .log ("put: " , a, b); return this .put (a, b); } });
包有内容的,可以看到堆栈信息,有login方法和onClick方法,通过这样的方式来快速定位代码。被hook的函数带有 (Native Method)
的标志
除了HashMap之外,LinkedHashMap中也有put方法,也是有可能有内容的
java.util.concurrent.ConcurrentHashMap中有有put方法
二、ArrayList.add 1 2 3 4 5 6 7 8 9 10 11 12 13 Java .perform (function ( ) { var addlist = Java .use ("java.util.ArrayList" ); addlist.add .overload ("java.lang.Object" ).implementation = function (obj ) { console .log ("add: " , obj); return this .add (obj); } addlist.add .overload ("int" , "java.lang.Object" ).implementation = function (a, obj ) { console .log ("add a + obj: " , a, obj); return this .add (a, obj); } });
这个输出的内容多点多,在命令行中不太好看,输出成文件格式
1 fride -UF -l 1.js -o log.txt
如果失败了可能不是脚本的问题重启一下app试试,还是不行就换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 Java .perform (function ( ) { function showStacks ( ) { console .log ( Java .use ("android.util.Log" ) .getStackTraceString ( Java .use ("java.lang.Throwable" ).$new() ) ); } var addlist = Java .use ("java.util.ArrayList" ); var addlist = Java .use ("java.util.ArrayList" ); addlist.add .overload ("java.lang.Object" ).implementation = function (obj ) { console .log ("add: " , obj); if (obj.equals ("username=13112345678" )) { showStacks (); console .log ("addlist.add :" , obj); } return this .add (obj); } });
一注入运行这个代码模拟器直接就是崩溃了,没有成功啊,但是思路就是这样的,打印出来的堆栈也是从调用add开始一直到login方法,在往上就是Android包中的了
除了add方法之外,还可能用到addAll、set方法
其他函数中也有add方法 Vector、LinkedList
三、TextUtils的isEmpty方法 这个方法是判断是否为空的方法,一般有输入的地方很有可能使用了这个方法来进行非空判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Java .perform (function ( ) { function showStacks ( ) { console .log ( Java .use ("android.util.Log" ) .getStackTraceString ( Java .use ("java.lang.Throwable" ).$new() ) ); } var textUtils = Java .use ("android.text.TextUtils" ); textUtils.isEmpty .implementation = function (str ) { console .log ("TextUtils.isEmpty: " , str); return this .isEmpty (str); } });
这里输入的账号密码是会调用isEmpty的,这个时候再判断一下打印出堆栈即可
不打印电话号了,没新意,打印下面的加密数据看看是在哪里调用的
跟着方法名找过去,找到了解密返回数据的方法,说明这串加密数据,就是服务端返回的数据结果
四、Log 一般发布的apk是不会有Log打印信息的,但是也不排除有的忘删掉了,或者开发不规范。不推荐一开始就使用。
1 2 3 4 5 6 var log = Java .use ("android.util.Log" );log.w .overload ("java.lang.String" , "java.lang.String" ).implementation = function (tag, msg ) { showStacks (); console .log ("Log.w: " , tag, msg); return this .w (tag, msg); }
五、Collections的sort方法 这是一个排序方法,可能会用到,直接hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var collection = Java .use ("java.util.Collections" ); collection.sort .overload ("java.util.List" ).implementation = function (list ) { try { showStacks (); var a = Java .cast (list, Java .use ("java.util.ArrayList" )); console .log ("Collections.sort: " , a.toString ()); } catch (error) { } return this .sort (list); }
六、JSONObject的put、getString方法等 这个是Java中的一个处理json数据的方法,但是现在用的比较少了,会用到外面的库的json数据处理方法。因为嘟嘟牛的抓包结果是想服务端发送了json数据的,所以就可以hook处理json的方法
put是将数据转换成json形式,getString是将json格式的数据还原
1 2 3 4 5 6 7 8 9 10 11 12 13 var jSONObject = Java .use ("org.json.JSONObject" ); jSONObject.put .overload ("java.lang.String" , "java.lang.Object" ).implementation = function (a, b ) { showStacks (); console .log ("JSONObject.put: " , a, b); return this .put (a, b); } jSONObject.getString .implementation = function (a ) { showStacks (); console .log ("JSONObject.getString: " , a); var result = this .getString (a); console .log ("result: " , result); return result; }
七、Toast的show方法 1 2 3 4 5 6 var toast = Java ,use ("android.widget.Toast" );toast.show .implementation = function ( ) { showStacks (); console .log ("Toast.show :" ); return this .show () }
app登录失败是有提示的,就可以试试hook Toast
八、Base64 1 2 3 4 5 6 7 8 var base64 = Java .use ("android.util.Base64" ); base64.encodeToString .overload ("[B" , "int" ).implementation = function (a, b ) { showStacks (); console .log ("Base64.encodeToString: " , a, b); var result = this .encodeToString (a, b); console .log ("result: " , result); return result; }
内容包括字节数据,和加密后的数据,一般为了保存字节数据会进行base64编码
九、String的getBytes、isEmpty方法 既然保存的是字节数据,那么就有可能使用了系统函数的转化字节的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var str = Java .use ("java.lang.String" ); str.getBytes .overload ("java.lang.String" ).implementation = function (a ) { var result = this .getBytes (a); var newStr = str.$new(result); console .log ("getBytes: " , newStr); return result; } str.getBytes .overload ().implementation = function ( ) { var result = this .getBytes (); var newStr = str.$new(result); console .log ("getBytes: " , newStr); return result; }
可以看到这些数据和协议还有密文有很大关系,加上数据也不多,直接打印出堆栈,都不需要过滤
十、String的构造函数 1 2 3 4 5 6 7 8 9 var str = Java .use ("java.lang.String" );str.$init .overload ('[B' ).implementation = function (a ) { var result = this .$init(a); console .log ("str $init:" ) return result; }
java/lang/string.java
可以看到string的任何一种构造方法都是抛出了一个错误,提示使用 StringFactory
搜索StringFactory找到StringFactory.java
在Java中使用的 new String() 方法其实在经过虚拟机的编译之后转换成了 StringFactory 中的方法,返回一个字符串,至于虚拟机怎么做到的,那就要看虚拟机的源码了,这个太深了,知道即可,所以要hook String的构造方法就需要hook StringFactory
String函数的构造替换成了StringFactory,所以应该去hook StringFactory中的方法
newStringFactoryString、newStringFactoryChars、newStringFactoryBytes、newStringFactoryBuffer、newStringFactoryBuilder
这是几个定义的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var stringFactory = Java .use ("java.lang.StringFactory" ) stringFactory.newStringFromString .implementation = function (a ) { showStacks (); var result = this .newStringFromString (a); console .log ("StringFactory.newStringFromString: " , result); return result; } stringFactory.newStringFromChars .overload ('[C' ).implementation = function (a ) { showStacks (); var result = this .newStringFromChars (a); console .log ("StringFactory.newStringFromChars: " , result); return result; }
十一、StringBuider、StringBuffer的hook 奖池还在累积
上面介绍了String转换的方法,String定义的方法,StringBuider就是String拼接的方法 +
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var sb = Java.use("java.lang.StringBuilder"); sb.toString.implementation = function() { var result = this.toString(); if (result.indexOf("Encrypt") != -1) { showStacks(); } console.log("StringBuilder.toString: ", result); return result; } var sb2 = Java.use("java.lang.StringBuffer"); sb2.toString.implementation = function() { var result = this.toString(); if (result.indexOf("username") != -1) { showStacks(); } console.log("StringBuffer.toString: ", result); return result; }
十二、findViewByld找控件id(打印R$id属性) 找到findViewByld的位置,但是运行会报错
有时候在源码中看到的名字不一定是正确的,需要检验一下
比如 android.appcompat.app 包中的 AppCompatActivity 。
枚举所有已经加载的类
1 2 3 4 5 6 7 var classes = Java .enumerateLoadedClassesSync (); for (var i = 0 ; i < classes.length ; i++) { var className = classes[i]; console .log ("className: " + className); }
AppCompatActivity 类只有一个,和看到的包名不一样,以frida的类为准,这个时候再去反 AppCompatActivity 类
你别说fittencode挺通人性,直接把正确的提示出来了
但是又有问题了,如何找到控件id呢——去hook R文件
而且,id是R.java的内部类,不同通过 .
来指向要使用 R$id
需要使用
1 2 3 var btn_login_id = Java .use ("com.dodonew.online.R$id" ).btn_login .value ;
1 2 3 4 5 6 7 8 9 10 11 var btn_login_id = Java .use ("com.dodonew.online.R$id" ).btn_login .value ; console .log ("btn_login_id: " + btn_login_id); var appCompatActivity = Java .use ("android.support.v7.app.AppCompatActivity" ); appCompatActivity.findViewById .implementation = function (a ) { if (a == btn_login_id) { showStacks (); console .log ("AppCompatActivity.findViewByle: " , a); } return this .findViewByle (a); }
这个时候发现点击是没有反应的,原因就是界面已经创建好了,代码已经执行过了,还找什么控件啊,不执行这个方法了。这是一个生命周期的问题,想要有效果就需要换一个hook模式
现在需要在界面创建之前进行注入操作
1 2 frida -U -f com.dodonew.online -l 1.js frida -U -f com.dodonew.online -l 1.js --no-pause
我勒个一波三折,学到不少
十三、setOnClickstener hook这个函数,对比控件id,打印函数栈
1 2 3 4 5 6 7 8 9 10 11 12 var btn_login_id = Java .use ("com.dodonew.online.R$id" ).btn_login .value ; console .log ("btn_login_id: " + btn_login_id); var view = Java .use ("android.view.View" ); view.setOnClickListener .implementation = function (a ) { if (this .getId () == btn_login_id) { showStacks (); console .log ("View.setOnClickListener: " , this .getId ()); } return this .setOnClickListener (a); }
和刚刚的是一个操作也需要加上 -f
上面这两个方法不推荐有限使用,因为距离加密的方法太远了,而且还需要分析出来控件id进行过滤,还是比较麻烦的。
可hook点老多了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 加密库相关的hook SSL相关的hook socket相关的hook SocketOutputStream SocketInputStream 读写文件相关的 java.io.File 证书双向验证 Keystore.load 通常有证书和密码 安卓退出进程的方式 // 快速定位协议头加密 okhttp3的addHeader 方法 var okhttp_Builder = Java.use("okhttp3.Request$Builder"); okhttp_Builder.addHeader.implementation = function(a, b) { showStacks(); return this.addHeader(a, b); } // 其实就是猜,猜app中使用了哪些方法,一般找一些底层的方法