|
frida入门
本文为看雪论坛优秀文章 看雪论坛作者ID:以和爲貴
一
java层的案例
demo_01
输入完用户名和密码之后发现窗口提示login faild:
![](https://img-blog.csdnimg.cn/7745ab7e1de1444894ee2813dba93241.png)
直接在jadx中搜索字符:
![](https://img-blog.csdnimg.cn/fa2938249f2c4bb2ba201c34a57a3cc8.png)
然后定位到关键代码:![](https://img-blog.csdnimg.cn/758e09963722438bb6620e2c163b01e0.png)
逻辑非常清晰:
输入用户名和密码。
final EditText editText = (EditText) findViewById(R.id.username);final EditText editText2 = (EditText) findViewById(R.id.password);
点击之后获取两个文本框中的值。public void onClick(View view) { String obj = editText.getText().toString(); String obj2 = editText2.getText().toString();
只有LoginActivity.a(obj, obj)=(obj2)时才能跳向正确的分支。else if (LoginActivity.a(obj, obj).equals(obj2)) { LoginActivity.this.startActivity(new Intent(LoginActivity.this.mContext, FridaActivity1.class)); LoginActivity.this.finishActivity(0);
然后交叉引用定位到了这个函数:
![](https://img-blog.csdnimg.cn/7d33a7a559b84f2a996f688680ac8871.png)
所以接下来我们就要hook这个函数得到他的返回值,然后将这个函数的返回值输入到第二个文本框中才可以:
首先用frida看看每个程序的pid,因为注入脚本的时候有的frida版本的问题需要使用程序的pid进行注入:
然后用objection看看内存中是否真正的存在这个activity:
![](https://img-blog.csdnimg.cn/eded3a905e00421d88448011690517e3.png)
之前我的一篇文章中的有个activity需要手动跳转到android intent launch_activity XXXXXX。
然后在看看这个activity中的函数在内存中的情况:
![](https://img-blog.csdnimg.cn/3504c66d765a48eabd39d85c1c88e3e7.png)
发现程序确实调用了这个函数:
(agent) [442509] Called com.example.androiddemo.Activity.LoginActivity.a(java.lang.String, java.lang.String)function hook_java(){ Java.perform(function(){ Java.use("com.example.androiddemo.Activity.LoginActivity").a.overload('java.lang.String', 'java.lang.String').implementation = function(arg1,arg2){ console.log("hook,start!") var result = this.a(arg1,arg2); console.log("arg1,arg2,result:",arg1,arg2,result); } })} function main(){ hook_java()//在main函数中调用hook_java函数 } setImmediate(main);
然后在命令行窗口中就可以发现函数的返回值了:
![](https://img-blog.csdnimg.cn/d4a652246dfc4d029a487fa3ba0e0400.png)
所以这个函数如果输入aaa123返回值就应该是:
ed5091524bdcb5bf75012e7562cf99d4f7078da00af8c70210196c835c27239f
点击登录按钮成功跳向了第一关:
点击登录按钮提示check faild。
![](https://img-blog.csdnimg.cn/44c52c6a838646d1b7774b8da9dcd5af.png)
那么接下来继续在jadx中搜索这个字符串:
![](https://img-blog.csdnimg.cn/546ef102899c49808c0d043fcffcc8d4.png)
然后定位到了这里:
![](https://img-blog.csdnimg.cn/5df23dcb5e10428ab4841c465ac016d7.png)
![](https://img-blog.csdnimg.cn/e767892c2be24b24b7e6fee4ae50ced2.png)
所以我们就要hook这个a函数让他返回这个字符串。R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=
用objection看一下,发现调用了这几个函数:
![](https://img-blog.csdnimg.cn/6c7de2ca3e6b4097baa00f8f34d77d1f.png)
我们发现这个函数在内存中是真实存在的,所以需要我们hook的只有这一个函数:
com.example.androiddemo.Activity.FridaActivity1.a([B)
![](https://img-blog.csdnimg.cn/f554434c01684bf7a33d855656719aae.png)
然后写js脚本就行了:function hook_java(){ Java.perform(function(){ Java.use("com.example.androiddemo.Activity.FridaActivity1").a.implementation = function(arg1){//这里有个函数重载,在刚刚的objection中也可以看出来 console.log("hook,start!") var result = "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL="; console.log("arg1:",arg1); return result; } })} function main(){ hook_java()//在main函数中调用hook_java函数 } setImmediate(main);![](https://img-blog.csdnimg.cn/ed7b8b7dd0c540528f1607067415191b.png)
成功过关。
然后继续下一关,应该是这里:
![](https://img-blog.csdnimg.cn/b262c274b74a40d1ad08525027004db7.png)
首先看看都有什么函数。
![](https://img-blog.csdnimg.cn/a527563c54eb4c398a83e706fc6a468c.png)
我们再用objection看看调用了哪些函数:
![](https://img-blog.csdnimg.cn/de552410ff974ce98729981072fe514b.png)
发现这两个函数并没有被调用,所以我们要hook这两个函数让他们主动执行:private static void com.example.androiddemo.Activity.FridaActivity2.setStatic_bool_var()private void com.example.androiddemo.Activity.FridaActivity2.setBool_var()
按照程序的执行流程我们可以知道:
![](https://img-blog.csdnimg.cn/45caaca176564aa89f39bc3510a78ca2.png)
我们要让onCheck() 函数中的if判断的返回值为假,才能跳向正确的分支,所以我们要让两个变量的值都为true,这里有两种思路,一种是hook两个变量的初始值,修改函数的初始值,还有一种方法是我们主动调用这俩个函数来修改变量的值。
我们先来实现一种简单的方法,直接修改变量的值:
对了之前忘了说了,我们可以再js脚本所在的文件夹中输入这个命令,来实现代码自动补全提示的功能:
function hook_java(){ Java.perform(function(){ //这里需要注意一下,静态的成员变量可以直接修改 // private static boolean static_bool_var = false; Java.use("com.example.androiddemo.Activity.FridaActivity2").static_bool_var.value = true; //动态的成员变量需要使用主动调用的方法 //private boolean bool_var = false; Java.choose("com.example.androiddemo.Activity.FridaActivity2",{ onMatch:function(instence){ console.log("found instence:",instence); instence.bool_var.value = true; },onComplete:function(){console.log("instence completed!")} }) })} function main(){ hook_java()//在main函数中调用hook_java函数 } setImmediate(main);
然后在来实现第二种方法,hook这两个函数,让他们主动执行一下,因为从刚刚的objection的分析中我们可以发现,那两个函数并没被调用,所以我们要hook它,让他主动执行一下。function hook_java(){ Java.perform(function(){ // private static void setStatic_bool_var() //这个函数是静态的函数,所以可以直接hook调用 Java.use("com.example.androiddemo.Activity.FridaActivity2").setStatic_bool_var(); // private void setBool_var() //这个函数不是static修饰的,所以要通过主动调用的方式 Java.choose("com.example.androiddemo.Activity.FridaActivity2",{ onMatch:function(instance){ console.log("found instence",instance); instance.setBool_var()//调用函数,执行函数 },onComplete:function(){console.log("instence,completed!");} }) })} function main(){ hook_java()//在main函数中调用hook_java函数 } setImmediate(main);
通过这两种方法我们都可以实现通关。
然后我们进入第三关:![](https://img-blog.csdnimg.cn/41aec819c09d4e6d883b9c10b645b130.png)
objection看一下执行了什么函数。
![](https://img-blog.csdnimg.cn/c1c0a39a9322401881a19916f1d8ce11.png)
发现修改成员变量的值就可以了:
![](https://img-blog.csdnimg.cn/96f7d80cf0f64c47929fcfbd9ccbde0b.png)
把这三个成员变量的值都修改为true,程序就会执行success分支。
当然我们也需要注意一下动态成员变量的修改方法:function hook_java(){ Java.perform(function(){ //private static boolean static_bool_var = false; //静态的成员变量可以直接修改 Java.use("com.example.androiddemo.Activity.FridaActivity3").static_bool_var.value = true; // private boolean bool_var = false; //private boolean same_name_bool_var = false; //动态的成员变量需要主动调用 Java.choose("com.example.androiddemo.Activity.FridaActivity3",{ onMatch:function(instence){ console.log("found instence:",instence); instence.bool_var.value = true;//修改成员变量的值 //这里需要注意一下,因为具有同名的成员变量和成员函数,所以修改成员变量的值的时候需要在前面加一个_ instence._same_name_bool_var.value = true;//修改成员变量的值 },onComplete:function(){console.log("instence completed!")} }) })} function main(){ hook_java()//在main函数中调用hook_java函数 } setImmediate(main);
成功进入第四关:
![](https://img-blog.csdnimg.cn/6720a90f185b40869af50e4507bf82fd.png)
由于我们不知道到底调用了哪些方法,这里我们还是先用objection看一下函数的执行:(到这一关了,告诉大家一个这个程序的bug):
可以直接用objection在不同的activity中来回跳。
![](https://img-blog.csdnimg.cn/bff8bb68eade44879bdbb536f8f277ae.png)
好了我们回来继续看看函数的调用:
![](https://img-blog.csdnimg.cn/d4f77cfc6525416a880c495877d40524.png)
然后接下来我们只需要让每一个check函数都返回true就可以了。function hook_java(){ Java.perform(function(){ Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check1.implementation = function(){return true}; Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check2.implementation = function(){return true}; Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check3.implementation = function(){return true}; Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check4.implementation = function(){return true}; Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check5.implementation = function(){return true}; Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check6.implementation = function(){return true}; })} function main(){ hook_java()//在main函数中调用hook_java函数 } setImmediate(main);
然后进入第五关:
先用objection看看调用了什么函数。
![](https://img-blog.csdnimg.cn/cb42bef3f2ec4129ad52fbbd9cb899f8.png)
这里我们需要分析一下代码:
![](https://img-blog.csdnimg.cn/aebf2d1446d842969907463d0cf56a00.png)
判断getDynamicDexCheck()这个函数的返回值是否为空。
![](https://img-blog.csdnimg.cn/7a217edc1bb64950b593497a0ad7e662.png)
然后发现他是动态加载一个dex文件:
![](https://img-blog.csdnimg.cn/14b2cfe091854abca9244acf649bb1eb.png)
然后这个check函数在这里声名:
![](https://img-blog.csdnimg.cn/2f698fdc9c4348539a330ff0238811c5.png)
我们如果直接hook这个check函数是不会成功的,所以我们要先找到这个dex文件:function fifth(){ Java.perform(function(){ Java.choose("com.example.androiddemo.Activity.FridaActivity5",{ onMatch:function(instance){ //用classname来查看 console.log("found instence getDynamicDexCheck():",instance.getDynamicDexCheck().$className); },onComplete:function(){console.log("search complete!");} }) //用枚举法看看在那个类里面 Java.enumerateClassLoaders({ onMatch:function(loader){ try { if(loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")){ console.log("Success found loader:",loader); //将默认的classloader替换成loader Java.classFactory.loader = loader; } } catch (error) { console.log("found,error!"+error); } },onComplete:function(){console.log("enum complete!")} }) Java.use("com.example.androiddemo.Dynamic.DynamicCheck").check.implementation = function(){return true;} })}
最后进入第六关:
![](https://img-blog.csdnimg.cn/74d413ef397e4aed84362e861057b5bf.png)
这里我们采取枚举所有类的方法来hook。function func6() { Java.perform(function () { Java.enumerateLoadedClasses({ onMatch: function (name, handle) { if (name.indexOf("com.example.androiddemo.Activity.Frida6") >= 0) { console.log(name); var frida6 = Java.use(name); frida6.check.implementation = function () { console.log("frida 6 check:", this); return true; }; } }, onComplete: function () { } }) });}
demo_02
安装好程序之后我们首先看一下程序的提示:
![](https://img-blog.csdnimg.cn/59b1ba5a4706410084e4f87901cf2218.png)
提示设备只能运行在俄罗斯的设备上,然后我们用objection看一下程序的activity:
![](https://img-blog.csdnimg.cn/2ec2abfeefde428da7b469235b6a7fd1.png)
然后竟然惊喜的发现程序可以在不同的activity中切换:![](https://img-blog.csdnimg.cn/cfb35bd5fbb14a2ea5f124472926283a.png)
![](https://img-blog.csdnimg.cn/d49e01e392214683979f50cfaba80a24.png)
![](https://img-blog.csdnimg.cn/74861d6eff9e41849ac3c02589697c8a.png)
......这并不是我们想要做的,还是从一开始的不能在俄罗斯的设备上开始分析吧:
通过搜索字符串,我们定位到了这里:
![](https://img-blog.csdnimg.cn/76ce6f49dded486da2d6aaed58f87d6f.png)
所以我们就要hook这个函数的返回值System.getProperty()为Russian。
![](https://img-blog.csdnimg.cn/3311e12786434e8691d136910d388877.png)
可以去看看system在哪个包下面:
接下来就可以写js脚本了:function main(){ Java.perform(function(){ var System = Java.use("java.lang.System"); console.log(System); System.getProperty.overload('java.lang.String').implementation = function (key) { var result = this.getProperty(key); result = "Russia"; console.log("System.getProperty:", key, result); return result; }; }) } setImmediate(main)
frida -U --no-pause -f com.tlamb96.spetsnazmessenger -l E:\homework_python\frida_env\shizhan_02.js
这里要注意一下,这里要使用spwan的方式注入,因为是在app启动的过程中注入的脚本。
接下来手机提示这样的界面:
![](https://img-blog.csdnimg.cn/a63979950c784c998573c642d61a7fd4.png)
继续字符串搜索看看。
![](https://img-blog.csdnimg.cn/e15288de272d43f5a4ba7f3f4d9d3ab8.png)
根据程序的逻辑,这两个变量成员得相等:
str.equals(getResources().getString(R.string.User)))
然后向上找找str:
String str = System.getenv("USER");
所以接下来我们就要hook这个getenv函数了。
然后找到(R.string.User)这个的值作为函数的返回值。
<string name="User">RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==</string>
这里注意一下,在写js脚本的时候之前写的js脚本不要删除,因为这两个都需要hook:
js脚本:function main(){ Java.perform(function(){ var System = Java.use("java.lang.System"); console.log(System); System.getProperty.overload('java.lang.String').implementation = function (key) { var result = this.getProperty(key); result = "Russia"; console.log("System.getProperty:", key, result); return result; }; System.getenv.overload('java.lang.String').implementation = function (key) { var result = this.getenv(key); result = "RkxBR3s1N0VSTDFOR180UkNIM1J9Cg=="; console.log("getenv :",key,result); return result; }; }) } setImmediate(main)![](https://img-blog.csdnimg.cn/878bda3c4a5242ddaabe99d7b5db5c22.png)
成功进入这个界面:![](https://img-blog.csdnimg.cn/eff65a1752ba4df8958ef30f34f1bcad.png)
随意输入一下,程序是这样提示的:
![](https://img-blog.csdnimg.cn/b3739fa628cc4a2e8defb978f4de195f.png)
然后我们继续去jadx中看看字符串,定位到了这里:
![](https://img-blog.csdnimg.cn/4d682c414b2e4c93b0eddc2ee69a9b52.png)
然后开始分析这个程序:if (this.n != null && this.o != null && !this.n.isEmpty() && !this.o.isEmpty()) {
这一行代码的意思是我们输入的用户名和密码都不能为空。if (!this.n.equals(getResources().getString(R.string.username))) {
这个的意思是输入的用户名要和资源文件中的相等。
<string name="username">codenameduchess</string>
</resources>
然后进入这个分支:} else if (!j()) {
然后看看j函数是什么:private boolean j() { byte[] digest = this.m.digest(this.o.getBytes());//这里是用户输入的密码经过m函数(跟进分析之后发现m函数是md5函数) String str = ""; for (byte b : digest) { str = str + String.format("%x", Byte.valueOf(b)); }//转换成hex字符串 return str.equals(getResources().getString(R.string.password));//最后这个str要和资源文件中的password相等才可以 // <string name="password">84e343a0486ff05530df6c705c8bb4</string> }
然后直接获得这个密码的明文就可以了。
![](https://img-blog.csdnimg.cn/dd5c7fbcc0c8480cb2fd334c878279ee.png)
那么我们将用户名:codenameduchess
密码:guest输入文本框之后就可以了:
然后成功进入下一个activity。
![](https://img-blog.csdnimg.cn/8dec6f929a514d65a6dbbdfbd8f44170.png)
![](https://img-blog.csdnimg.cn/390466f3ce9b48aeb9a19a17bd0ffcc0.png)
![](https://img-blog.csdnimg.cn/390466f3ce9b48aeb9a19a17bd0ffcc0.png)
然后随意输入之后发现没有什么提示,只能看代码了:
我们发现这里有程序界面中的字符串。
![](https://img-blog.csdnimg.cn/d8eac19d48cb40c29795a10abc3af4fa.png)
我们可以猜测,接下来的操作肯定是要输入字符串,所以我们可以hook这个com.tlamb96.kgbmessenger.b.a函数,看看他的函数调用栈,可能就会有发现:
根据刚刚界面的参数我们可以知道,我们接下来是要hook这个构造函数的:
public a(int i, String str, String str2, boolean z)
var a = Java.use("com.tlamb96.kgbmessenger.b.a"); a.$init.implementation = function(i,str,str2,z){ this.$init(i,str,str2,z); console.log("a,$init",i,str,str2,z); console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new())); } })
调用栈取自r0ysue大佬的知识星球。
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
![](https://img-blog.csdnimg.cn/43db383e99744a13999181d7b2acd1c8.png)
发现是由这个函数调用来的:
com.tlamb96.kgbmessenger.MessengerActivity.onSendMessage
从gadx中看看:
![](https://img-blog.csdnimg.cn/1f8dc470c03b4df8af01ae150279fe18.png)
在127行发现确实是由这个函数调用的。
然后我们发现程序的逻辑就是让这个a函数的返回值为:
![](https://img-blog.csdnimg.cn/27bc2f19bad7487490b2b66034d8ed8b.png)
然后我们用objection看一下,程序在执行过程中确实调用了这个函数:
com.tlamb96.kgbmessenger.MessengerActivity.a
![](https://img-blog.csdnimg.cn/c91616d7943f4504b976295baf3b663a.png)
这个a函数不是静态。
![](https://img-blog.csdnimg.cn/46c4c4344597494d92620f297ee1ae24.png)
所以我一开始想用主动调用的方法来着,但是通过主动调用hook这个函数之后,即使没有输入他也会hook成功,所以达不到我们想要的效果:Java.choose("com.tlamb96.kgbmessenger.MessengerActivity",{ onMatch:function(instance){ console.log("found instance",instance); instance.a = function(str){ result = "V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003"; return result; } },onComplete:function(){console.log("search,completed!")}})![](https://img-blog.csdnimg.cn/6111538f7ab04814b2ee9c6578456c4b.png)
发现这样是不可以的,所以还是采用java.use的方法来hook这个函数。Java.use("com.tlamb96.kgbmessenger.MessengerActivity").a.implementation = function(x){ var result = this.a(x); console.log("a:x,result:",x,result); if(x=="kanxue"){ result= Java.use("java.lang.String").$new("V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003"); } return result;}
如果我们输入kanxue的话就会注入脚本。
![](https://img-blog.csdnimg.cn/b87fa98021b7427e9a007f68367626ca.png)
程序就会正确运行。
这里我们采用了类似于爆破的方法,但是这样程序到最后是不会得到flag的
我们在程序的末尾发现了flag是由i和j函数产生的。
![](https://img-blog.csdnimg.cn/3e35c3f727a34e52b72f9b3574fd7043.png)
然后这个i函数里面的两个参数
char[] charArray = this.q.substring(19).toCharArray();
char[] charArray2 = this.s.substring(7, 13).toCharArray();q和s是由我们的程序的输入框中的值得到的。![](https://img-blog.csdnimg.cn/7e7c44ce83de45a781c952477688c298.png)
所以我们还是要按照解密顺序一步步的来,不能直接返回两个字符串的值。
![](https://img-blog.csdnimg.cn/5a2e81c0734449bbab0f0d8e5f7d59bc.png)
这里我们用android stdio将java算法变成dex文件来解密。
![](https://img-blog.csdnimg.cn/afc38d9379df4a4ba054727c777f2583.png)
build\intermediates\javac\debug\classes\com\example\myapplication这个目录下是编译得class文件,然后将.class文件转换成.jar文件在转换成.dex文件,然后将dex文件放在data/local/tmp文件下,然后给他777权限,最后在js脚本中加载dex文件。
![](https://img-blog.csdnimg.cn/6ed78bb00b2c45c2a18d5fd0a45c9c2c.png)
在js脚本刚开始的地方:
![](https://img-blog.csdnimg.cn/a5b93759b9fd415e8c07cc889f258850.png)
然后输入执行后的结果。
![](https://img-blog.csdnimg.cn/fc00d44284cc46679994cd9d7ad50933.png)
继续去jadx中看看。
![](https://img-blog.csdnimg.cn/0bc0f90d363145dca29f5e680482a182.png)
所以接下来就要分析这个b函数了。
![](https://img-blog.csdnimg.cn/10600d54c1744647a287f68f366d83cb.png)
如果还是按照之前的方法就不好弄了,所以直接编写py脚本解密就可以了:
![](https://img-blog.csdnimg.cn/2fe494b06bd54ea480d42afdbeec1d5d.png)
输入程序的运行结果之后得到flag:
![](https://img-blog.csdnimg.cn/e035eac6073b4d0692fcc435999e2dca.png)
二
so层的案例
当程序执行到这一步的时候,如果点击好吧,就会退出程序:
![](https://img-blog.csdnimg.cn/4250eeb55f854a359d6a30539dd78c4d.png)
然后再jadx中定位到关键代码。
![](https://img-blog.csdnimg.cn/49d2ec06545049dcaff1227be2d59525.png)
我们可以发现:
![](https://img-blog.csdnimg.cn/e572e90fbab0474d81efd355d77cece9.png)
sn是我输入的字符串,然后转成字节,转成string类型。
然后这一行代码中传入了:((MyApp) RegActivity.this.getApplication()).saveSN(sn);saveSN这个关键函数。
我们跟进去分析一下,他是native层的函数。
我们再从程序的入口点分析分析:
我们刚刚分析的注册的函数是从这里跳过来的:
![](https://img-blog.csdnimg.cn/470379c65bc845839adb2d0e8ac58ca9.png)
如果我们再doRegister() 函数中点击注册按钮,就会执行这里。intent.setComponent(new ComponentName(BuildConfig.APPLICATION_ID, "com.gdufs.xman.RegActivity"));
然后我们再向上回溯:
![](https://img-blog.csdnimg.cn/fcd213b28bda45688aa7a58e6c4cc2b6.png)
在这个oncreate函数中的onclick方法中:
![](https://img-blog.csdnimg.cn/bd3cc1b749d64c5882be250d53917971.png)
根据MyApp.m 的值判断是否需要注册。
然后我们分析一下这个MyAPP方法,发现他的逻辑也在so层:
![](https://img-blog.csdnimg.cn/914a3ccb02c24ed49315905052a1be89.png)
所以我们要分析的就是这几个so层的方法了,在ida中打开看看:
我们在export中并没有发现savesn方法:
![](https://img-blog.csdnimg.cn/0af53589ff1b493486b9c81849e93192.png)
所以它采用的是动态注册的方法,直接去jni中看看:
首先要导入jni。h头文件,不要忘了这一点:
这里展示一下我修复之后的jni函数:
![](https://img-blog.csdnimg.cn/4cf77a75925047ab85dc29103ecab913.png)
然后我们发现确实注册了这几个函数:
![](https://img-blog.csdnimg.cn/34eb7638bf3847d591b57e99a79ed67f.png)
我们首先来分析第一个函数"initSN"。
也就是那个n1函数:
![](https://img-blog.csdnimg.cn/bc522fd6c55445da9e39bb5d5d30aa36.png)
首先打开这个文件/sdcard/reg.dat,我们会发现里面是一些奇怪的字符:
![](https://img-blog.csdnimg.cn/ba761da4f53a480f97267095613e05fe.png)
然后将文件中的内容存贮在v6中:
如果v6的值为"EoPAoY62@ElRD",那么将v8的值设置为1,否则将v8的值设置为0。
然后我们看一下n3函数:
![](https://img-blog.csdnimg.cn/99fabdb2c93a4359aca3b9fc15ab60c6.png)
然后跟进 v2 = getValue(a1);这里看看:
![](https://img-blog.csdnimg.cn/ba79bf7b925046d2a900870dc2ec152a.png)
发现这里将m赋值,也就是刚刚给v8赋的值在这里传递给了m。
最后我们来看一下savesn函数:
这里我展示一下修复之后的代码:
![](https://img-blog.csdnimg.cn/dbca0396bbcf498195ff66cff88648a4.png)
先打开这个文件中的内容/sdcard/reg.dat
然后将我们输入的字符串str传入v7:
v7 = (*env)->GetStringUTFChars(env, str, 0);
然后通过解密这个函数,就能得到flag。
最后得到的flag为:xman{201608Am!2333}
先附上js爆破的代码:var fputs_str = null; function Hook() { Java.perform(function () { const imports = Module.enumerateImportsSync("libmyjni.so"); const imports_len = imports.length; var fputs_addr = null; for (var i = 0; i < imports_len; i++) { if (imports.name == "fputs") { fputs_addr = imports.address; break; } } if (fputs_addr != null) { Interceptor.attach(fputs_addr, { onEnter: function (args) { fputs_str = args[0].readCString(); }, onLeave: function (retval) { } }) } })} function Invoke(temp) { Java.perform(function () { Java.choose("com.gdufs.xman.MyApp", { onMatch: function (instance) { instance.saveSN(temp); }, onComplete: function () { } }) })} function attack() { Hook(); Java.perform(function () { const _array = new Array("EoP", "AoY", "62@", "ElR"); const end = "D"; const secret = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()?_" const _array_len = _array.length; const secret_len = secret.length const myapp = Java.use("com.gdufs.xman.MyApp").$new(); var flag = ""; for (var i = 0; i < _array_len; i++) { var flag = false; for (var j = 0; j < secret_len; j++) { if (flag == true) { break; } for (var k = 0; k < secret_len; k++) { if (flag == true) { break; } for (var m = 0; m < secret_len; m++) { const temp = secret[j] + secret[k] + secret[m]; console.log(`temp: ${temp}`); myapp.saveSN(temp); if (_array == fputs_str) { flag += temp; console.log(`flag: ${temp}`); flag = true; break; } } } } } for (var i = 0; i < secret_len; i++) { const temp = secret; console.log(`temp: ${temp}`); Invoke(temp); if (end == fputs_str) { flag += temp; console.log(`flag: ${temp}`); break; } } console.log(`flag: xman{${flag}}`); })}
这道题的逻辑倒是不难,接下来我们试一试frida:
首先我们就要hook掉那个java层的killprocess的函数,一执行就退出,太恶心了:
![](https://img-blog.csdnimg.cn/d4f7adfe17e84a85ad29417485a9b09e.png)
我们会发现这个函数在这个包里面:
![](https://img-blog.csdnimg.cn/6e4566c6cdab4d6490c03fc4caa27386.png)
直接展示frida代码:function hook_java(){ Java.perform(function(){ var MyApp = Java.use("com.gdufs.xman.MyApp"); MyApp.saveSN.implementation = function(str){ console.log("MyApp.saveSN.str:",str); this.saveSN(str);//调用一下函数 } //hook掉killProcess函数,不让他执行 var Process = Java.use("android.os.Process"); Process.killProcess.implementation = function(pid){ console.log("Process.killProcess not implement!",pid); } console.log("hook completed!"); }) }
然后我们尝试着hook一下so层的用户层的函数,试试能不能找到:function hook_native(){ //找到模块的地址 var base_myjni = Module.findBaseAddress("libmyjni.so"); //如果以spwan的模式启动,那么就要判断base_myjni的值是否为0,一开始会不加载这个so文件 if(base_myjni){ console.log("base_myjni",base_myjni);//base_myjni 0xbfd0e000 //找到要hook的函数 这个函数得是export函数 //参数: so所在路径,要hook的函数的名字 var n2 = Module.findExportByName("libmyjni.so","n2"); //thumb格式的函数,hook的时候在静态分析的地址上面加1 //ida地址:0x000011F8 实际地址:n2: 0xbfd0f1f9 //相差一个模块的地址 console.log("hook_native()__ n2:",n2); //n2 - base_myjni = 偏移 + 1;//11f9 = 偏移 + 1; 偏移 = 11f8 也就是ida中的地址 //开始hook函数 Interceptor.attach(n2,{ onEnter:function(args){ //args是一个数组 console.log("hook_native()__ n2 onEnter:",args[0],args[1],args[2]); },onLeave:function(retval){ } }); } }
我们可以发现,成功的hook到了函数:
![](https://img-blog.csdnimg.cn/eabb0e3eeeca4760954852c23145db5f.png)
然后我们来hook一下这个系统层的函数,看看是不是我们输入的值:
![](https://img-blog.csdnimg.cn/2f967ed5dfb248a2bb0c01109ce5f9a4.png)
详细过程在js脚本里面有注释://hook这个函数GetStringUTFChars//这个函数GetStringUTFChars在libart里面function hook_libart(){ //枚举所有的文件来找到 //首先找到so var module_libart = Process.findModuleByName("libart.so"); //看看有哪些符号 var symbols = module_libart.enumerateSymbols(); //找到函数的名字和地址 var add_GetStringUTFChars = null; for(var i = 0;i < symbols.length;i++){ var name = symbols.name; if(name.indexOf("art") >= 0 ){ if( (name.indexOf("JNI") >= 0) && (name.indexOf("CheckJNI") == -1) ){ if(name.indexOf("GetStringUTFChars") >= 0){ console.log("hook_libart()__ name:",name);//找到函数的名字 add_GetStringUTFChars = symbols.address;//找到函数的地址 } } } } //开始hook函数 if(add_GetStringUTFChars){ Interceptor.attach(add_GetStringUTFChars,{ onEnter:function(args){ console.log("onEnter find add_GetStringUTFChars************************************\r\n"); },onLeave:function(retval){ //从ida中看出返回值为const char *类型 console.log("onLeave GetStringUTFChars_native_retval************************************\r\n:",ptr(retval).readCString()); } }) }
输入注册码为kanxue
确实可以hook到这个函数,并且返回值也是和我们预料的一样:
还可以hook这个系统层的函数来查看程序时候被注册://通过hook strcmp函数来查看m的值,来查看程序是否被注册function hook_libc() { //hook libc的函数 var strcmp = Module.findExportByName("libc.so", "strcmp"); console.log("strcmp:", strcmp); Interceptor.attach(strcmp, { onEnter: function (args) { var str_2 = ptr(args[1]).readCString(); if (str_2 == "EoPAoY62@ElRD") { console.log("strcmp:", ptr(args[0]).readCString(), ptr(args[1]).readCString()); } }, onLeave: function (retval) { } });}
然后我们在尝试一下利用frida向程序中写值://用firda向文件中写内容function write_reg_dat() { //frida 的api来写文件 var file = new File("/sdcard/reg.dat", "w"); file.write("EoPAoY62@ElRD"); file.flush(); file.close();}
这样就能达到爆破这道题目的效果。
|
|