lixiaolevae 发表于 2021-11-18 20:22

某大厂生鲜超市加密协议分析

## 接口抓包 分析

### 工具

**Charles**

关于charles的使用可翻阅我之前的charles专题文章

### 测试手机

**Nexus 5x**

**郑重声明,本文只分享思路,不做它用,为保护案例商家安全隐私,敏感信息用xxx代替**

#### 接口:附近可服务的门店

curl
所有接口调用url均为(https://colour.xxxxxxx.com/api), 通过postbody参数functionId控制获取具体的数据

```sql
curl -H 'Host: colour.xxxxxxx.com' -H 'x-mlaas-at: wl=0' -H 'user-agent: xxxxxxxapp_android' -H 'content-type: application/x-www-form-urlencoded; charset=utf-8' --data-binary "commonExtend=&loginType=4&sign=b6beeee33ad4142cc54f3e55a045fbb1c70ecdfdbffa985b559cc36797d20357&screen=1794*1080&d_brand=LGE&body=%7B%22commonExtend%22%3A%22%22%2C%22data%22%3A%7B%22lon%22%3A%22120.02877%22%2C%22lat%22%3A%2230.278442%22%7D%2C%22appName%22%3A%22xxxxxxx%22%2C%22screen%22%3A%221794*1080%22%2C%22lon%22%3A%22120.143936%22%2C%22platformId%22%3A%221%22%2C%22clientVersion%22%3A%223.6.4%22%2C%22storeId%22%3A%22232686%22%2C%22recommendSwitch%22%3A%22true%22%2C%22eu%22%3A%2275B6364667C69667%22%2C%22fv%22%3A%220333461727947597%22%2C%22osVersion%22%3A%228.1.0%22%2C%22partner%22%3A%22huawei%22%2C%22v%22%3A2%2C%22tenantId%22%3A%221%22%2C%22client%22%3A%22android%22%2C%22clientVersionBuild%22%3A%222110251117%22%2C%22model%22%3A%22Nexus5X%22%2C%22networkType%22%3A%22wifi%22%2C%22brand%22%3A%22LGE%22%2C%22lat%22%3A%2230.323437%22%7D&clientVersion=3.6.4&eu=75B6364667C69667&fv=0333461727947597&d_model=Nexus5X&functionId=xxxxxxx_platform_address_getPosition&t=1636957653670&partner=huawei&osVersion=8.1.0&build=2110251117&appid=****fresh_APP&client=xxxxxxx_android&lang=zh_CN&networkType=wifi" --compressed 'https://colour.xxxxxxx.com/api'
```
postBody URL解码后
```sql
commonExtend=&loginType=4&sign=b6beeee33ad4142cc54f3e55a045fbb1c70ecdfdbffa985b559cc36797d20357&screen=1794*1080&d_brand=LGE&body={"commonExtend":"","data":{"lon":"120.02877","lat":"30.278442"},"appName":"xxxxxxx","screen":"1794*1080","lon":"120.143936","platformId":"1","clientVersion":"3.6.4","storeId":"232686","recommendSwitch":"true","eu":"75B6364667C69667","fv":"0333461727947597","osVersion":"8.1.0","partner":"huawei","v":2,"tenantId":"1","client":"android","clientVersionBuild":"2110251117","model":"Nexus5X","networkType":"wifi","brand":"LGE","lat":"30.323437"}&clientVersion=3.6.4&eu=75B6364667C69667&fv=0333461727947597&d_model=Nexus5X&functionId=xxxxxxx_platform_address_getPosition&t=1636957653670&partner=huawei&osVersion=8.1.0&build=2110251117&appid=****fresh_APP&client=xxxxxxx_android&lang=zh_CN&networkType=wifi
```
接口正确返回 responseBody
```javascript
{
        "code": "0",
        "success": true,
        "msg": null,
        "data": {
                "success": true,
                "businessCode": 0,
                "msg": null,
                "type": 1,
                "locationInfo": {
                        "addressExt": "浙江省杭州市余杭区",
                        "addressSummary": "浙江省杭州市",
                        "storeId": null,
                        "lat": "30.278442",
                        "lon": "120.02877",
                        "testShop": false
                },
                "defaultAddress": null,
                "tenantShopInfoList": [{
                        "storeId": 232xxx,
                        "storeName": "华东****鲜云超",
                        "storeAddress": "江东中路与江东门北街交汇处",
                        "promiseInfo": "最快30分钟达 | 230.96KM",
                        "tenantDesc": "",
                        "businessInfo": "",
                        "tenantInfo": {
                                "tenantId": 1,
                                "tenantName": "****鲜",
                                "bigLogo": "http://img12.360buyimg.com/freshapp/jfs/t1/144864/26/9153/27011/5f6ae507E9dfc96a5/fc2f58d77bcbf2cd.png",
                                "smallLogo": "http://img12.360buyimg.com/freshapp/jfs/t1/143548/8/13335/6626/5fa4affbE87f4ded3/f46b57081818d3ba.png",
                                "circleLogo": "http://img12.360buyimg.com/freshapp/jfs/t1/140501/2/9085/6589/5f6ae50bE0508f49c/ae52b1fc1dc2aa59.png",
                                "contactTel": "4006068768",
                                "supportGiftCard": false,
                                "supportEmployeeCard": false,
                                "supportInvoiceCenter": false,
                                "supportBalance": false,
                                "clientInfo": null
                        },
                        "lon": "118.737681",
                        "lat": "32.036757",
                        "valid": true,
                        "freeBuy": false,
                        "delivery": false
                }, {
                        "storeId": 196243,
                        "storeName": "华中****鲜云超",
                        "storeAddress": "光谷保利广场",
                        "promiseInfo": "最快30分钟达 | 539.52KM",
                        "tenantDesc": "",
                        "businessInfo": "",
                        "tenantInfo": {
                                "tenantId": 1,
                                "tenantName": "****鲜",
                                "bigLogo": "http://img12.360buyimg.com/freshapp/jfs/t1/144864/26/9153/27011/5f6ae507E9dfc96a5/fc2f58d77bcbf2cd.png",
                                "smallLogo": "http://img12.360buyimg.com/freshapp/jfs/t1/143548/8/13335/6626/5fa4affbE87f4ded3/f46b57081818d3ba.png",
                                "circleLogo": "http://img12.360buyimg.com/freshapp/jfs/t1/140501/2/9085/6589/5f6ae50bE0508f49c/ae52b1fc1dc2aa59.png",
                                "contactTel": "4006068768",
                                "supportGiftCard": false,
                                "supportEmployeeCard": false,
                                "supportInvoiceCenter": false,
                                "supportBalance": false,
                                "clientInfo": null
                        },
                        "lon": "114.410486",
                        "lat": "30.490744",
                        "valid": true,
                        "freeBuy": false,
                        "delivery": false
                }],
                "nearStore": false,
                "fix": false,
                "fixLat": null,
                "fixLon": null
        },
        "extMap": {}
}
```
接口错误返回:接口有调用时效,会检测时间戳参数t,时效性为五分钟,五分钟后再次调用返回异常
```javascript
{"code":"1","echo":"invalid signature"}
```

接口分析得知,有三个加密参数 sign eu fv

## 逆向分析

### 工具

**jadx-反编译**

**frida+objection 动态调试**

### sign加密分析
sign从字符串特征和长度来看,看起来像sha256

jadx打开apk搜索关键字“HmacSha256”,看到加密HmacSha256搜选出多个

!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118200627.png)

frida+objection跟踪调用入参

需要objection个个追踪然后和抓包得到的sign比对,最终确定调用的为 com.xxx.common.http.GatewaySignatureHelper.HMACSHA256
!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118200712.png)

!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118200745.png)

objection追踪


```javascript
android hooking watch class_method com.xxx.common.http.GatewaySignatureHelper.HMACSHA2
56--dump-args --dump-return --dump-backtrace
```
--dump-backtrace追踪调用方法栈
```shell
(agent) Called com.xxx.common.http.GatewaySignatureHelper.HMACSHA256([B, [B)
(agent) Backtrace:
        com.xxx.common.http.GatewaySignatureHelper.HMACSHA256(Native Method)
        com.xxx.common.http.GatewaySignatureHelper.signature(TbsSdkJava:60)
        com.xxx.common.http.HttpRequest.paramHandler(TbsSdkJava:108)
        com.xxx.common.http.HttpRequest.add(TbsSdkJava:9)
        com.xstore.****fresh.modules.search.SearchRequest.getWareInfosIcon(TbsSdkJava:11)
        com.xstore.****fresh.modules.productdetail.utils.GetWareInfoIconUtils.getWareInfoMsg(TbsSdkJava:7)
        com.xstore.****fresh.modules.category.menulist.NewProductCategoryFragment.setListView(TbsSdkJava:34)
        com.xstore.****fresh.modules.category.menulist.NewProductCategoryFragment.initView(TbsSdkJava:32)
        com.xstore.****fresh.modules.category.menulist.NewProductCategoryFragment.onCreateView(TbsSdkJava:3)
        androidx.fragment.app.Fragment.performCreateView(TbsSdkJava:4)
        androidx.fragment.app.FragmentStateManager.createView(TbsSdkJava:15)
        androidx.fragment.app.FragmentStateManager.moveToExpectedState(TbsSdkJava:23)
        androidx.fragment.app.FragmentManager.executeOpsTogether(TbsSdkJava:34)
        androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(TbsSdkJava:10)
        androidx.fragment.app.FragmentManager.execPendingActions(TbsSdkJava:4)
        androidx.fragment.app.FragmentManager$5.run(TbsSdkJava:1)
        android.os.Handler.handleCallback(Handler.java:790)
        android.os.Handler.dispatchMessage(Handler.java:99)
        android.os.Looper.loop(Looper.java:164)
        android.app.ActivityThread.main(ActivityThread.java:6494)
        java.lang.reflect.Method.invoke(Native Method)
        com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

(agent) Arguments com.xxx.common.http.GatewaySignatureHelper.HMACSHA256(, )
(agent) Return Value: b196066d6b926a9e032ea9ae1b0a52a048b03ac063ad55bfbc3fca0fe88959c6
```
上游重要的方法为这三个:
        com.xxx.common.http.GatewaySignatureHelper.HMACSHA256(Native Method)
        com.xxx.common.http.GatewaySignatureHelper.signature(TbsSdkJava:60)
        com.xxx.common.http.HttpRequest.paramHandler(TbsSdkJava:108)


追signature
```shell
android hooking watch class_method com.xxx.common.http.GatewaySignatureHelper.signature--dump-args --dump-return
```
多次触发抓包发现调用的是以下方法
```java
public static String signature(Map<String, String> map, String str) {
      if (map == null || map.isEmpty() || TextUtils.isEmpty(str)) {
            return null;
      }
      TreeSet treeSet = new TreeSet();
      for (String str2 : map.keySet()) {
            treeSet.add(str2);
      }
      StringBuffer stringBuffer = new StringBuffer();
      Iterator it = treeSet.iterator();
      while (it.hasNext()) {
            String obj = it.next().toString();
            String str3 = map.get(obj);
            if (DEBUG) {
                String str4 = TAG;
                Log.d(str4, "sorted key : " + obj + ", value : " + str3);
            }
            if (!TextUtils.isEmpty(str3)) {
                stringBuffer.append(str3);
                stringBuffer.append("&");
            }
      }
      String stringBuffer2 = stringBuffer.toString();
      if (stringBuffer2.endsWith("&") && stringBuffer2.length() > 1) {
            stringBuffer2 = stringBuffer2.substring(0, stringBuffer2.length() - 1);
      }
      if (DEBUG) {
            String str5 = TAG;
            Log.d(str5, "raw signature param str : " + stringBuffer2);
      }
      return HMACSHA256(strToByteArray(stringBuffer2), strToByteArray(str));
    }
```
多次触发调用后,可确定第二个参数str是加密盐值,且为恒定值:**fa5010c35exxxxxxx40060d65d3f3801 **
第一个参数是map,objection只显示为,无法显示其具体kv内容
写个frida hook脚本将map kv打印出来

```javascript
function main() {
    console.log("Enter hook js");
    Java.perform(function x() {
      console.log("Inside Java Perform");
      var cls = Java.use("com.xxx.common.http.GatewaySignatureHelper");
      cls.signature
      .overload('java.util.Map', 'java.lang.String')
      .implementation
      = function(map, salt) {
            console.log("args1: " + map.toString());
            console.log("args2: " + salt);
            var result = this.signature(map, salt);
            return result;
      }
      
    })
}
setImmediate(main)
```
```shell
frida -U -f com.xstore.****fresh -l hook_signature.js --no-pause
```
但很遗憾,有反注入检测,执行后app直接重启
也没关系,用笨办法试一下,将postbodyStr按&切开后组装成map,调用signature看看得到的sign是否一致,或者相差多少

```java
package com.*******.hmdd.spider.cmptr.xxxxxxxxxx;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;

public class xxxxxxxxxxTestSign {

    public static void main(String[] args) {
      String postBody = "commonExtend=&loginType=4&sign=a992b4c32cb9e81500ea37adb4dfbc8a63a661bb3a290c4fdb39abf4d752d4ac&screen=1794*1080&d_brand=LGE&body={\"commonExtend\":\"\",\"data\":{\"lon\":\"120.02877\",\"lat\":\"30.278442\"},\"appName\":\"xxxxxxx\",\"screen\":\"1794*1080\",\"lon\":\"120.02877\",\"platformId\":\"1\",\"clientVersion\":\"3.6.4\",\"storeId\":\"232686\",\"recommendSwitch\":\"true\",\"eu\":\"8646C6D4A785B424\",\"fv\":\"937385A766E41727\",\"osVersion\":\"8.1.0\",\"partner\":\"huawei\",\"v\":2,\"tenantId\":\"1\",\"client\":\"android\",\"clientVersionBuild\":\"2110251117\",\"model\":\"Nexus5X\",\"networkType\":\"wifi\",\"brand\":\"LGE\",\"lat\":\"30.278442\"}&clientVersion=3.6.4&eu=8646C6D4A785B424&fv=937385A766E41727&d_model=Nexus5X&functionId=xxxxxxx_platform_address_getPosition&t=1636963490297&partner=huawei&osVersion=8.1.0&build=2110251117&appid=****fresh_APP&client=xxxxxxx_android&lang=zh_CN&networkType=wifi";
      String[] kvStr = postBody.split("&");
      Map<String, String> map = new HashMap<>();
      for (String kv : kvStr) {
            String[] cell = kv.split("=");
            if (cell.length == 2) {
                map.put(cell, cell);
            }
      }
      String sign = signature(map, SALT);
      System.out.println("sign = " + sign);
    }

    public static final String SALT = "fa5010c35exxxxxxx40060d65d3f3801";

    public static String signature(Map<String, String> map, String str) {
      if (map == null || map.isEmpty() || str == null || str.length() == 0) {
            return null;
      }
      TreeSet treeSet = new TreeSet();
      for (String str2 : map.keySet()) {
            treeSet.add(str2);
      }
      StringBuffer stringBuffer = new StringBuffer();
      Iterator it = treeSet.iterator();
      while (it.hasNext()) {
            String key = it.next().toString();
            if ("sign".equals(key)) {
                continue;
            }
            String value = map.get(key);
            if (value == null || value.length() == 0) {
                continue;
            }
            stringBuffer.append(value);
            stringBuffer.append("&");

      }
      String stringBuffer2 = stringBuffer.toString();
      if (stringBuffer2.endsWith("&") && stringBuffer2.length() > 1) {
            stringBuffer2 = stringBuffer2.substring(0, stringBuffer2.length() - 1);
      }
      return HMACSHA256(strToByteArray(stringBuffer2), strToByteArray(str));
    }

    private static String HMACSHA256(byte[] bArr, byte[] bArr2) {
      try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(bArr2, "HmacSHA256");
            Mac instance = Mac.getInstance("HmacSHA256");
            instance.init(secretKeySpec);
            return byte2hex(instance.doFinal(bArr));
      } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
      } catch (InvalidKeyException e2) {
            e2.printStackTrace();
            return null;
      }
    }

    public static String byte2hex(byte[] bArr) {
      StringBuilder sb = new StringBuilder();
      int i = 0;
      while (bArr != null && i < bArr.length) {
            String hexString = Integer.toHexString(bArr & 255);
            if (hexString.length() == 1) {
                sb.append('0');
            }
            sb.append(hexString);
            i++;
      }
      return sb.toString().toLowerCase();
    }

    public static byte[] strToByteArray(String str) {
      if (str == null) {
            return null;
      }
      return str.getBytes();
    }
}
```
```java
sign = a992b4c32cb9e81500ea37adb4dfbc8a63a661bb3a290c4fdb39abf4d752d4ac
```
好家伙,运行后发现sign和接口抓包的一毛一样
说明postBody中所有的参数都参与了sign的运算,换句话说sign参数是postBody参数构造的最后一步。


### fv&eu 分析
这两个一致在变化,应该也是加密
搜下代码

!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118201231.png)
有多个,xxxcrashreport像是崩溃报告类,排除。其他的一个一个watch追吧


定位到

!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118201322.png)

!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118201401.png)
!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118201438.png)

由于eu和fv一致变化,猜测androidId是随机的,即调用了getRandomString方法,此可以通过hook证明确实调用了getRandomString
!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118201513.png)
eu和fv的加工
!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118201546.png)
对象EncryptResult只是个简单的封装,含有eu和fv两个参数
那么java很好还原,做做变体即可。
用到的HexUtils如下,还原时照抄就好了
!(https://gitee.com/lixiaolevae/tuchuang/raw/master/20211118201621.png)

## 心得

1. 逆向需要耐心也需要大胆的猜想去不断尝试,同时需要寻求巧妙的验证方式。本例的分析向上和向下的追溯均有,灵活应对
2. 逆向工作会用到的很多好用的工具,平时注意多收集一些好用的工具或博文以事半功倍,本文所用到的工具和相关扩展知识点均贴出了链接,方便读者收藏~
3. 本文旨在分享一些逆向技巧和思路,本文所举case相关敏感已打码略去,读者不可利用本文所述内容进行非法商业获取利益,若执意带来的法律责任由读者自行承担。

kanmanli 发表于 2021-11-20 00:39

咔c君 发表于 2021-11-18 21:00

学习了不错

ll小布 发表于 2021-11-18 21:11

学习了不错

pigger 发表于 2021-11-18 21:12

支持支持支持

tttyyyuuu 发表于 2021-11-18 21:14

写得很详细,学习了

baihe1 发表于 2021-11-18 21:20

哇,这个厉害,虽然看了半天JAVA也没看懂{:1_908:}。

sandy1991 发表于 2021-11-18 21:38

写得不错!值得学习一小时

zhi048 发表于 2021-11-18 22:26

有点意思

lyfjyq 发表于 2021-11-18 23:04

这下可以不花钱吃肉蛋奶了

93244 发表于 2021-11-19 00:06

好厉害呀,值得我学习!
页: [1] 2 3 4 5 6
查看完整版本: 某大厂生鲜超市加密协议分析