抓包
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
| 12
 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
| 12
 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
| 12
 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函数
| 12
 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类
| 12
 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 函数里
| 12
 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长度一致
| 12
 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
| 12
 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
| 12
 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_SHreturn_val:  924af08b6533322960ae5a597777084e226cbc7e
 args[2]:  a84c5883206309ad076deea939e850dc&8cf90dfd9a89070a9e8653d92f57349b924af08b6533322960ae5a597777084e226cbc7e
 return_val:  a9673ab3c2f64b755a639c9c9e641b2fc6f089ab
 
 | 
