导语
学了一段时间的XPosed,发现XPosed真的好强,只要技术强,什么操作都能实现...
这次主要记录一下我对这款应用的逆向思路
apk检查
1) 使用MT管理器检查apk的加壳情况
2) 发现是某数字的免费版本
3) 直接使用frida-dexdump
4) 脱下来后备用
应用分析
1) 进入应用之后会发现里边含有登录
会员
等模块
2) 我们先不管登录的部分,先检查会员
的使用场景,一般在会员
的使用场景或者显示场景中都会有检查是否是VIP
的业务逻辑,根据这个来加载显示不同的资源
会员
分析
1) 通过对应用的检查发现在添加虚拟机设备的时候用到了会员
权限
2) 同时弹出一个对话框,应用也贴心的告诉我们VIP
的使用场景
3) 打开算法助手,算法助手对应用进行了常见功能的Hook,最重要的是支持免费加固的Hook
4) 将这3项勾选
5) 重复1、2步,在日志中检查OnClick和弹窗是否有用的信息
6) 很幸运,在这一步就获取有关的逻辑
7) 且函数名称并没有被混淆,能够从调用堆栈读出以下逻辑:点击下载按钮->检查是否VIP->用户没有登录->显示购买VIP的弹窗
8) 打开jadx将脱壳后的dex文件载入,搜索checkVip
9) 选择第一个函数,发现无有用信息,继续进入checkVip
函数
10) 在此函数中发现getUserConf,即获取用户的配置
11) 通过对此函数的阅读,得出此函数的返回值为UserBean
12) 然后检查UserBean
13) 很明显,有关VIP的数据都在此,使用XPosed来修改这些成员变量,即可达到对显示UI的修改,即使服务器对这些数据有校验也不影响,至少在UI层面已经成功了
抓包分析
1) 使用抓包工具检查网络请求,当点击底部的导航栏的时候,应用会发送网络请求
2) 通过检查getInfo
,获取一个Post请求的链接
3) 对此链接进行引用查询,发现有关用户的逻辑
4) 阅读此函数,网络请求库可能为Retrofit,当请求成功的时候会将用户的信息保存起来并移除广告?同样也能得到UserBean
,这个关键的信息
编写插件
思路
1) 通过对应用的分析可以得出一个关键的信息getUserConf
2) getUserConf
函数右键->复制为XPosed片段
XposedHelpers.findAndHookMethod("com.vmos.pro.account.AccountHelper", classLoader, "getUserConf", 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);
}
});
3) 可以看到jadx为我们一键生成了有关的Hook代码,但是这样就行了吗?我可以告诉你,不行。别忘了,这是一个加壳的应用,即使它是一款免费的加壳
加壳应用Hook
通过对网上公开资料的查询,发现即使应用加固也需要在运行时进行还原修复,使用jadx打开加固的apk文件,找到attachBaseContext
XposedHelpers.findAndHookMethod("com.stub.StubApp", param.classLoader, "attachBaseContext", android.content.Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Context context = (Context) param.args[0];//获取运行时的Context
ClassLoader classLoader = context.getClassLoader()//获取真正的ClassLoader
//在此添加Hook VIP等操作,使用classLoader
}
});
继续编写
1) 获取到正确的ClassLoader后,对getUserConf
函数的返回值进行遍历
XposedHelpers.findAndHookMethod("com.vmos.pro.account.AccountHelper", classLoader, "getUserConf", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Object UserBean = param.getResult();
for (Field field : UserBean.getClass().getDeclaredFields()) {
//设置可访问, 极其重要, 不然会崩溃
field.setAccessible(true);
//使用反射来获取运行时的数据
var name = field.getName();
var type = field.getType();
var value = field.get(UserBean);
Log.i("HookTag", name + ": " + value);
}
super.afterHookedMethod(param);
}
});
2) Hook完成后,能够发现nickName
是正确的,能够对应上UI的显示
3) 接下来只需要对循环里的数据进行判断赋值,然后返回即可
//修改名称,其他自行测试
for (Field field : UserBean.getClass().getDeclaredFields()) {
......
if (name.equals("nickName")) {
field.set(UserBean, "测试文字");
} else if(......) {
......
}
}
param.setResult(UserBean);//设置返回值,替换掉param.getResult()获取的
4) UI显示和下载功能
插件下载破解
获取到VIP
后,发现还有一个插件下载的逻辑没有效果
下载逻辑分析
1) 当点击Root或者XPosed的时候,会提示加载失败
2) 但是点击谷歌服务的时候却有效果,猜测是网络请求
3) 打开抓包工具,通过对两者的对比,发现是其中少了一些数据,所以才会加载失败
4) 在jadx中搜索getPluginUrl
,通过阅读此函数发现有2个匿名函数,failure
和success
5) 使用jadx默认给我们的参数Hook不太行,这时候需要使用其他函数来获取vu
//vu<jo4>.class 无法获取
//使用loadClass来获取,在参数中填写vu即可
Class<?> vu = classLoader.loadClass("vu");
XposedHelpers.findAndHookMethod("com.vmos.pro.activities.main.fragments.PluginHelper$getPluginDownloadBean$2$1", classLoader, "success", vu, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Log.i("HookTag", "success: " + Arrays.toString(param.args));
super.beforeHookedMethod(param);
}
});
//或者使用XposedBridge.hookAllMethods
6) 通过对这两者的Hook,当点击Root
插件按钮时会进入success
且参数为:
谷歌服务按钮:success: [CommonResult{code=0, msg='OK', data=RespPlugin{systemPluginResult=RespPluginInfo{pluginUrl='http://xxx/xxx/plugin/android71gp_plugin-64bit3.zip', pluginMd5='62c38ec6b509e546e9fe9566969f7c49', version=0}}}]
Root按钮:success: [CommonResult{code=0, msg='OK', data=RespPlugin{systemPluginResult=null}}]
7) 可以明显发现其中确实是少了一些数据,接下来只需要补齐下载链接即可,但是如何获取这个链接呢?