某会员商店App的api接口分析
字数 3.1k / 15 min 读完 / 阅读1、目的
探索学习app的加解密机制,并通过api模拟调用的方式,发起业务请求。仅供学习。
2、工具准备
样本App版本:v5.0.80,v5.0.90
设备:Oppo R9s(Android7.1.1)+ MacOS Big Sur(Intel)
注入框架:xposed、frida(hluda 15.2.2)
反编译&其他:JEB、jadx、Charles
3、过程
大致分为抓包、脱壳、反编译、动态调试/加解密算法探索,构造模拟请求几个步骤,每个步骤都可能有不同的异常出现,本文主要记录在过程中的主体脉络和流程,过程中会附上关键代码。
3.1 抓包
首先尝试在手机上配置wifi代理,但Charles中无法看到相应的包记录。猜测是因为App屏蔽了网络代理,因此改用其他方式。手机上安装Drony,并开启手机全局网络代理(类型选择:socks5),代理地址指向Chares,此时就可以愉快的看到请求记录了。
如果是通过iOS抓包,直接通过小火箭抓包也是灰常方便。另外下载Drony App可能需要TZ,解决无法访问的问题。
在抓到的报文中,可以看到每次请求中,都包含了一些奇怪的header,比如t、spv、n、st,这些字段大概率与api接口的加密与签名有关。接下来,需要结合代码进一步分析。
3.2 脱壳&反编译
直接通过Xposed + 反射大师App,即可做到轻松脱壳,App未针对Xposed做检测。脱壳后得到7个dex文件,使用python脚本合并,将7个dex文件利用jadx全部反编译成Java文件到同一目录,即可直接翻阅App反编译后的源码。
import os, sys |
这时直接在反编译的结果中搜索关键词”spv”,却发现找不到。难道这些字段都隐藏到so中了,那就麻烦了。这时使用JEB再次反编译试试看,再次搜索”spv”,找到了。
这里,要提醒一下:针对反编译,同样的dex文件,用不同的反编译工具,结果也会不一样,可读性差异很多,因此当使用一种工具反编译失败的话,可以尝试用不同的工具,比如,通用一段代码的反编译结果,使用jadx时,提示反编译失败,如下:
/* JADX WARN: Code restructure failed: missing block: B:61:0x017a, code lost: |
但是使用JEB时,结果则基本可用,如下:
public ad intercept(w.a arg19) { |
3.3 动态调试分析
拿到反编译源码后,接下来就需要结合frida动态分析代码调用链,找到api调用的核心算法逻辑并加以验证。
在App最新版本v5.0.90上,连接frida客户端。frida注入失败。随后换了hluda、xcube等方案均以失败告终,看了下app的加固方案,使用的腾讯的加固方案,对应的壳文件是libshell-super.cn.xxxxclub.app.so
,尝试绕过壳的反注入逻辑,也没有效果。
这时偶然看到旧版本的app使用的壳文件是libshell-super.2019.so
,灵光一闪,感觉旧版本的app上应该有机会,于是下载安装v5.0.80,frida注入成功了。app上开启了强制更新,于是在Charles上hook重写了app检查更新接口的返回结果,让app检查不到新版本,app仍然可以继续使用(后续有风险,历史接口可能下线)。
旧版本app上也可以使用frida工具集:Objection,通过调试和代码比对,基本确认了核心的算法签名逻辑位置:
签名的传入参数为分别为:t
- 时间戳、data_json
- 按json序列化后的业务对象参数、n
- 去掉”-“符号后的uuid(32位字符串)、auth_token
- 登录后用户令牌,按照如下规则排列所得:
"{t}{data_json}{n}{auth_token}" |
返回字符串即为签名结果 - st
该签名算法有使用native方法,具体算法逻辑应该需要反汇编相应的so文件了。签名规则已经基本明确了,直接调用java层方法,走RPC调用即可得到我们想要的结果。偷懒了,就不去深挖汇编代码了,笔者也不确认一定能找到结果-_-||
3.4 RPC调用
1)创建js文件app_inject.js
,声明rpc接口
var g_instance = null; |
2)创建frida客户端,声明rpc调用。文件名:frida_client.py
import frida |
3)构造参数,发起RPC调用。文件名:demo.py
# -*- coding: utf-8 -*- |
再看看结果,已经成功得到响应数据了。大功告成!
3.5 踩坑说明
在执行frida js注入时,Java.enumerateClassLoaders()
仅支持Android 7.0及以上系统,若使用低版本的Android系统,如Android 6.1,则需要使用send(),进行消息异步通知。当采用异步通知时,在Python客户端的编码中,需要定义消息回调函数,同时将异步调用封装成同步调用,方便上游调用使用。对应的js代码和python代码如下:
app_inject_for_android_6.0.js
:
var g_instance = null; |
frida_client_for_android_6.0.js
:
import uuid |
3.6 备注
通过测试验证,可以发现两个版本v5.0.80,v5.0.90的签名算法是一致的。因此可以直接利用v5.0.80做签名即可。
打完收工!