从破解运动世界校园学习模拟器检测与Xposed检测

几个月没登过csdn 期间对于安卓逆向自然是多学习了一些 

本次研究学习运动世界校园的程度应该会比以前深一点

(以前发的那篇算个什么东西 简直是羞耻 留着就当激励自己了)

这次对360加固进行脱壳 拿到真实DEX以后就没什么难度了 只是练一个阅读代码的学习能力而已 脱壳方法多种多样 与主题无关暂不详述

 1 检测模拟器文件 JAVA层

前文提到的检测模拟器特征文件的函数仍然存在  只是没有用File.exists()的方法了 先放出反编译后的代码

private static List m(Context object) {
    Object object2;
    try {
        object2 = (String)aj.b((Context)object, b.ac, "[]");
        object = a.a().b((String)object2, object.getString(2131296410));//这个String为SWCampus2017DDY_ 用于解密加密后的Json Json内容为检测文件列表 我是根据这个字符串在strings.xml里的名字为emulator_key跟进来的
        object = (List)w.a().fromJson((String)object, new TypeToken>(){}.getType());
        if (object == null) return Arrays.asList((Object[])com.zjwh.android_wh_physicalfitness.b.a.u);
        object2 = object;
    }
    catch (Exception exception) {
        object = null;
        object2.printStackTrace();
        if (object == null) return Arrays.asList((Object[])com.zjwh.android_wh_physicalfitness.b.a.u);
        object2 = object;
        if (!object.isEmpty()) return object2;
        return Arrays.asList((Object[])com.zjwh.android_wh_physicalfitness.b.a.u);
    }
    if (!object.isEmpty()) return object2;
    return Arrays.asList((Object[])com.zjwh.android_wh_physicalfitness.b.a.u);
    {
        catch (Exception exception) {}
    }
}
首先不看内部代码 直接hook 在模拟器内运行的返回值为


一看便知 只要将返回值设为空列表就可以了

param.setResult(new ArrayList());

检测模拟器文件这关就算是过了

2 检测模拟器特征  JNI层(实则JAVA层hook 也可改SO)

JNI就不提了 全都是抄别人的代码 可以在后文看看原作者的文章 有学习价值

先上检测函数的代码

首先最大的大忌就是把这么多功能都写在一个函数里 直接hook return false就可以了

public static boolean a(Context var0) {
        block26 : {
            block19 : {
                var14_6 = (String)aj.b("emulator_switch", "");//明文硬编码 大忌 一看就知道是干什么的了
                if (!TextUtils.isEmpty((CharSequence)var14_6)) {
                    var14_6 = com.zjwh.android_wh_physicalfitness.utils.a.a().b((String)var14_6, var0.getString(2131296410));
                    d.f((String)"emulatorSwitch", (String)var14_6);
                    var14_6 = (EmulatorSwitchBean)new Gson().fromJson((String)var14_6, EmulatorSwitchBean.class);
                    if (var14_6 != null) {
                        var5_7 = var14_6.isCpu();//这些我不知道是不能混淆掉吗 一看就知道是检测硬件信息
                        var6_8 = var14_6.isCpuFreq();
                        var11_9 = var14_6.isKernel();
                        var10_10 = var14_6.isGravity();
                        var9_11 = var14_6.isTemp();
                        var8_12 = var14_6.isVolt();
                        var7_13 = var14_6.isFile();
                        var12_14 = var14_6.isGps();
                        var2_15 = var14_6.getPassCount();
                        var13_16 = var5_7;
                        var5_7 = var12_14;
                        break block19;
                    }
                }
                var5_7 = false;
                var7_13 = false;
                var8_12 = false;
                var9_11 = false;
                var10_10 = false;
                var11_9 = false;
                var6_8 = false;
                var13_16 = false;
                var2_15 = 0;
            }
            if (!var13_16) ** GOTO lbl44
            var14_6 = JniAnti.getCpuinfo();//检测CPU信息 包含桌面平台就是模拟器
            if (!var14_6.contains((CharSequence)"Genuine Intel(R)") && !var14_6.contains((CharSequence)"Intel(R) Core(TM)") && !var14_6.contains((CharSequence)"Intel(R) Pentium(R)") && !var14_6.contains((CharSequence)"Intel(R) Xeon(R)") && !(var12_14 = var14_6.contains((CharSequence)"AMD"))) ** GOTO lbl44
            var4_17 = 1;
            ** GOTO lbl45
            {
                catch (Exception var0_3) {
                    var1_19 = var2_15;
                    var2_15 = 0;
                    break block20;
                }
            }
            catch (Exception var0_1) {
                block20 : {
                    block25 : {
                        block24 : {
                            block23 : {
                                block22 : {
                                    block21 : {
                                        var1_19 = 0;
                                        var2_15 = 0;
                                        ** GOTO lbl118
lbl44: // 2 sources:
                                        var4_17 = 0;
lbl45: // 2 sources:
                                        var3_18 = var4_17;
                                        if (!var11_9) ** GOTO lbl58
                                        var1_19 = var4_17;
                                        var14_6 = JniAnti.getKernelVersion();
                                        var1_19 = var4_17; //下面的应该是检测服务
                                        if (var14_6.contains((CharSequence)"qemu+")) ** GOTO lbl-1000
                                        var1_19 = var4_17;
                                        if (var14_6.contains((CharSequence)"tencent")) ** GOTO lbl-1000
                                        var3_18 = var4_17;
                                        var1_19 = var4_17;
                                        if (var14_6.contains((CharSequence)"virtualbox")) lbl-1000: // 3 sources:
                                        {
                                            var3_18 = var4_17 + 1;
                                        }
lbl58: // 4 sources:
                                        var4_17 = var3_18;
                                        if (!var10_10) break block21;
                                        var4_17 = var3_18;
                                        var1_19 = var3_18;
                                        if (a.a((Context)var0)) break block21;
                                        var4_17 = var3_18 + 1;
                                    }
                                    var3_18 = var4_17;
                                    if (var9_11) {
                                        var3_18 = var4_17;
                                        var1_19 = var4_17;
                                        if (!TextUtils.isEmpty((CharSequence)a.d((Context)var0))) break block22;
                                        var3_18 = var4_17 + 1;
                                    }
                                }
                                var4_17 = var3_18;
                                if (var8_12) {
                                    var4_17 = var3_18;
                                    var1_19 = var3_18;
                                    if (!TextUtils.isEmpty((CharSequence)a.e((Context)var0))) break block23;
                                    var4_17 = var3_18 + 1;
                                }
                            }
                            var3_18 = var4_17;
                            if (var7_13) {
                                var3_18 = var4_17;
                                var1_19 = var4_17;
                                if (JniAnti.checkAntiFile() <= 0) break block24;//JNI层的文件检测 检测列表也可以逆向SO看出来
                                var3_18 = var4_17 + 1;
                            }
                        }
                        if (!var5_7) ** GOTO lbl101
                        var1_19 = var3_18;
                        try {
                            var5_7 = a.c((Context)var0);
                            if (var5_7) break block25;
                        }
                        catch (Exception var0_4) {
                            var3_18 = var1_19;
                            var1_19 = var2_15;
                            var2_15 = var3_18;
                        }
                        var1_19 = var3_18 + 1;
                        ** GOTO lbl103
                        ** GOTO lbl118
                    }
                    var1_19 = var3_18;
lbl103: // 2 sources:
                    var3_18 = var1_19;
                    var4_17 = var2_15;
                    if (!var6_8) break block26;
                    try {
                        var5_7 = c.a(a.a()).equals((Object)"0M");//不知道是什么 期待大牛点拨
                        var3_18 = var1_19;
                        var4_17 = var2_15;
                        ** if (!var5_7) goto lbl-1000
                    }
                    catch (Exception var0_5) {
                        var3_18 = var1_19;
                        var1_19 = var2_15;
                        var2_15 = var3_18;
                    }
lbl-1000: // 1 sources:
                    {
                        var3_18 = var1_19 + 1;
                        var4_17 = var2_15;
                    }
lbl-1000: // 2 sources:
                    {
                    }
                }
                var0_2.printStackTrace();
                var3_18 = var2_15;
                var4_17 = var1_19;
            }
        }
        if (var3_18 <= 0) return false;
        if (var3_18 < var4_17) return false;
        return true;
    }

直接return false 就可以破解了.我都不想看了..

(还是看看SO吧 不然没有长进)

直接SO拖进IDA 没有任何混淆防护 

查看字符串 不仅可以看到所有检测项目
从破解运动世界校园学习模拟器检测与Xposed检测_第1张图片

还可以看到原作者为qtfreet00 官方连LOG信息都没有改

可以直接修改SO里的检测关键词 使其返回空String 或可能抛出各种NotFound异常(未尝试)

值得一提的是 在JNI_OnLoad里 做了调用者类名的检测

我在测试的时候修改为自己的类名 偷偷化用为自用的模拟器检测工具了 致谢原作者qtfreet00 
从破解运动世界校园学习模拟器检测与Xposed检测_第2张图片


JNI层检测模拟器 原作者qtfreet00 Github
实现原理 原作者qtfreet00

说实话在JNI层检测除了cydia 没有什么hook native方便的方法 本来是个不错的解决方案(谁知道被官方用成这样了 笑

建议官方程序员学习一下支付宝或者一号店的防hook技术

3 Xposed检测(包括Cydia)

检测到手机环境不安全的就是这货了 鉴于还没有学习过如何用通用的方法屏蔽检测 暂时还是通过hook运动世界校园来过掉它

检测xposed又有3个函数

第一个 
搜索进程内存映射找Xposed Cydia
映射 读取/proc/pid/maps
寻找.so和.jar结尾
是否包含com.saurik.substrate XposedBridge.jar

很好过 还是return false
         

private static boolean b() {
    String string2;
    HashSet hashSet = new HashSet();
    String string3 = "/proc/" + Process.myPid() + "/maps";//读取映射
    try {
        string3 = new BufferedReader((Reader)new FileReader(string3));
        while ((string2 = string3.readLine()) != null) {
            if (!string2.endsWith(".so") && !string2.endsWith(".jar")) continue;//过滤so和jar
            hashSet.add((Object)string2.substring(string2.lastIndexOf(" ") + 1));
        }
        hashSet = hashSet.iterator();
    }
    catch (Exception exception) {
        exception.printStackTrace();
        return false;
    }
    do {
        if (!hashSet.hasNext()) {
            string3.close();
            return false;
        }
        string2 = (String)hashSet.next();
        if (!string2.contains((CharSequence)"com.saurik.substrate")) continue;//cydia
        return true;
    } while (!string2.contains((CharSequence)"XposedBridge.jar"));//xposed
    return true;
}

第二个 无趣的getInstalledApplications()  return false;
private static boolean d(Context context) {
    for (ApplicationInfo applicationInfo : context.getPackageManager().getInstalledApplications(128)) {
        if (applicationInfo.packageName.equals((Object)"de.robv.android.xposed.installer")) {
            return true;
        }
        if (!applicationInfo.packageName.equals((Object)"com.saurik.substrate")) continue;
        return true;
    }
    return false;
}
第三个 我最喜欢的一个窝里龌龊的玩法 栈回溯 是很多调用者检测的常用手法 虽然这个样本不需要检测调用者

使用了StackTraceElement
主动抛出异常 来获取栈回溯exception.getStackTrace()
这个函数返回一个 StackTraceElement[]
获取这个 StackTraceElement[]长度exception.getStackTrace().length;
遍历包名stackTraceElement.getClassName()

知识点:
检测数组第二个元素是不是com.android.internal.os.ZygoteInit.main
因为如果存在Substrate框架hookdalvik.system.NativeStart.main
调用后会出现2次com.android.internal.os.ZygoteInit.main,而不是一次。

检测com.saurik.substrate.MS$2 的invoked方法
检测de.robv.android.xposed.XposedBridge 的handleHookedMethod方法


代码基本不需要注释 return false
private static boolean a() {
    try {
        throw new Exception("blah");//blah有点可爱
    }
    catch (Exception exception) {
        StackTraceElement[] arrstackTraceElement = exception.getStackTrace();
        int n2 = arrstackTraceElement.length;
        int n3 = 0;
        for (int i2 = 0; i2 < n2; ++i2) {
            StackTraceElement stackTraceElement = arrstackTraceElement[i2];
            int n4 = n3++;
            if (stackTraceElement.getClassName().equals((Object)"com.android.internal.os.ZygoteInit")) {
                n4 = n3;
                if (n3 == 2) {
                    return true;
                }
            }
                if (stackTraceElement.getClassName().equals((Object)"com.saurik.substrate.MS$2") && stackTraceElement.getMethodName().equals((Object)"invoked")) {
                return true;
            }
            if (stackTraceElement.getClassName().equals((Object)"de.robv.android.xposed.XposedBridge") && stackTraceElement.getMethodName().equals((Object)"handleHookedMethod")) {
                return true;
            }
            n3 = n4;
        }
        return false;
    }
}



总结

写博客真有意思啊 即使水平差强人意 但是逆向也很有意思呀hhhh

不过 只要这种强制运动的跑步软件还存在 就有学生抱着学习的目的进行友好的逆向学习

有空可能会做做其他软件的吧 不过其他软件好像没有这些若有似无的检测?



严重声明: 本文的目的只有一个,通过一个案例来分析现在应用逆向分析技巧,如果有人利用本文内容进行任何商业目的和非法牟利,带来的任何法律责任将由操作者本人承担,和本文作者没有任何关系,所以还是由衷的希望大家秉着技术学习的目的阅读此文,非常感谢!



你可能感兴趣的:(从破解运动世界校园学习模拟器检测与Xposed检测)