首页
社区
课程
招聘
[原创]rpc调用某安App的X-App-Token签名函数
发表于: 2022-7-29 19:12 5230

[原创]rpc调用某安App的X-App-Token签名函数

2022-7-29 19:12
5230

阅读此文档的过程中遇到任何问题,请关注公众号【移动端Android和iOS开发技术分享】或加QQ群【309580013

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,这也就是我们的切入点:

 

image-20220729145855104

静态分析

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

 

image-20220729151122154

 

由于dex文件较多,不方便查询,使用jadx把多个dex文件导出为源码:

1
2
3
4
5
6
7
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:

 

image-20220729152147309

 

找到关键函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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())};
    }

删除无关代码后:

1
2
3
4
5
6
7
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*':

1
2
3
4
5
6
7
8
9
10
11
12
{
  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函数的入参已经确定,接下来就是实现RPC

 

python源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import frida
from flask import Flask, request
import base64
result = {}
 
 
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

 

image-20220729190018280

End

阅读此文档的过程中遇到任何问题,请关注公众号【移动端Android和iOS开发技术分享】或加QQ群【309580013

IMG_4048


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//