此文不涉及任何道义和伦理的讨论

尝试旧版本APP,查看对应的旧版本接口是否加密

安卓的历史版本可以通过hiapk下载,资源的链接一般类似这种形式: http://apk.hiapk.com/appinfo/com.maihaoche.bentley/1050805,最后的数字用于标识不同的版本号。 IOS的历史版本可以通过itunes下载,只需修改请求中携带的版本号即可下载不同版本,具体参见:iTunes下载 App Store 任意历史版本应用

在mac上通过Genymotion可以安装安卓apk,在虚拟中配置代理地址10.0.3.2,端口8888,配合charles即可将虚拟机中请求拦截到charles中。

分析了若干历史版本之后,发现该的App2.X版本未进行加密,但是对应的域名和接口也已经停用,所以这条路走不通。

尝试反编译APP

apk的反编译可以使用:apk2gold 反编译之后,可以看到解析出的java代码。 得到代码之后如何定位到加密部分的逻辑因场景而定,在这个app中,定位到一个关键类BaseWeb,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
protected static TreeMap<String, Object> jiaMi(TreeMap<String, Object> treeMap, boolean z) {
        ...
        try {
            String sha1 = sha1(treeMap, z);
            if (sha1 == null) {
                return null;
            }
            treeMap2.put("key", sha1);
            return treeMap2;
        } catch (Exception e) {
            u.a(e);
            treeMap2.put("key", "");
            return treeMap2;
        }
    }

可以看到key字段是通过sha1函数生成,具体代码如下:

 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 sha1(TreeMap<String, Object> treeMap, boolean z) {
        if (treeMap == null) {
            treeMap = new TreeMap();
        }
        if (!z) {
            treeMap.put("publicKey", Info.a().getMoreStrInfo(AppContext.getInstance(), 345));
        } else if (!mApplication.isLogin()) {
            return null;
        } else {
            treeMap.put("publicKey", mApplication.getUser().getPrivateKey());
        }
        if (treeMap == null || treeMap.size() <= 0) {
            return "";
        }
        String str = "";
        int i = 0;
        for (String str2 : treeMap.keySet()) {
            Object obj = treeMap.get(str2);
            if (obj != null) {
                if (i > 0) {
                    str = str + "^_~";
                } else {
                    i++;
                }
                ...                    
                str = str + str2 + "=" + formaterFloat(Double.parseDouble(obj.toString()));
                ...
            }
        }
        // 标准的sha1加密
        return i.e(str);
    }

这个方法根据z的取值决定publicKey是通过 Info.a().getMoreStrInfo(AppContext.getInstance(), 345)这个方法来生成,还是直接取mApplication.getUser().getPrivateKey() 。 因为我们感兴趣的接口的z取值都是false,所以要深入探究一下 Info.a().getMoreStrInfo(AppContext.getInstance(), 345)这个方法。 反编译发现这个方法是包裹在so中,也是常见的保护敏感信息的方式。 Hopper反编译 结果:

祭出神器IDA Pro,代码可读性提高很多:

汇编无力,直接F5: 找到传入参数为345时候的对应逻辑:

其实就是简单取得了一个字符串:4cbce54e——17c70193

成功

因为参数是完全明文的,将参数和反编译获取的publicKey放入TreeMap,拼接成字符串之后执行标准sha1,即可获取一个合法的key

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
        TreeMap<String,Object> treeMap = new TreeMap();
        treeMap.put("publicKey", "4cbce54---------70193");
        treeMap.put("参数key", "参数value");

        String str = "";
        int i = 0;
        for (String str2 : treeMap.keySet()) {
            Object obj = treeMap.get(str2);
            if (obj != null) {
                if (i > 0) {
                    str = str + "^_~";
                } else {
                    i++;
                }
                if (obj instanceof String) {
                    str = str + str2 + "=" + obj;
                }
            }
        }
        System.out.println(e(str));

对比结果

真实请求:

模拟 key :