关于android:Android逆向rpc调用某安App的XAppToken签名函数

2次阅读

共计 5213 个字符,预计需要花费 14 分钟才能阅读完成。

1. 指标
在学习的过程中,会遇到有些算法比拟麻烦,没有方法间接还原。那咱们就另辟蹊径,不去剖析具体的算法实现。间接应用 rpc 的形式调用算法函数,本文章以某安 App 的 X -App-Token 签名函数为例。

2. 操作环境

  • mac 零碎
  • frida-dexdump:导出加固后 dex 文件
  • Charles:抓取 http 接口
  • 已 Root 安卓机:脱壳
  • Python3.8:实现 rpc 性能
  • Jadx:导出 dex 文件为源码
  • Android Studio:动态剖析

3. 流程
寻找切入点
通过 Charles 抓包获取到关键词为 X -App-Token,这也就是咱们的切入点:

动态剖析应用查壳工具发现该 apk 应用的是 360 加固,启动 App 后,应用 frida-dexdump 的 frida-dexdump -FU 命令导出 dex 文件:

因为 dex 文件较多,不不便查问,应用 jadx 把多个 dex 文件导出为源码:

import os​
for file in os.listdir(os.curdir):    
    if file.find(".dex") > 0:
        sh = 'jadx -j 1 -r -d ./ ./' + file        
        print(sh)        
os.system(sh)

将以上的 python 脚本放到 dex 同级目录,切换到 dex 目录,并执行以上脚本,执行实现后会生成 sources 文件夹,应用 Android Studio 关上该文件夹,全局搜寻 X -App-Token:

找到要害函数:

private final String[] m13135() {String str;        Locale locale = Locale.getDefault();        String valueOf = String.valueOf(Build.VERSION.SDK_INT);        String str2 = locale.getLanguage() + '-' + ((Object) locale.getCountry());        byte[] bytes = (this.f16167.m13205() + "; ; ;" + this.f16167.m13207() + ";" + ((Object) Build.MANUFACTURER) + ";" + ((Object) Build.BRAND) + ";" + ((Object) Build.MODEL) + ";" + ((Object) Build.DISPLAY) + ";" + ((Object) C4765.m13174().m12851())).getBytes(Charsets.UTF_8);        Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");        String encodeToString = Base64.encodeToString(bytes, 0);        Intrinsics.checkNotNullExpressionValue(encodeToString, "encodeToString(device.to…eArray(), Base64.DEFAULT)");        String sb = new StringBuilder(encodeToString).reverse().toString();        Intrinsics.checkNotNullExpressionValue(sb, "StringBuilder(device).reverse().toString()");        String replace = new Regex("\\r\\n|\\r|\\n|=").replace(sb, BuildConfig.FLAVOR);        String as = AuthUtils.getAS(this.f16166, replace);        if (C4765.m13166().m13307()) {str = "1";} else {str = C4765.m13166().m13300() ? "2" : "0";}        Intrinsics.checkNotNullExpressionValue(as, "appToken");        String r1 = this.f16167.m13203();        Intrinsics.checkNotNullExpressionValue(r1, "appMetadata.channel");        return new String[]{"User-Agent", this.f16170, "X-Requested-With", "XMLHttpRequest", "X-Sdk-Int", valueOf, "X-Sdk-Locale", str2, "X-App-Id", "com.coolapk.market", "X-App-Token", as, "X-App-Version", this.f16168, "X-App-Code", String.valueOf(this.f16169), "X-Api-Version", "12", "X-App-Device", replace, "X-Dark-Mode", str, "X-App-Channel", r1, "X-App-Mode", this.f16167.m13197().toString(), "X-App-Supported", String.valueOf(this.f16167.m13199())};    }

删除无关代码后:

private final String[] m13135() {byte[] bytes = (this.f16167.m13205() + "; ; ;" + this.f16167.m13207() + ";" + ((Object) Build.MANUFACTURER) + ";" + ((Object) Build.BRAND) + ";" + ((Object) Build.MODEL) + ";" + ((Object) Build.DISPLAY) + ";" + ((Object) C4765.m13174().m12851())).getBytes(Charsets.UTF_8);        String encodeToString = Base64.encodeToString(bytes, 0);        String sb = new StringBuilder(encodeToString).reverse().toString();        String replace = new Regex("\\r\\n|\\r|\\n|=").replace(sb, BuildConfig.FLAVOR);        String as = AuthUtils.getAS(this.f16166, replace);    }

由此可看出,X-App-Token 参数由 AuthUtils.getAS 办法生成,本篇文章的目标是通过 rpc 间接调用 getAS 函数,所以不会去具体分析 getAS 办法的实现。调用 getAS 办法的入参有两个,第一个是 context,第二个参数即为咱们须要拼接的参数。看源码可知,该参数由多个参数拼成,而后再 base64。具体查看每一个函数,整顿后的后果如下:

  • this.f16167.m13205():android_id
  • this.f16167.m13207():wifi 的 mac 地址
  • ((Object) Build.MANUFACTURER):硬件制造商
  • ((Object) Build.MODEL):零碎定制商
  • ((Object) Build.DISPLAY):显示屏参数
  • ((Object) C4765.m13174().m12851()):这个参数为空,咱们就暂且不论

你也能够间接应用命令 frida-trace -UF -j ‘!getBytes*’:

{onEnter(log, args, state) {log(`String.getBytes=${this.toString()}=`)    log(`String.getBytes(${args.map(JSON.stringify).join(',')})`);  },​  onLeave(log, retval, state) {if (retval !== undefined) {log(`<= ${JSON.stringify(retval)}`);    }  }}

获取到后果如下,依据后果反推参数值 e8e69e7384cb09c0; ; ; F4:F5:DB:24:A6:E1; Xiaomi; xiaomi; MI 5X; QL1515-tiffany-build-20171026203938; null
后果至此,咱们的 getAS 函数的入参曾经确定,接下来就是实现 RPCpython 源码如下:

import fridafrom flask import Flask, requestimport base64result = {}​​def on_message(message, data):    if message['type'] == 'send':        payload = message['payload']        if "###" in payload:            global result            array = payload.split("###")            result[array[0]] = array[1]        print(message['payload'])    elif message['type'] == 'error':        print(message['stack'])​​js_code = '''rpc.exports = {// 函数名 getAS    sign: function(params){Java.perform(function(){// 拿到 context 上下文            var currentApplication = Java.use('android.app.ActivityThread').currentApplication();            var context = currentApplication.getApplicationContext();​            // use 加载的类门路            var AuthUtils = Java.use('com.coolapk.market.util.AuthUtils');            var sign = AuthUtils.getAS(context, params);  // context,params            send(params+"###"+sign);        }    )    }};'''​process = frida.get_usb_device().attach('酷安')script = process.create_script(js_code)script.on('message', on_message)script.load()​app = Flask(__name__)​​@app.route('/get_sign')def get_sign():    device_id = request.args.get('device_id', '')    mac_address = request.args.get('mac_address','').upper()    manufacturer = request.args.get('manufacturer', '')    model = request.args.get('model','')    display = request.args.get('display', '')    params = f'{device_id}; ; ; {mac_address}; {manufacturer}; {model}; {display}; null'base_en = base64.encodebytes(params.encode('utf-8')).decode('utf-8')    base_en = base_en[::-1]    base_en = base_en.replace('\n','')    base_en = base_en.replace('\r', '')    base_en = base_en.replace('=','')    script.exports.sign(base_en)    sign = result[base_en]    return sign​​if __name__ == '__main__':    app.run()

运行 python 脚本后,浏览器调用 get_sign 办法即可获取到 X -App-Token

End

正文完
 0