Android逆向学习(七)绕过root检测与smali修改学习
一、写在前面
这是吾爱破解正己大大教程的第五个作业,然后我的系统还是ubuntu, 这个是剩下作业的完成步骤。
二、任务目标
现在我们已经解决了一些问题,现在剩下的问题有
- hash校验需要解决
- 关于root检测的
- 关于与smali学习的
1.解决hash校验的问题
老样子,我们先查看hash校验的代码,看一下这个代码的逻辑
直接把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
| public final boolean check_Hash(Context context) { String apkPath = getApkPath((Context) this); FileInputStream fileInputStream = null; try { try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); byte[] bArr = new byte[1024]; Ref.IntRef intRef = new Ref.IntRef(); FileInputStream fileInputStream2 = new FileInputStream(new File(apkPath)); while (true) { try { int read = fileInputStream2.read(bArr); intRef.element = read; if (read <= 0) { break; } messageDigest.update(bArr, 0, intRef.element); } catch (Exception e) { e = e; fileInputStream = fileInputStream2; e.printStackTrace(); if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e2) { e2.printStackTrace(); } } return false; } catch (Throwable th) { th = th; fileInputStream = fileInputStream2; if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e3) { e3.printStackTrace(); } } throw th; } } String bigInteger = new BigInteger(1, messageDigest.digest()).toString(16); Log.e("zj2595", "hash:" + bigInteger); boolean areEqual = Intrinsics.areEqual(bigInteger, this.ApkHash); try { fileInputStream2.close(); } catch (IOException e4) { e4.printStackTrace(); } return areEqual; } catch (Exception e5) { e = e5; } } catch (Throwable th2) { th = th2; } }
|
2.解决root检验的问题
这个一共有三个检测方法,用了一个或操作,然后我就简要总结一下这个三个方法
检查有没有test-keys
检查发布的系统版本是测试版(test-keys)还是发布版(release-keys),这个不一定,因为有的系统发布版就是test-keys,而且这个很少有人用
检查相应目录下有没有su文件或者有没有superuser.apk这种只有root手机才有的信息
这个方法经常使用,不过规避方法也很多
使用which命令来检测是否存在su
这个其实也挺简单的,如果用hook的话
实际上如果一个手机被root之后也是很难被发现的,因为如果root后就可以使用xposed或者frida这些工具进行hook,hook的功能是非常的强大的。
然后这个的解决方法也是很多,比如:
- 直接修改smali代码,永远返回真(之前讲到过,不讲了)
- hook这个check代码,永远返回真(之前也讲到过,不讲了)
- 我想到的是使用frida做一个能够一劳永逸的hook代码
3.关于smali学习的题目

这里smali学习部分的作业,我们的目标就是通过修改smali代码,使vip和会员到期时间通过,并且修改钻石数量。
三、实现方法
问题一的实现
通过对代码的分析我们可以了解到。首先会读取apk的路径,然后计算出hash值,然后使用这个hash值和之前的hash值进行比对,就可以得到最终的正确的hash值。
这个的解决方法就太多了,我们就随便选择一个方式解决掉。
- 比如frida直接hook这个函数让他永远返回true。
- hook这个getApkPath((Context) this)的方法,然后修改这个路径到我们自己安装包的路径,不过在上一篇文章中这个方法有点问题(我怀疑是这个apk本身的bug)
- 修改this.ApkHash,让这里hash值和实际计算的hash值一样。
这些方法在上一篇博客中已经介绍过了,所以这里不再赘述了,直接使用第一个方法给他hook了
1 2 3 4 5 6 7
| Java.perform(function () { Java.use("com.zj.wuaipojie.ui.ChallengeFifth").check_Hash.implementation = function (j) { var result = this.check_Hash(j); send(result); return true; } })
|
这样就可以很轻松的通过(frida真厉害!)

问题二的实现
关于root的实现我们先看一下java代码(注:这个地方smali2java插件是有问题的,所以这一串代码我是通过jd-gui逆向得到的)
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
| public final boolean check_root() { return (checkRootMethod1() || checkRootMethod2() || checkRootMethod3()); } public final boolean checkRootMethod1() { String str = Build.TAGS; boolean bool2 = false; boolean bool1 = bool2; if (str != null) { bool1 = bool2; if (StringsKt.contains$default(str, "test-keys", false, 2, null)) bool1 = true; } return bool1; }
public final boolean checkRootMethod2() { for (byte b = 0; b < 10; b++) { (new String[10])[0] = "/system/app/Superuser.apk"; (new String[10])[1] = "/sbin/su"; (new String[10])[2] = "/system/bin/su"; (new String[10])[3] = "/system/xbin/su"; (new String[10])[4] = "/data/local/xbin/su"; (new String[10])[5] = "/data/local/bin/su"; (new String[10])[6] = "/system/sd/xbin/su"; (new String[10])[7] = "/system/bin/failsafe/su"; (new String[10])[8] = "/data/local/su"; (new String[10])[9] = "/su/bin/su"; if ((new File((new String[10])[b])).exists()) return true; } return false; }
public final boolean checkRootMethod3() { boolean bool1 = false; boolean bool2 = false; boolean bool3 = false; Process process = null; try { Process process1 = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" }); process = process1; BufferedReader bufferedReader = new BufferedReader(); process = process1; InputStreamReader inputStreamReader = new InputStreamReader(); process = process1; this(process1.getInputStream()); process = process1; this(inputStreamReader); process = process1; String str = bufferedReader.readLine(); bool1 = bool3; if (str != null) bool1 = true; bool2 = bool1; if (process1 != null) { process = process1; } else { return bool2; } process.destroy(); } finally { Exception exception = null; } return bool2; }
|
我们一共发现了三个检测的方法,这个在前面的分析已经讲过了,然后我们启动一下frida

之后我们写一个frida脚本然后运行一下hook,这个hook的主要目的就是把checkroot2里面的查找跟root有关文件的东西给屏蔽掉,就是如果一个手机是已经root了的,那么就会产生su等文件,我们就是通过frida hook避免这个程序发现这些文件,然后我之前以为camodroid比较全面可以完成这些东西,但是实际运行发现camodroid并不能完全屏蔽掉这些,所以我自己写了一个(在camodroid上改的)。
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
| Java.perform(function () { Java.use('java.io.File')['$init'].overload('java.lang.String').implementation = function (pathString) {
let map = { "/system/xbin/busybox": "/system/xbin/busybo", "/system/bin/su": "/system/bin/s", "/system/xbin/su": "/system/bin/s", "/system/app/Superuser.apk":"1", "/sbin/su":"1", "/data/local/xbin/su":"1", "/data/local/bin/su":"1", "/system/sd/xbin/su":"1", "/system/bin/failsafe/su":"1", "/data/local/su":"1", "/su/bin/su":"1", }; var retval; if (map[pathString] != null) { retval = this.$init("/system/bin/suasaadfghdfgh"); } else { retval = this.$init(pathString); } return retval; }; Java.use("com.zj.wuaipojie.ui.ChallengeFifth").checkRootMethod1.implementation = function () { var result = this.checkRootMethod1(); send(result); send("check1") return result; } Java.use("com.zj.wuaipojie.ui.ChallengeFifth").checkRootMethod2.implementation = function () { var result = this.checkRootMethod2(); send(result); send("check2") return result; } Java.use("com.zj.wuaipojie.ui.ChallengeFifth").checkRootMethod3.implementation = function () { var result = this.checkRootMethod3(); send(result); send("check3") return result; } })
|
结果如下图片

然后有个问题就是check3方法,这个方法本来就有问题

就是正常情况下就不应该使用只使用/system/xbin/which去检测是否有su文件,这个算是吾爱作业软件的一个小bug吧
问题三的实现
这个界面在smalilearn里面,我们直接smali2java插件给转了,这个代码不长,所以就直接粘贴到这里了
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
| public final class SmaliLearn extends AppCompatActivity { private final int vip_coin;
public final int isVip() { return 0; }
public final long vipEndTime() { return 1671889481513L; }
public final int getVip_coin() { return this.vip_coin; }
protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(2131427364); ((Button) findViewById(2131230819)).setOnClickListener(new SmaliLearn$.ExternalSyntheticLambda0(this, (TextView) findViewById(2131231219), (TextView) findViewById(2131231221), (TextView) findViewById(2131231220))); }
public static final void m1onCreate$lambda0(SmaliLearn smaliLearn, TextView textView, TextView textView2, TextView textView3, View view) { int isVip = smaliLearn.isVip(); if (isVip == 0) { textView.setText("非会员"); } else if (isVip == 1) { textView.setText("会员"); } else if (isVip == 4) { textView.setText("大会员"); } else if (isVip == 16) { textView.setText("超级会员"); } else if (isVip == 99) { textView.setText("至尊会员"); } long time = new Date().getTime(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); if (smaliLearn.vipEndTime() < time) { textView2.setText("已过期"); } else { textView2.setText(simpleDateFormat.format(Long.valueOf(smaliLearn.vipEndTime()))); } int i = smaliLearn.vip_coin; if (i != 0) { textView3.setText(String.valueOf(i)); } } }
|
这个修改的部分有
原来的isVIp那个const/16 v0,0x63,就是把原来的v0修改成了99,关于smali这个东西,我是学过微机原理,对MIPS,X64等汇编语言有较多的使用经验,我建议如果你真的想学smali的话可以先去学学这些汇编,不然很难上手smali

这个是修改了时间,我就把第一位修改成了9

这个就是把调用返回值换成了直接进行赋值不调用


然后下面就是我们的运行结果,完结撒花!!!
