本帖最后由 ostara 于 2023-10-29 16:59 编辑
学了一段时间的XPosed,发现XPosed真的好强,只要技术强,什么操作都能实现... 这次主要记录一下我对这款应用的逆向思路 使用MT管理器检查apk的加壳情况
发现是某数字的免费版本 直接使用frida-dexdump 脱下来后备用
- 进入应用之后会发现里边含有
登录 会员 等模块 - 我们先不管登录的部分,先检查
会员 的使用场景,一般在会员 的使用场景或者显示场景中都会有检查是否是VIP 的业务逻辑,根据这个来加载显示不同的资源
通过对应用的检查发现在添加虚拟机设备的时候用到了会员 权限
同时弹出一个对话框,应用也贴心的告诉我们VIP 的使用场景 打开算法助手,算法助手对应用进行了常见功能的Hook,最重要的是支持免费加固的Hook 将这3项勾选
重复1、2步,在日志中检查OnClick和弹窗是否有用的信息 很幸运,在这一步就获取有关的逻辑
且函数名称并没有被混淆,能够从调用堆栈读出以下逻辑:点击下载按钮->检查是否VIP->用户没有登录->显示购买VIP的弹窗 打开jadx将脱壳后的dex文件载入,搜索checkVip
选择第一个函数,发现无有用信息,继续进入checkVip 函数 在此函数中发现getUserConf,即获取用户的配置 通过对此函数的阅读,得出此函数的返回值为UserBean 然后检查
UserBean 很明显,有关VIP的数据都在此,使用XPosed来修改这些成员变量,即可达到对显示UI的修改,即使服务器对这些数据有校验也不影响,至少在UI层面已经成功了
使用抓包工具检查网络请求,当点击底部的导航栏的时候,应用会发送网络请求
通过检查getInfo ,获取一个Post请求的链接 对此链接进行引用查询,发现有关用户的逻辑 阅读此函数,网络请求库可能为Retrofit,当请求成功的时候会将用户的信息保存起来并移除广告?同样也能得到UserBean ,这个关键的信息 - 通过对应用的分析可以得出一个关键的信息
getUserConf 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); } });
- 可以看到jadx为我们一键生成了有关的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 } });
- 获取到正确的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); } });
Hook完成后,能够发现nickName 是正确的,能够对应上UI的显示 接下来只需要对循环里的数据进行判断赋值,然后返回即可
//修改名称,其他自行测试 for (Field field : UserBean.getClass().getDeclaredFields()) { ...... if (name.equals("nickName")) { field.set(UserBean, "测试文字"); } else if(......) { ...... } } param.setResult(UserBean);//设置返回值,替换掉param.getResult()获取的
UI显示和下载功能 获取到VIP 后,发现还有一个插件下载的逻辑没有效果 当点击Root或者XPosed的时候,会提示加载失败
但是点击谷歌服务的时候却有效果,猜测是网络请求 打开抓包工具,通过对两者的对比,发现是其中少了一些数据,所以才会加载失败
在jadx中搜索getPluginUrl ,通过阅读此函数发现有2个匿名函数,failure 和success
使用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
- 通过对这两者的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}}]
- 可以明显发现其中确实是少了一些数据,接下来只需要补齐下载链接即可,但是如何获取这个链接呢?
充值VIP获取其中的链接扫描网站链接?或者找到一个函数获取?- 猜测
- 上面2点显然不是我能够解决的,2333,那就通过对链接的猜测吧,根据能够下载的谷歌服务链接来看,root和xposed可能为:
http://xxx/xxx/plugin/android71root_plugin-64bit.zip' http://xxx/xxx/plugin/android71xposed_plugin-64bit.zip' - 通过一系列猜测,得出来正确的下载链接,MD5的话只需要在终端输入
md5 file 即可得到
- 阅读函数,检查参数发现具体的链接在
vu 中的data ,但是返回类型是T,这就比较麻烦了
- 这里采用一种比较麻烦的方法来修改:
- Hook
vu 类的m49633 函数获取返回结果 - 遍历返回结果的
Fields - 找到含有
systemPluginResult 的field - 使用
field.getType() 获取到Class<?> jo4 - 使用
jo4.newInstance() 创建一个实例ret - 再次遍历
ret.getDeclaredFields() - 根据
pluginMd5 和pluginUrl 分别赋值到ret 中 - 最后使用
field.set(data, ret); 赋值即可 - 具体代码截图,
pluginMd5 和pluginUrl 是我获取的正确链接,就不公布了
注:若转载请注明大神论坛来源(本贴地址)与作者信息。
|