frida入门
本文为看雪论坛优秀文章
看雪论坛作者ID:以和爲貴
一
java层的案例
demo_01
直接在jadx中搜索字符:
然后定位到关键代码:
逻辑非常清晰:
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();
else if (LoginActivity.a(obj, obj).equals(obj2)) {LoginActivity.this.startActivity(new Intent(LoginActivity.this.mContext, FridaActivity1.class));LoginActivity.this.finishActivity(0);
所以接下来我们就要hook这个函数得到他的返回值,然后将这个函数的返回值输入到第二个文本框中才可以:
之前我的一篇文章中的有个activity需要手动跳转到android intent launch_activity XXXXXX。
发现程序确实调用了这个函数:
(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);
所以这个函数如果输入aaa123返回值就应该是:
ed5091524bdcb5bf75012e7562cf99d4f7078da00af8c70210196c835c27239f
那么接下来继续在jadx中搜索这个字符串:
然后定位到了这里:
所以我们就要hook这个a函数让他返回这个字符串。
R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=我们发现这个函数在内存中是真实存在的,所以需要我们hook的只有这一个函数:
com.example.androiddemo.Activity.FridaActivity1.a([B)
然后写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);
成功过关。
首先看看都有什么函数。
我们再用objection看看调用了哪些函数:
发现这两个函数并没有被调用,所以我们要hook这两个函数让他们主动执行:
private static void com.example.androiddemo.Activity.FridaActivity2.setStatic_bool_var()private void com.example.androiddemo.Activity.FridaActivity2.setBool_var()
我们要让onCheck() 函数中的if判断的返回值为假,才能跳向正确的分支,所以我们要让两个变量的值都为true,这里有两种思路,一种是hook两个变量的初始值,修改函数的初始值,还有一种方法是我们主动调用这俩个函数来修改变量的值。
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);
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);
objection看一下执行了什么函数。
发现修改成员变量的值就可以了:
把这三个成员变量的值都修改为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);
由于我们不知道到底调用了哪些方法,这里我们还是先用objection看一下函数的执行:(到这一关了,告诉大家一个这个程序的bug):
好了我们回来继续看看函数的调用:
然后接下来我们只需要让每一个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);
这里我们需要分析一下代码:
判断getDynamicDexCheck()这个函数的返回值是否为空。
然后发现他是动态加载一个dex文件:
然后这个check函数在这里声名:
我们如果直接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替换成loaderJava.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;}})}
这里我们采取枚举所有类的方法来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
提示设备只能运行在俄罗斯的设备上,然后我们用objection看一下程序的activity:
然后竟然惊喜的发现程序可以在不同的activity中切换:
......这并不是我们想要做的,还是从一开始的不能在俄罗斯的设备上开始分析吧:
所以我们就要hook这个函数的返回值System.getProperty()为Russian。
可以去看看system在哪个包下面:
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)
继续字符串搜索看看。
根据程序的逻辑,这两个变量成员得相等:
str.equals(getResources().getString(R.string.User)))
然后向上找找str:
String str = System.getenv("USER");
然后找到(R.string.User)这个的值作为函数的返回值。
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)
成功进入这个界面:
随意输入一下,程序是这样提示的:
然后我们继续去jadx中看看字符串,定位到了这里:
然后开始分析这个程序:
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()) {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>}
那么我们将用户名:codenameduchess
密码:guest输入文本框之后就可以了:
然后成功进入下一个activity。
然后随意输入之后发现没有什么提示,只能看代码了:
我们可以猜测,接下来的操作肯定是要输入字符串,所以我们可以hook这个com.tlamb96.kgbmessenger.b.a函数,看看他的函数调用栈,可能就会有发现:
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()));}})
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
发现是由这个函数调用来的:
com.tlamb96.kgbmessenger.MessengerActivity.onSendMessage
在127行发现确实是由这个函数调用的。
然后我们用objection看一下,程序在执行过程中确实调用了这个函数:
com.tlamb96.kgbmessenger.MessengerActivity.a
这个a函数不是静态。
所以我一开始想用主动调用的方法来着,但是通过主动调用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!")}})
发现这样是不可以的,所以还是采用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;}
程序就会正确运行。
我们在程序的末尾发现了flag是由i和j函数产生的。
然后这个i函数里面的两个参数
char[] charArray = this.q.substring(19).toCharArray();
char[] charArray2 = this.s.substring(7, 13).toCharArray();
所以我们还是要按照解密顺序一步步的来,不能直接返回两个字符串的值。
这里我们用android stdio将java算法变成dex文件来解密。
build\intermediates\javac\debug\classes\com\example\myapplication这个目录下是编译得class文件,然后将.class文件转换成.jar文件在转换成.dex文件,然后将dex文件放在data/local/tmp文件下,然后给他777权限,最后在js脚本中加载dex文件。
在js脚本刚开始的地方:
然后输入执行后的结果。
继续去jadx中看看。
所以接下来就要分析这个b函数了。
如果还是按照之前的方法就不好弄了,所以直接编写py脚本解密就可以了:
输入程序的运行结果之后得到flag:
二
so层的案例
然后再jadx中定位到关键代码。
我们可以发现:
sn是我输入的字符串,然后转成字节,转成string类型。
((MyApp) RegActivity.this.getApplication()).saveSN(sn);如果我们再doRegister() 函数中点击注册按钮,就会执行这里。
intent.setComponent(new ComponentName(BuildConfig.APPLICATION_ID, "com.gdufs.xman.RegActivity"));在这个oncreate函数中的onclick方法中:
根据MyApp.m 的值判断是否需要注册。
所以我们要分析的就是这几个so层的方法了,在ida中打开看看:
所以它采用的是动态注册的方法,直接去jni中看看:
然后我们发现确实注册了这几个函数:
我们首先来分析第一个函数"initSN"。
首先打开这个文件/sdcard/reg.dat,我们会发现里面是一些奇怪的字符:
然后将文件中的内容存贮在v6中:
然后跟进 v2 = getValue(a1);这里看看:
发现这里将m赋值,也就是刚刚给v8赋的值在这里传递给了m。
先打开这个文件中的内容/sdcard/reg.dat
然后将我们输入的字符串str传入v7:
v7 = (*env)->GetStringUTFChars(env, str, 0);
最后得到的flag为:xman{201608Am!2333}
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[i].name == "fputs") {fputs_addr = imports[i].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.lengthconst 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[i] == fputs_str) {flag += temp;console.log(`flag: ${temp}`);flag = true;break;}}}}}for (var i = 0; i < secret_len; i++) {const temp = secret[i];console.log(`temp: ${temp}`);Invoke(temp);if (end == fputs_str) {flag += temp;console.log(`flag: ${temp}`);break;}}console.log(`flag: xman{${flag}}`);})}
我们会发现这个函数在这个包里面:
直接展示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!");})}
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一下这个系统层的函数,看看是不是我们输入的值:
详细过程在js脚本里面有注释:
//hook这个函数GetStringUTFChars//这个函数GetStringUTFChars在libart里面function hook_libart(){//枚举所有的文件来找到//首先找到sovar 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[i].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[i].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());}})}
//通过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) {}});}
//用firda向文件中写内容function write_reg_dat() {//frida 的api来写文件var file = new File("/sdcard/reg.dat", "w");file.write("EoPAoY62@ElRD");file.flush();file.close();}
看雪ID:以和爲貴
https://bbs.pediy.com/user-home-939330.htm
招生![2023春季班]《安卓高级研修班(网课)》月薪三万计划https://www.kanxue.com/book-leaflet-84.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!