本文案例是对美图秀秀app的sig参数和client_session参数生成分析。
案例环境:美图版本9.3.5 、工具Jadx、夜神安卓7、Frida
APP包名: com.mt.mtxx.mtxx
@toc
接口分析
抓包,搜索接口和参数如下:
url = "https://api.xiuxiu.meitu.com/v2/search/feeds.json"
params = {
"client_timestamp": "1639113315051",
"client_timezone": "GMT+8",
"is_gdpr": "0",
"client_channel_id": "baidu",
"client_model": "MI 9",
"cpuModel": "shamu",
"client_brand": "Android",
"resolution": "900*1600",
"client_id": "1089867602",
"sigVersion": "1.3",
"runtimeMaxMemory": "192",
"ad_sdk_version": "5.7.20",
"feed_sort": "normal",
"is_test": "0",
"keyword": "韩国证件照",
"client_network": "wifi",
"ram": "3482",
"cpuVendor": "Other",
"count": "12",
"version": "9.3.5.0",
"search_type": "0",
"is_basic": "0",
"country_code": "CN",
"app_hot_start_times": "1",
"attachFlag": "409",
"client_session": "8b5f0ef740d6d22fd3a5c5e7cd069e45",
"is64Bit": "0",
"client_is_root": "2",
"client_operator": "CHINA MOBILE",
"gid": "2638425610",
"mac": "08:00:27:97:F2:76",
"client_language": "zh_CN",
"cn_switch_on": "0",
"sig": "50a300ed42c1202deb5439a77c3dcc70",
"sigTime": "1639113315054",
"client_os": "5.1.1",
"lang": "1",
"is_privacy": "0",
"user_agent": "mtxx-9350-Xiaomi-MI 9-android-5.1.1-9d212252",
"is_ohos": "0",
"sigEnv": "0",
"appAreaType": "3",
"personality_not_recommend": "0",
"community_version": "2.0.0",
"android_sdk_int": "22",
"ad_personality_not_recommend": "0",
"imei": "863254944222826",
"is_device_support_64": "0",
"android_id": "e3be29a75f28a5c2"
}
经过分析, 动态参数有 client_timestamp、client_session、sig、sigTime。
client_timestamp,sigTime是时间戳,client_session、sig 应该是加密参数。
接口分析完成,接下来进行源码分析。
源码分析
查壳,尬住,查出来是阿里聚安全。
这个壳没啥好脱法,所以不脱了,直接丢jadx中反编译。
8G内存吃满了,没办法我修改成了12G,如果你没这么多内存就换其他工具,Apktools或者JEB反编译。
内存加载完成之后,占了8.2G。
开始静态搜索加密参数,和sig相关的搜索词有很多,"sig"、sig;、"sig、=sig 等等。
经过多次查看,最后找到了比较符合的位置。
进去之后,观察文件内容,有很多SigEntity重载方法。
那如何知道生成sig的是哪一个方法呢,这里最好是hook查看。
Frida hook
启动frida
不知道参数名该怎么写的话,就随便写两个,然后看frida的报错提示就可以。
{
'type': 'error', 'description': "Error: generatorSig(): specified argument types do not match any of:
overload('java.lang.String', '[Ljava.lang.String;', 'java.lang.String')
overload('java.lang.String', '[Ljava.lang.String;', 'java.lang.String', 'java.lang.Object')",
}
这是报错中给出的参数类型,直接拿来用。每个都测一下就知道哪个是生成sig的方法了。
Frida HOOK代码如下:
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode_hook = """
Java.perform(
function(){
console.log("1. start hook");
var ba = Java.use("com.meitu.secret.SigEntity");
if (ba != undefined) {
console.log("2. find class");
ba.generatorSig.overload('java.lang.String', '[Ljava.lang.String;', 'java.lang.String', 'java.lang.Object').implementation = function (a1,a2,a3,a4) {
console.log("3. find function");
console.log(a1);
console.log(a2);
console.log(a3);
console.log(a4);
var res = ba.generatorSig(a1,a2,a3,a4);
console.log("计算result:" + res);
return res;
}
}
}
)
"""
process = frida.get_usb_device().attach('com.mt.mtxx.mtxx')
script = process.create_script(jscode_hook)
script.on('message', on_message)
print('[*] Hook Start Running')
script.load()
sys.stdin.read()
触发请求,查看frida打印内容。
- 第一个参数是:search/feeds.json
- 第二个参数是:提交的参数、包括设备环境
- 第三个参数是:6184556633574670337
- 第四个参数是:com.meitu.remote.hotfix.app.RemoteHotfixApplication@3fbe8c4
经过多次测试,除了提交的参数,比如搜索词之外,其他都是定值。 需要注意的是第四个参数是Object对象。
根据代码逻辑判断,只有 obj符合Context类型时,才会生成sig,所以可以笃定obj是一个Context对象。
攒够了参数之后,就可以根据方法去编写加密算法了,但是查看了一下加密函数的逻辑,调用了native中的方法。
秉承着不分析so文件的思路,此时有两种解决方法。通过unidbg去模拟调用so中的方法;或者依旧通过frida来RPC导出函数生成sig。
sig生成
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode_hook_2 = """
Java.perform(
function(){
console.log('Hook Start !');
var SigEntity = Java.use('com.meitu.secret.SigEntity');
var BaseApplication = Java.use('com.meitu.library.application.BaseApplication');
var str1 = "search/feeds.json";
var strArr = ["1639127762948","CMCC","GMT+8","0","2638419901","baidu","LIO-AN00","kirin990","HUAWEI","900*1600","zh_CN","08:00:27:CF:AE:71","1089867602","0","192","5.7.20","7.1.2","hot","0","闪闪","1","0","wifi","mtxx-9350-HUAWEI-LIO-AN00-android-7.1.2-879a8b83","3038","华为","12","0","0","1","0","9.3.5.0","0","0","2.0.0","CN","1","409","25","0","8c4f7fae2f81d530ecc10f372b9ce0f1","0","863064425644392","0","8fbf78cea9289fd1","2"]
var str3 = "6184556633574670337";
var content = SigEntity.getContext();
var signResult = SigEntity.generatorSig(str1, strArr, str3, content);
console.log("sig: ", signResult.sig.value)
console.log("sigTime: ", signResult.sigTime.value)
console.log("sigVersion: ", signResult.sigVersion.value)
}
)
"""
process = frida.get_usb_device().attach('com.mt.mtxx.mtxx')
script = process.create_script(jscode_hook_2)
script.on('message', on_message)
print('[*] Hook Start Running')
script.load()
sys.stdin.read()
执行结果:
Frida Flask PRC
正常的RPC导出代码:
import frida
def on_message(message, data):
print("[%s] => %s" % (message, data))
def start_hook():
session = frida.get_usb_device().attach('com.mt.mtxx.mtxx')
print("[*] start hook")
js_code = '''
rpc.exports = {
"a": function (kw) {
var ret = {};
Java.perform(function(){
var SigEntity = Java.use("com.meitu.secret.SigEntity");
var BaseApplication = Java.use('com.meitu.library.application.BaseApplication');
var str1 = "search/feeds.json";
var str3 = "6184556633574670337";
var content = BaseApplication.getApplication();
var result = SigEntity.generatorSig(str1, kw, str3, content);
ret["result"]=result.sig.value;
console.log("[*] Sig:",result.sig.value);
}
)
return ret;
}
}
'''
script = session.create_script(js_code)
script.on('message', on_message)
script.load()
return script
kw = '闪闪'
params = ['1639127762948', 'CMCC', 'GMT+8', '0', '2638419901', 'baidu', 'LIO-AN00', 'kirin990', 'HUAWEI', '900*1600','zh_CN', '08:00:27:CF:AE:71', '1089867602', '0', '192', '5.7.20', '7.1.2', 'hot', '0', kw, '1', '0', 'wifi','mtxx-9350-HUAWEI-LIO-AN00-android-7.1.2-879a8b83', '3038', '华为', '12', '0', '0', '1', '0', '9.3.5.0', '0','0', '2.0.0', 'CN', '1', '409', '25', '0', '8c4f7fae2f81d530ecc10f372b9ce0f1', '0', '863064425644392', '0','8fbf78cea9289fd1', '2']
result = start_hook().exports.a(params)
print(result)
通过Flask配合Frida实现RPC远程调用。
import frida
from flask import Flask, jsonify, request
app = Flask(__name__)
def on_message(message, data):
print("[%s] => %s" % (message, data))
def start_hook():
session = frida.get_usb_device().attach('com.mt.mtxx.mtxx')
js_code = '''
rpc.exports = {
"a": function (kw) {
var ret = {};
Java.perform(function(){
var SigEntity = Java.use("com.meitu.secret.SigEntity");
var BaseApplication = Java.use('com.meitu.library.application.BaseApplication');
var str1 = "search/feeds.json";
var str3 = "6184556633574670337";
var content = BaseApplication.getApplication();
var result = SigEntity.generatorSig(str1, kw, str3, content);
ret["result"]=result.sig.value;
console.log("[*] Sig:",result.sig.value);
}
)
return ret;
}
}
'''
script = session.create_script(js_code)
script.on('message', on_message)
script.load()
return script
@app.route("/hook")
def search():
kw = request.args.get("kw")
params = ['1639127762948', 'CMCC', 'GMT+8', '0', '2638419901', 'baidu', 'LIO-AN00', 'kirin990', 'HUAWEI', '900*1600','zh_CN', '08:00:27:CF:AE:71', '1089867602', '0', '192', '5.7.20', '7.1.2', 'hot', '0', kw, '1', '0', 'wifi','mtxx-9350-HUAWEI-LIO-AN00-android-7.1.2-879a8b83', '3038', '华为', '12', '0', '0', '1', '0', '9.3.5.0', '0','0', '2.0.0', 'CN', '1', '409', '25', '0', '8c4f7fae2f81d530ecc10f372b9ce0f1', '0', '863064425644392', '0','8fbf78cea9289fd1', '2']
result = start_hook().exports.a(params)
return jsonify({'result':result})
if __name__ == '__main__':
app.run()
示例:
client_session分析
接着再看一下 client_session
点进去
m77880a方法
m74573a 和 m74574a ,这是一个md5算法。
所以 client_session的生成很简单。