Analysis of shell app Authorization parameters

Posted by algarve4me on Sat, 25 Dec 2021 09:00:06 +0100

Well, 2021 is coming to an end. I've been busy studying for a year. At the end of the year, write some articles and summarize the learning results!

The app we want to analyze today is the shell version number: v2 66.0, small partners can go to major applications and agree to download by themselves. It is also a good case of reverse analysis.

Reference link: https://core.vivcms.com/2021/10/25/637.html

1. Packet capturing and parameter finding

The first step is to open our charles and directly capture the package. It is found that the corresponding package is not caught at this time. OK, let's change the way of vpn packet capture. Open postern first, and then charles. At this time, you will successfully catch the packet. As shown in the figure below, Authorization is the field to be analyzed. After many tests, it is found that other fields can be removed, but this field alone cannot be removed.

2. Decompilation and static analysis

Find the encryption parameter Authorization, and then decompile apk. Drag our shell app into jadx to see how the encryption logic is written.

Search for a wave. It feels like MAP2 put("Authorization", getSignString(str3, null)); , Click in and have a look. Then we come here. We can see (as shown in the figure below) that we call a getSignString() method, pass in the parameters str3 and null, and then calculate the result as the value of authorization.


Follow in to see how the getSignString() method is implemented, and then come here.

The specific code is as follows. Yes, you can boldly guess that this is the core encryption method. First, we statically analyze a wave of logic, and then use frida to dynamically debug to verify whether our conjecture is correct.

  public String getSignString(String str, Map<String, String> map2) {
        Map<String, String> urlParams = getUrlParams(str);
        HashMap hashMap = new HashMap();
        if (urlParams != null) {
            hashMap.putAll(urlParams);
        }
        if (map2 != null) {
            hashMap.putAll(map2);
        }
        ArrayList arrayList = new ArrayList(hashMap.entrySet());
        Collections.sort(arrayList, new Comparator<Map.Entry<String, String>>() { // from class: com.bk.base.netimpl.a.1
            public int compare(Map.Entry<String, String> entry, Map.Entry<String, String> entry2) {
                return entry.getKey().compareTo(entry2.getKey());
            }
        });
        String httpAppSecret = ModuleRouterApi.MainRouterApi.getHttpAppSecret();
        boolean notEmpty = Safe.C2266e.notEmpty(httpAppSecret);
        String str2 = BuildConfig.FLAVOR;
        if (!notEmpty) {
            try {
                httpAppSecret = JniClient.GetAppSecret(APPConfigHelper.getContext());
            } catch (Exception e) {
                e.printStackTrace();
                httpAppSecret = str2;
            }
        }
        String httpAppId = ModuleRouterApi.MainRouterApi.getHttpAppId();
        if (Safe.C2266e.notEmpty(httpAppId)) {
            str2 = httpAppId;
        } else {
            try {
                str2 = JniClient.GetAppId(APPConfigHelper.getContext());
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        StringBuilder sb = new StringBuilder(httpAppSecret);
        for (int i = 0; i < arrayList.size(); i++) {
            Map.Entry entry = (Map.Entry) arrayList.get(i);
            sb.append(((String) entry.getKey()) + "=" + ((String) entry.getValue()));
        }
        String str3 = TAG;
        LjLogUtil.m30128d(str3, "sign origin=" + ((Object) sb));
        String SHA1ToString = DeviceUtil.SHA1ToString(sb.toString());
        String encodeToString = Base64.encodeToString((str2 + ":" + SHA1ToString).getBytes(), 2);
        String str4 = TAG;
        LjLogUtil.m30128d(str4, "sign result=" + encodeToString);
        return encodeToString;
    }

You can see that the getSignString() method passes in two parameters, one is a string type parameter and the other is a hashmap type parameter. First, call the getUrlParams() method, which is to get the stack of parameters behind the request url.

  private Map<String, String> getUrlParams(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }
        HashMap hashMap = new HashMap();
        Uri parse = Uri.parse(str);
        for (String str2 : parse.getQueryParameterNames()) {
            String str3 = str2.toString();
            hashMap.put(str3, parse.getQueryParameter(str3));
        }
        return hashMap;
    }

Next, I declare a hashmap, put urlParams and map2 respectively, and then define an arrayList array and order the array. After that, two variables httpAppSecret and httpAppId are defined. What is the specific purpose and what is the value? It's okay. I don't know at present. It doesn't matter. Let's continue to look.

Next, set a string sb, first add httpAppSecret, and then take out the elements in the arrayList array to do a splicing operation. Then come to this method, deviceutil SHA1ToString(sb.toString());, You can guess this sha1 algorithm by looking at the keywords.

  public static String SHA1ToString(String str) {
        try {
            MessageDigest instance = MessageDigest.getInstance("SHA-1");
            instance.update(str.getBytes());
            byte[] digest = instance.digest();
            StringBuffer stringBuffer = new StringBuffer();
            for (byte b : digest) {
                String hexString = Integer.toHexString(b & 255);
                if (hexString.length() < 2) {
                    stringBuffer.append(0);
                }
                stringBuffer.append(hexString);
            }
            return stringBuffer.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return BuildConfig.FLAVOR;
        }
    }

Finally, the result of sha1 calculation is spliced with the str2 parameter (base64. Encodetostring ((str2 + ":" + sha1tosttring) getBytes(), 2);), A base64 operation is performed. This str2 is the previous httpAppId, and the calculated value is the final encryption result, that is, Authorization.

3.frida dynamic debugging

It's almost time for static analysis. It's time to take out our frida to see what the actual transfer participation results look like and what httpAppSecret and httpAppId are.

js code is as follows:

Java.perform(function(){
    var getSig = Java.use("com.bk.base.netimpl.a");
    getSig.getSignString.implementation = function (v1, v2) {
        console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
        console.log("Sig Parameter 1: " + v1);
        var it = v2.keySet().iterator();
        var result = "";
        while(it.hasNext()){
            var keystr = it.next().toString();
            var valuestr = v2.get(keystr).toString();
            result += keystr + valuestr + "        ";
        }
        console.log("Sig Parameter 2: " + result);
        var res = this.getSignString(v1,v2)
        console.log("Sig Encrypted data:", res)
        console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
        return res;
    }

    var api = Java.use("com.bk.base.router.ModuleRouterApi");
    api.getHttpAppSecret = function (){
        console.log("getHttpAppSecret" + api.getHttpAppSecret());
        return api.getHttpAppSecret();
    }

    api.getHttpAppId = function () {
    console.log("getHttpAppId" + api.getHttpAppId());
    return api.getHttpAppId();
    }


    var jni = Java.use("com.homelinkndk.lib.JniClient");
    jni.GetAppId.implementation = function (v1) {
        var res  = this.GetAppId(v1);
        console.log("appId Jni param1: " + v1);
        console.log("appId Jni: " + res);
        return res;
    }

    var log = Java.use("com.bk.base.util.bk.LjLogUtil");
    log.d.overload('java.lang.String', 'java.lang.String').implementation = function (v1,v2){
        console.log("Log.d(): " + "v1:", v1, "v2:", v2);
    }

    var encrypt = Java.use('com.bk.base.util.bk.DeviceUtil');
    encrypt.SHA1ToString.implementation = function(parm1){
        console.log("SHA1 Pre encryption parameters:", parm1)
        var res = this.SHA1ToString(parm1)
        console.log("SHA1 Encrypted results:", res)
         return res;
    }
});

httpAppId is a fixed value 20180111_android:, the source is as follows:

Continue to analyze the source of httpAppSecret. First, take a look at the process of sha1 encryption:

SHA1 Pre encryption parameters: d5e343d453aecca8b14b2dc687c381cacity_id=110000home_ab_group=Bis_first_entry=0latitude=39.974194longitude=116.416306page=1tab_id=rentV2
SHA1 Encrypted results: 69afab7f7275b2b49f4b7790033498fcae22f343

You can see that before sha1 encryption parameter transmission, first splice a fixed string d5e343d453aecca8b14b2dc687c381ca, and then the parameter behind the url of the splice request. Yes, this fixed value is httpAppSecret.

Then talk about the difference between get and post requests. In the get request, the hashmap is null, while in the post request, the requested body is involved in the calculation.



Then the analysis is over.

4 code restore:

The specific code will not be published. After all, it is not very friendly to others. Look at the last data. Those who are interested can try to analyze a wave by themselves, and there will be a lot of gains.

Topics: security Network Protocol https