抓包
postern+chales
https://mapi.appvipshop.com/vips-mobile/rest/shopping/product/module/list/v2
OAuth Authentication | |
---|
api_sign | 851720db2410ed92d3628f1739c00e1374d26c02 |
分析
jadx打开唯品会7.45.6.apk,搜索api_sign
进入apiSign
1 2 3 4 5 6
| public static String apiSign(Context context, TreeMap<String, String> treeMap, String str) throws Exception { if (context == null) { context = VCSPCommonsConfig.getContext(); } return VCSPSecurityConfig.getMapParamsSign(context, treeMap, str, false); }
|
进入getMapParamsSign
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
| public static String getMapParamsSign(Context context, TreeMap<String, String> treeMap, String str, boolean z) { String str2 = null; if (treeMap == null) { return null; } boolean z2 = false; Set<Map.Entry<String, String>> entrySet = treeMap.entrySet(); if (entrySet != null) { Iterator<Map.Entry<String, String>> it = entrySet.iterator(); while (true) { if (it == null || !it.hasNext()) { break; } Map.Entry<String, String> next = it.next(); if (next != null && next.getKey() != null && ApiConfig.USER_TOKEN.equals(next.getKey()) && !TextUtils.isEmpty(next.getValue())) { z2 = true; break; } } } if (z2) { if (TextUtils.isEmpty(str)) { str = VCSPCommonsConfig.getTokenSecret(); } str2 = str; } return getSignHash(context, treeMap, str2, z); }
|
一顿操作不过是为了生成str2作为参数传入getSignHash
1 2 3 4 5 6 7 8
| public static String getSignHash(Context context, Map<String, String> map, String str, boolean z) { try { return gs(context.getApplicationContext(), map, str, z); } catch (Throwable th) { VCSPMyLog.error(clazz, th); return "error! params invalid"; } }
|
调用了gs函数
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
| private static String gs(Context context, Map<String, String> map, String str, boolean z) { try { if (clazz == null || object == null) { synchronized (lock) { initInstance(); } } if (gsMethod == null) { gsMethod = clazz.getMethod("gs", Context.class, Map.class, String.class, Boolean.TYPE); } return (String) gsMethod.invoke(object, context, map, str, Boolean.valueOf(z)); } catch (Exception e) { e.printStackTrace(); return "Exception gs: " + e.getMessage(); } catch (Throwable th) { th.printStackTrace(); return "Throwable gs: " + th.getMessage(); } } private static void initInstance() { if (clazz == null || object == null) { try { Class<?> cls = Class.forName("com.vip.vcsp.KeyInfo"); clazz = cls; object = cls.newInstance(); } catch (Exception e) { e.printStackTrace(); } } }
|
gs中通过反射获取KeyInfo类,并反射调用了该类的gs方法,进入KeyInfo类
1 2 3 4 5 6 7 8 9
| private static final String LibName = "keyinfo"; public static String gs(Context context, Map<String, String> map, String str, boolean z) { try { return gsNav(context, map, str, z); } catch (Throwable th) { return "KI gs: " + th.getMessage(); } } private static native String gsNav(Context context, Map<String, String> map, String str, boolean z);
|
gsNav位于libkeyinfo.so中,想必就是api_sign生成的位置,IDA打开libkeyinfo.so
F5反汇编,想必主要加密逻辑在 j_Functions_gs
函数里
1 2 3 4 5 6 7 8 9 10 11
| int __fastcall Java_com_vip_vcsp_KeyInfo_gsNav(int a1, int a2, int a3, int a4, int a5, int a6) { int v9; // r5
if ( j_Utils_ima() ) v9 = j_Functions_gs(a1, a2, a4, a5, a6); else v9 = 0; j_Utils_checkJniException(a1); return v9; }
|
进入j_Functions_gs后,上面对params的map做处理
可疑函数j_getByteHash,看这逻辑要么返回0,要么返回((int (__fastcall *)(JNIEnv *))(*a1)->NewStringUTF)(a1)
进入j_getByteHash,看着像是调用SHA1算法返回字符串,固定长度为160位的消息摘要,40个字符长度,和api_sign长度一致
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
| char *__fastcall getByteHash(int a1, int a2, int a3, int a4, char *a5) { char *v7; // r4 int i; // r5 int v9; // r2 _QWORD v11[8]; // [sp+0h] [bp-D8h] BYREF _DWORD v12[26]; // [sp+44h] [bp-94h] BYREF
if ( !a3 ) return 0; v7 = a5; j_SHA1Reset(v12); j_SHA1Input(v12, a3, a4); if ( j_SHA1Result(v12) ) { for ( i = 0; i != 5; ++i ) { v9 = v12[i]; v11[6] = 0LL; v11[7] = 0LL; v11[4] = 0LL; v11[5] = 0LL; v11[0] = 0LL; v11[1] = 0LL; v11[2] = 0LL; v11[3] = 0LL; sprintf((char *)v11, "%08x", v9); strcat(a5, (const char *)v11); } } return v7; }
|
frida
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function main() { Java.perform(function () { // 导出jni函数 var native_func = Module.findExportByName( "libkeyinfo.so", "getByteHash" );
Interceptor.attach(native_func, { onEnter: function (args) { // getByteHash(int a1, int a2, int a3, int a4, char *a5) // getByteHash(JNIEnv *env, jobject a2, int a3, int a4, char *a5) console.log('args[2]: ', Memory.readCString(args[2])); }, onLeave: function (return_val) { console.log('return_val: ', Memory.readCString(return_val)); } }); }); }
setImmediate(main)
|
一次api_sign的生成调用了两次非魔改后的sha1算法,两次前面都加了盐a84c5883206309ad076deea939e850dc&8cf90dfd9a89070a9e8653d92f57349b
1 2 3 4
| args[2]: a84c5883206309ad076deea939e850dc&8cf90dfd9a89070a9e8653d92f57349bapi_key=23e7f28019e8407b98b84cd05b5aef2c&app_name=shop_android&app_version=7.45.6&channel_flag=0_1&client=android&client_type=android&context={"615":"1","872":"1"}&darkmode=0&deeplink_cps=&extParams={"floatwin":"1","preheatTipsVer":"4","exclusivePrice":"1","stdSizeVids":"","showSellPoint":"3","mclabel":"1","cmpStyle":"1","ic2label":"1","reco":"1","vreimg":"1","couponVer":"v2","live":"1"}&fdc_area_id=103102105112&mars_cid=ace44cff-85cb-37d0-a38a-b7499fa3e70b&mobile_channel=kowd7uq2:::&mobile_platform=3&other_cps=&page_id=page_te_commodity_search_1646140605059&phone_model=Pixel XL&productIds=6919509726012421404,6919653222532612239,6919504313346508736,6919594284198742478,6919507303163532941,6919724272812959428,6919299612550957276,6919665164573995599,6919665282973824340,6919553310211147976&province_id=103102&referer=com.achievo.vipshop.search.activity.VerticalTabSearchProductListActivity&rom=Dalvik/2.1.0 (Linux; U; Android 10; Pixel XL Build/QP1A.191005.007.A3)&scene=search&sd_tuijian=0&session_id=ace44cff-85cb-37d0-a38a-b7499fa3e70b_shop_android_1646136043568&skey=2d30297ff20ec9b7442dc4f3c335abdc&source_app=android&standby_id=kowd7uq2:::&sys_version=29×tamp=1646140654&user_token=F4B99E10F54F31E68D773637A4FC3D78E92EC915&warehouse=VIP_SH return_val: 924af08b6533322960ae5a597777084e226cbc7e args[2]: a84c5883206309ad076deea939e850dc&8cf90dfd9a89070a9e8653d92f57349b924af08b6533322960ae5a597777084e226cbc7e return_val: a9673ab3c2f64b755a639c9c9e641b2fc6f089ab
|