-
-
[原创]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,这也就是我们的切入点:
静态分析
使用查壳工具发现该apk使用的是360加固,启动App后,使用frida-dexdump的frida-dexdump -FU
命令导出dex文件:
由于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:
找到关键函数:
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