现在越来越多的高校开始使用一些所谓数字化校园的应用来管理学生工作,很多同学被要求安装手机应用来记录跑步状况,完成任务才可以通过体育期末考试。笔者通过本文,以“运动世界校园”为实例,将为你展示 Android 平台的反编译和使用 Xposed 框架进行 Hook 软件。
一、分析软件工作原理
以“运动世界校园”为例,通过使用此应用一段时间,可以总结出以下特点:
- 软件和后台使用API进行交互,在早期版本中API没有鉴权导致可以通过伪造请求来完成跑步,但是后期API迭代了好几个版本,目前认为现在的请求需要间隔一个合理的时间差(跑步时间),而且需要计算一个token用来鉴权,避免了伪造请求。
- 跑步开始和跑步完成时,会记录时间、设备序列号、IMEI等敏感信息,上传服务器。此操作用来记录手机设备唯一性,用来检测频繁换手机,一台设备上登陆不同账号的代跑行为。
- 使用百度地图API,通过GPS和由百度提供的LBS服务进行定位,软件中的配速,通过定位速度来计算。
- 软件中用来评价跑步是否合格的步频,通过加速度传感器和计步器来获取数据。(后期发现计步器部分的代码完全没有发挥作用)
- 软件检测Root但不会影响跑步,目前软件通过检测Xposed Installer来检测Xposed框架,当检测到存在时,会禁止开始跑步。(代码中还有通过JNI方式检测Xposed框架的代码但是笔者的版本中这段代码被注释)
二、HOW TODO
Xposed框架操作简单,模块写起来也简单得多,通过Hook系统传感器和GPS就可以达到模拟步频和定位的效果,但是软件目前会检测Xposed,所以需要反编译应用,把检测方法干掉。
三、反编译应用
我前后反编译了软件的两个版本,1.x和2.x功能变化不大,只是UI有一部分变化。通过EDEX对代码进行解包,发现应用套了壳。1.x版本使用360加固,2.x版本使用网易云盾(原网易云加固)对应用进行了加密。
通过查找相关资料,我在看雪论坛发现了一篇文章,文章说的很简略,其实原理很简单,是Dalvik虚拟机模式下基于Android运行时的内存dex文件的dump。这种方法比手撕汇编门槛低得多,比较适合菜的逆向升级人(比如我。
于是通过Hook Android系统的类加载DexClassLoader,获取到了应用真实的dex。(这连EDEX都省了)然后转换为jar拆包查看需要Hook的函数。通过搜索功能,很容易就能看到检测Xposed框架的方法。
四、编写Xposed模块
这地方不进行太多赘述,我主要的目的主要有两个,一个是干掉检测Xposed的代码,另一个是Hook系统加速度传感器的数据,模拟跑步时的数据波动。至于模拟GPS,暂时我选择了使用MockLocation的权限来做。(模拟位置没写完,我在使用的时候使用的是别人的模拟位置应用)
下面贴出主要代码:
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
if (loadPackageParam.packageName.equals("com.zjwh.android_wh_physicalfitness")) { XposedBridge.log("找到目标应用"); XposedHelpers.findAndHookMethod("com.stub.StubApp", loadPackageParam.classLoader, "getNewAppInstance", Context.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); XposedBridge.log("找到防反编译壳"); Context context = (Context) param.args[0]; ClassLoader classLoader = context.getClassLoader(); XposedHelpers.findAndHookMethod("com.zjwh.android_wh_physicalfitness.emulator.c", classLoader, "a", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); XposedBridge.log("成功劫持检测方法a()"); param.setResult(false); } }); XposedHelpers.findAndHookMethod("com.zjwh.android_wh_physicalfitness.emulator.c", classLoader, "b", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); XposedBridge.log("成功劫持检测方法b()"); param.setResult(false); } }); XposedHelpers.findAndHookMethod("com.zjwh.android_wh_physicalfitness.emulator.c", classLoader, "c", Context.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); XposedBridge.log("成功劫持检测方法c(Context)"); param.setResult(false); } }); //劫持传感器 final Class<?> sensorEL = XposedHelpers.findClass("android.hardware.SystemSensorManager$SensorEventQueue", classLoader); XposedBridge.hookAllMethods(sensorEL, "dispatchSensorEvent", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { int handle = (Integer) param.args[0]; Field field = param.thisObject.getClass().getDeclaredField("mSensorsEvents"); field.setAccessible(true); Sensor ss = ((SparseArray<SensorEvent>) field.get(param.thisObject)).get(handle).sensor; if (ss == null) { XposedBridge.log("未劫持到传感器"); return; } if (ss.getType() == Sensor.TYPE_ACCELEROMETER) { count += 1; //步频100算法 if (count % 3 == 0) { ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] * 100; ((float[]) param.args[1])[1] += (float) -10; } else if (count % 2 == 0) { ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] * 1000; ((float[]) param.args[1])[2] += (float) -20; ((float[]) param.args[1])[1] += (float) -5; } else { ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] * 10; ((float[]) param.args[1])[2] += (float) 20; ((float[]) param.args[1])[1] += (float) -15; } XposedBridge.log("传感器类型:" + ss.getType() + "加速度传感器,数据" + ((float[]) param.args[1])[0] + "," + ((float[]) param.args[1])[1] + "," + ((float[]) param.args[1])[2]); } if (ss.getType() == Sensor.TYPE_STEP_COUNTER || ss.getType() == Sensor.TYPE_STEP_DETECTOR) { if (10000 * count <= max) { ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] + 10000 * count; count += 1; } else { count = 0; } XposedBridge.log("传感器类型:" + ss.getType() + "计步器,数据" + ((float[]) param.args[1])[0]); } } }); } }); } |
目前该应用已在GitHub上开源,地址为LIznzn/FuckRunning,由于时间仓促,只实现了基础功能,给大家以学习和参考,同时也欢迎大佬来完善本项目。(我一直觉得步频算法好几把炫酷)
参考资料:
求教怎么使用 😥
求反编译后的代码