首页
论坛
课程
招聘
[原创]某大厂生鲜超市加密协议分析
2021-11-18 20:24 11323

[原创]某大厂生鲜超市加密协议分析

2021-11-18 20:24
11323

接口抓包 分析

工具

Charles

 

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

测试手机

Nexus 5x

 

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

接口:附近可服务的门店

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

1
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%223x.278442%22%7D%2C%22appName%22%3A%22xxxxxxx%22%2C%22screen%22%3A%221794*1080%22%2C%22lon%22%3A%221xx.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%223x.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解码后

1
commonExtend=&loginType=4&sign=b6beeee33ad4142cc54f3e55a045fbb1c70ecdfdbffa985b559cc36797d20357&screen=1794*1080&d_brand=LGE&body={"commonExtend":"","data":{"lon":"xxx.02877","lat":"xx.278442"},"appName":"xxxxxxx","screen":"1794*1080","lon":"xxx.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":"xx.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

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
66
67
68
69
70
71
72
73
74
75
76
{
    "code": "0",
    "success": true,
    "msg": null,
    "data": {
        "success": true,
        "businessCode": 0,
        "msg": null,
        "type": 1,
        "locationInfo": {
            "addressExt": "浙江xxxxxxx",
            "addressSummary": "浙江省xxx",
            "storeId": null,
            "lat": "xx.27x442",
            "lon": "xxx.02x77",
            "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,时效性为五分钟,五分钟后再次调用返回异常

1
{"code":"1","echo":"invalid signature"}

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

逆向分析

工具

jadx-反编译

 

frida+objection 动态调试

sign加密分析

sign从字符串特征和长度来看,看起来像sha256

 

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

 

image-20211118200626723

 

frida+objection跟踪调用入参

 

需要objection个个追踪然后和抓包得到的sign比对,最终确定调用的为 com.xxx.common.http.GatewaySignatureHelper.HMACSHA256
image-20211118200712661

 

image-20211118200745887

 

objection追踪

1
2
android hooking watch class_method com.xxx.common.http.GatewaySignatureHelper.HMACSHA2
56  --dump-args --dump-return --dump-backtrace

--dump-backtrace追踪调用方法栈

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
(agent) [172759] Called com.xxx.common.http.GatewaySignatureHelper.HMACSHA256([B, [B)
(agent) [172759] 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) [172759] Arguments com.xxx.common.http.GatewaySignatureHelper.HMACSHA256([object Object], [object Object])
(agent) [172759] 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

1
android hooking watch class_method com.xxx.common.http.GatewaySignatureHelper.signature  --dump-args --dump-return

多次触发抓包发现调用的是以下方法

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
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只显示为[object Object],无法显示其具体kv内容
写个frida hook脚本将map kv打印出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
1
frida -U -f com.xstore.****fresh -l hook_signature.js --no-pause

但很遗憾,有反注入检测,执行后app直接重启
也没关系,用笨办法试一下,将postbodyStr按&切开后组装成map,调用signature看看得到的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
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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\":\"xxx.02877\",\"lat\":\"xx.278442\"},\"appName\":\"xxxxxxx\",\"screen\":\"1794*1080\",\"lon\":\"xxx.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\":\"xxx.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[0], cell[1]);
            }
        }
        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[i] & 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();
    }
}
1
sign = a992b4c32cb9e81500ea37adb4dfbc8a63a661bb3a290c4fdb39abf4d752d4ac

好家伙,运行后发现sign和接口抓包的一毛一样
说明postBody中所有的参数都参与了sign的运算,换句话说sign参数是postBody参数构造的最后一步。

fv&eu 分析

这两个一致在变化,应该也是加密
搜下代码

 

image-20211118201231262
有多个,xxxcrashreport像是崩溃报告类,排除。其他的一个一个watch追吧

 

定位到

 

image-20211118201321979

 

image-20211118201401263
image-20211118201438690

 

由于eu和fv一致变化,猜测androidId是随机的,即调用了getRandomString方法,此可以通过hook证明确实调用了getRandomString
image-20211118201513898
eu和fv的加工
image-20211118201546631
对象EncryptResult只是个简单的封装,含有eu和fv两个参数
那么java很好还原,做做变体即可。
用到的HexUtils如下,还原时照抄就好了
image-20211118201621038

心得

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

看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

最后于 2021-11-19 10:33 被灵风_spirit编辑 ,原因: 脱敏
收藏
点赞2
打赏
分享
最新回复 (4)
雪    币: 891
活跃值: 活跃值 (591)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
活跃值 2021-11-19 09:56
2
0
位置暴露了,注意屏蔽
雪    币: 581
活跃值: 活跃值 (1427)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
灵风_spirit 活跃值 2021-11-19 10:35
3
0
多谢
雪    币: 581
活跃值: 活跃值 (1427)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
灵风_spirit 活跃值 2021-11-19 10:36
4
0
位置暴露了,注意屏蔽
多谢
游客
登录 | 注册 方可回帖
返回