本文仅研究了某麦app的购票接口,不涉及破盾方法。

本文仅供研究学习使用,请勿用于任何商业和非法用途。否则后果自负。

在之前的文章中我们详细分析了大麦的购票协议接口,并使用了 Python + Frida RPC 的方式成功实现了购票。本文我们尝试将其改为用 Xposed 来实现。

环境

设备:pixel 5(Android11已root)

app平台:Android

app版本:8.10.9

工具:

抓包:Postern + Charles

动态静态分析:jadx、frida

LSPosed版本:1.9.2

Magisk版本:26.1

流程拆解

先来分解一下我们之前获取订单详情的 frida hook 流程:

构建MtopContext对象作为参数主动调用buildParams获取加密参数

  1. 构建MtopContext对象

    ​ 构造 MtopRequest 对象

    ​ 构造 MtopBusiness 对象

    ​ 构造 MtopContext 对象

  2. 主动调用buildParams

  3. 主动调用CookieManager.d获取Cookie

  4. 向服务器发送请求并获取返回值,从中提取 signKey用于订单构建

androidStudio 环境配置

  1. 创建一个空项目

  2. 改一下项目名称,这里我们使用java写

  3. 拷贝 XposedBridgeApi.jar 到新建工程的libs目录

    • 没有 libs 目录的话就在这里新建一个
  4. 修改app目录下的 build.gradle 文件,在AndroidManifest.xml 中增加Xposed相关内容

  5. 新建 assets 文件夹,然后在 assets 目录下新建文件 xposed_init,在里面写上 hook 类的完整路径

开始分析

获取 realClassLoader 原理

Xposed hook的时机非常早,因为首先拿到的是壳的 classLoader ,而非脱壳后真正的 realClassLoader。所以在有壳的情况下直接hook目标函数是无法找到目标函数的。

那么我们得先拿到脱壳后的 realClassLoader

加壳app的启动流程

  1. 启动时只加载壳自己的 StubApplication(不是业务代码)

  2. 壳在 attachBaseContext()onCreate() 里:

    • 加载加密的 dex

    • 解密原始 dex

    • 使用 DexClassLoader / PathClassLoader 加载业务代码

    • 有时还会用反射把真实的 Application 替换进去

最后才进入真实代码逻辑

所以,我们要hook attachBaseContext() 或者 onCreate()

方法名 原因
attachBaseContext(Context) 加壳 App 最早可执行逻辑,通常在此加载原始 dex
onCreate() 有些壳把 dex 加载延后到这一步
两者之后 ClassLoader 已经被替换或补充,可以安全用来加载真实类 ✅

确定 hook 入口

查看壳的入口,找到attachBaseContext(Context)或者onCreate()方法的地址

  • 查看 AndroidManifest.xml 从 application 中可以找到壳的入口 com.ali.mobisecenhance.ld.StubApplication
  • 进来之后可以看到这个类本身没有attachBaseContext(Context)或者onCreate(),那么跟进它继承的父类看看
  • 进来之后可以看到这两个方法
  • 一般来说我们优先选择attachBaseContext()onCreate() 可作为补充

获取 realClassLoader

  • 这里我们通过 hook attachBaseContext()成功的拿到了脱壳后的 realClassLoader
  • 拿到了realClassLoader之后就可以编写代码继续之后的流程了

代码编写

按照之前的拆解流程,构建出 MtopContext 对象之后就可以调用 buildParams()方法获取到加密参数了

构建MtopContext对象

构造 MtopRequest 对象

  • 构造成功说明我们成功的加载了类对象,访问类对象的方法也没有问题。

  • 访问的时候需要注意一下是静态的还是非静态的,分别使用callStaticMethod()callMethod()

构造 MtopBusiness 对象

  • 这里调用静态工厂方法 build() 拿到实例

构造 MtopContext 对象并调用 buildParams()

  • 执行没有报错,但是调用buildParams()的返回值为空
  • 经过尝试可能是因为attachBaseContext()执行完成后业务代码还没有加载完毕,换为hook onCreate()可以正常执行返回加密数据

更换 hook 时机为 onCreate()

  • 现在可以正常打印出加密参数

主动调用CookieManager.d获取Cookie

向服务器发送请求并获取返回值

接下来手动用 HttpURLConnection 发起 GET 请求,格式跟之前 python 中保持相同即可

  • 需要注意,因为 Android 默认是不允许在主线程进行网络 I/O 的。解决办法是把网络请求放到子线程中执行。

提取返回值中的 signKey

  • 提取到之后保存下来,用于构建订单使用