android reverse engineering: http api signature (MD5 calculation)

533 Views Asked by At

Maybe someone can help me here. I have the following android system and an android app, which gets MCU firmware, boot logo and an app for car settings via a Chinese server: https://2-din.ru/product/Opel-Astra-J-2010-2015-7051

The MCU is responsible for the interaction between my Android car radio and the connected Canbus box. In the past, I unfortunately overwrote the factory MCU from the Chinese and have had problems activating the buttons on the radio ever since. They are assigned incorrectly and cannot be programmed correctly. Although the "correct" car model is selected, the buttons on the radio do not work properly; the steering wheel buttons work fine.

Well, the Russian dealer and his technical support and also the warranty department can't seem to help me any further, nor can the forums 4PDA, RedMOD and XDA. I therefore have to get to the factory MCU of the Chinese myself and have already analyzed and recorded the Car Choose App with IDA and the network traffic with Proxymon. I have a disassembly with SMALI files ready in case anyone needs it to help me.

The Car Choose App makes specific POST requests to the server with the following content, recorded with Proxymon. This is only one example.

http://api.mcu.cardoor.cn/move/mcu/queryRelationConfig
POST /move/mcu/queryRelationConfig HTTP/1.1
Host: api.mcu.cardoor.cn
Content-Type: application/json
Charset: UTF-8
Content-Length: 202
User-Agent: Dalvik/2.1.0 (Linux; U; Android 9; Octa - TS9 Build/OPM2.171019.012)
Connection: Keep-Alive
Accept-Encoding: gzip

{"appid":"dfsgherthdfghkj6o78tdftyw4uyrtyj","id":"11","language":"de-DE","level":"7","ratio":"0","remark":"2_203_81_8111_80_Ts9.4.3_11","resourcesId":"10040312","sig":"749f9e7c78c840e8e7ad7c0d5de81dfd"}


HTTP/1.1 200 OK
Server: Tengine
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Date: Mon, 11 Jul 2022 22:43:50 GMT
Via: cache30.l2st4-5[50,0], cache7.de3[252,0]
Timing-Allow-Origin: *
EagleId: 4f85b19b16575794305394119e

{"code":"CD000001","msg":"处理成功","body":{"appRelationConfigList":[{"configJson":"","cipherStatus":"","level":"7","configId":"2","name":"A\\C Control","rank":100,"logo":"null","remark":"2_203_81_8111_80_Ts8_100","id":"10040315","superId":"100","type":1}]}}

As can be seen, a signature is sent with the request (like every request):

"sig":"749f9e7c78c840e8e7ad7c0d5de81dfd"

In another forum it was mentioned to me that this is just an MD5 hash. So far I haven't quite understood how this is composed. From the forum it says:

what is behind "sig:" is a simple MD5 hash, so nothing with "Secret Key". First, a TreeMap object is created, which consists of the following components

file: <output path>
mcu_version : <mcu_version>
sys_version: <sys_version>

This is then passed to the function com/tw/carchoose/upgrade/utils/NetworkUtils;->getSig. The function then converts the TreeMap object into a Stringbuilder object (object for assembling strings efficiently) with a while loop, where the values ​​are concatenated and at the end the whole thing is returned as one long string. The result of the function is in turn appended to a StringBuilder object along with a constant "dfsgherthdfghkj5j6o78tdftyw4uyr". This StringBuilder object is then converted to a string and the final MD5 hash is then created from it, which ends up in the "sig" property in the JSON string of the POST request.

These procedures mainly take place in the classes com/tw/mcudebug/MCUService/MCUService and com/tw/carchoose/upgrade/utils/NetworkUtils.

Update files are then as I see it at first glance under the base URL http://update.cardoor.cn/terminal/software/update/car/android/ downloaded in the requested version.

Who can help me how to recalculate the MD5 and give me step by step instructions? Maybe a small tool can be built for it? For those interested, here is the disassembly with the SMALI files: https://my.hidrive.com/share/29or9vwhkd (or here the complete Car Choose APK: https://my.hidrive.com/lnk/sAKynad1)


UPDATE

I found the following TreeMap code with jadx-gui, which seems to be related to the signature. Unfortunately, my knowledge of Java is not sufficient to understand this function. Can someone explain to me what is happening here with "sig" and the constant "dfsgherthdfghkj5j6o78tdftyw4uyr"?

Car Choose - configuration list

public void hR(String str, String str2) {
        if (!this.gt) {
            iC(str, str2);
            return;
        }
        this.ic.setTitle(2131099702);
        this.ic.setMessage(getString(2131099696));
        this.ic.show();
        TreeMap treeMap = new TreeMap();
        treeMap.put("level", str);
        treeMap.put("language", this.language);
        treeMap.put("remark", str2);
        treeMap.put("id", this.gf);
        treeMap.put("ratio", this.gQ);
        treeMap.put("resourcesId", this.f0if);
        treeMap.put("sig", com.tw.carchoose.upgrade.a.c.q(com.tw.carchoose.upgrade.a.c.p(treeMap) + "dfsgherthdfghkj5j6o78tdftyw4uyr"));
        treeMap.put("appid", "dfsgherthdfghkj6o78tdftyw4uyrtyj");
        Log.e("gss", "params:" + treeMap.toString());
        com.tw.carchoose.upgrade.a.m.au("http://api.mcu.cardoor.cn/move/mcu/queryRelationConfig", treeMap, new C0028ac(this, str));
    }

Car Choose - checkPwd

public void hS(String str, String str2) {
        if (!this.gt) {
            iC(str, str2);
            return;
        }
        TreeMap treeMap = new TreeMap();
        treeMap.put("remark", str2);
        treeMap.put("password", this.hW);
        treeMap.put("id", this.gf);
        treeMap.put("resourcesId", this.f0if);
        treeMap.put("sig", com.tw.carchoose.upgrade.a.c.q(com.tw.carchoose.upgrade.a.c.p(treeMap) + "dfsgherthdfghkj5j6o78tdftyw4uyr"));
        treeMap.put("appid", "dfsgherthdfghkj6o78tdftyw4uyrtyj");
        Log.e("gss", "params:" + treeMap.toString());
        com.tw.carchoose.upgrade.a.m.au("http://api.mcu.cardoor.cn/move/mcu/checkPwd", treeMap, new C0029ad(this));
    }

Car Choose - getNew Car Choose App

private void hW() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        View inflate = LayoutInflater.from(this).inflate(fE ? 2130903070 : 2130903071, (ViewGroup) null);
        this.fV = (Button) inflate.findViewById(2131296379);
        this.ii = (TextView) inflate.findViewById(2131296378);
        this.in = (TextView) inflate.findViewById(2131296328);
        this.ib = (ProgressBar) inflate.findViewById(2131296327);
        this.fV.setVisibility(8);
        this.ib.setVisibility(8);
        this.id = (LinearLayout) inflate.findViewById(2131296383);
        this.id.setVisibility(8);
        ((TextView) inflate.findViewById(2131296384)).setOnClickListener(new aj(this));
        ((TextView) inflate.findViewById(2131296385)).setOnClickListener(new ak(this));
        ((ImageView) inflate.findViewById(2131296316)).setOnClickListener(new al(this));
        this.fV.setOnClickListener(new am(this));
        if (this.gt) {
            TreeMap treeMap = new TreeMap();
            treeMap.put("plat", this.hY);
            treeMap.put("sig", com.tw.carchoose.upgrade.a.c.q(com.tw.carchoose.upgrade.a.c.p(treeMap) + "dfsgherthdfghkj5j6o78tdftyw4uyr"));
            treeMap.put("appid", "dfsgherthdfghkj6o78tdftyw4uyrtyj");
            com.tw.carchoose.upgrade.a.m.au("http://api.mcu.cardoor.cn/carchoose/getNew", treeMap, new an(this));
        } else {
            this.ii.setText(getText(2131099860));
        }
        this.hu = builder.create();
        this.hu.setCanceledOnTouchOutside(false);
        this.hu.setCancelable(false);
        this.hu.show();
        this.hu.getWindow().setContentView(inflate);
        this.hu.getWindow().setGravity(17);
    }

Car Choose - getNew Canbox

public void cB() {
        TreeMap treeMap = new TreeMap();
        treeMap.put("canbox_version", this.bO);
        treeMap.put("sig", com.tw.carchoose.upgrade.a.c.q(com.tw.carchoose.upgrade.a.c.p(treeMap) + "dfsgherthdfghkj5j6o78tdftyw4uyr"));
        treeMap.put("appid", "dfsgherthdfghkj6o78tdftyw4uyrtyj");
        com.tw.carchoose.upgrade.a.m.au("http://api.mcu.cardoor.cn/canbox/getNew", treeMap, new A(this));
    }

Car Choose - Single MCU

public void de(String str, AbstractC0035b abstractC0035b) {
        if (!com.tw.carchoose.upgrade.a.c.r(this.cs)) {
            Toast.makeText(com.tw.carchoose.upgrade.a.k.o, this.cs.getString(2131099829), 0).show();
            if (this.cj == null) {
                return;
            }
            this.cj.dT();
            return;
        }
        this.cj = abstractC0035b;
        TreeMap treeMap = new TreeMap();
        treeMap.put("mcu_version", str);
        treeMap.put("sig", com.tw.carchoose.upgrade.a.c.q(com.tw.carchoose.upgrade.a.c.p(treeMap) + "dfsgherthdfghkj5j6o78tdftyw4uyr"));
        treeMap.put("appid", "dfsgherthdfghkj6o78tdftyw4uyrtyj");
        com.tw.carchoose.upgrade.a.m.au("http://api.mcu.cardoor.cn/move/mcu/singlemcu", treeMap, new D(this));
    }

Car Choose - MCU activity

private void rC() {
        try {
            rF();
            String str = this.path + "/MCUdebug/";
            File file = new File(str);
            if (!file.exists()) {
                file.mkdir();
            }
            File file2 = new File(str + this.pC + "_" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + "_MCUdebug_log.txt");
            FileOutputStream fileOutputStream = new FileOutputStream(file2);
            fileOutputStream.write(this.pK.getText().toString().getBytes());
            fileOutputStream.close();
            if (!com.tw.carchoose.upgrade.a.c.r(this)) {
                this.pE.setText(getResources().getString(2131099667));
                this.pE.show();
                return;
            }
            TreeMap treeMap = new TreeMap();
            treeMap.put("file", file2.getPath());
            treeMap.put("mcu_version", this.pC);
            treeMap.put("sys_version", this.pF);
            treeMap.put("sig", com.tw.carchoose.upgrade.a.c.q(com.tw.carchoose.upgrade.a.c.p(treeMap) + "dfsgherthdfghkj5j6o78tdftyw4uyr"));
            treeMap.put("appid", "dfsgherthdfghkj6o78tdftyw4uyrtyj");
            new b(this, treeMap, file2).start();
        } catch (Exception e) {
            this.pE.setText(getResources().getString(2131099668));
            this.pE.show();
            Log.e("MCUActivity", Log.getStackTraceString(e));
        }
    }

Car Choose - main function!

public void df(String str, String str2, String str3, AbstractC0035b abstractC0035b, String str4, String str5) {
        if (com.tw.carchoose.upgrade.a.k.n) {
            Log.e("gss", "apkId " + str);
        }
        this.cn = false;
        this.id = str;
        if (!com.tw.carchoose.upgrade.a.c.r(this.cs)) {
            Toast.makeText(com.tw.carchoose.upgrade.a.k.o, this.cs.getString(2131099829), 0).show();
            if (abstractC0035b == null) {
                return;
            }
            abstractC0035b.dT();
            return;
        }
        this.cj = abstractC0035b;
        TreeMap treeMap = new TreeMap();
        if (!str4.equals("") && !str5.equals("")) {
            try {
                treeMap.put("longitude", URLDecoder.decode(str4, "UTF-8"));
                treeMap.put("latitude", URLDecoder.decode(str5, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        treeMap.put("sourceId", "");
        treeMap.put("sourceList", "[{'sourceId':'" + str + "','type':'" + str2 + "','version':'" + str3 + "'}]");
        treeMap.put("imeiId", com.tw.carchoose.upgrade.a.d.u());
        treeMap.put("oemId", com.tw.carchoose.upgrade.a.d.v());
        treeMap.put("version", "0");
        treeMap.put("group", "1");
        treeMap.put("requireURLDecoderParam", "true");
        treeMap.put("channel", "car");
        treeMap.put("sourcetype", str2);
        treeMap.put("appId", "CarChoose");
        treeMap.put("sysversion", SystemProperties.get("ro.tw.version"));
        treeMap.put("sig", com.tw.carchoose.upgrade.a.c.q(com.tw.carchoose.upgrade.a.c.p(treeMap) + "dfsgherthdfghkj5j6o78tdftyw4uyr"));
        treeMap.put("appid", "dfsgherthdfghkj6o78tdftyw4uyrtyj");
        if (com.tw.carchoose.upgrade.a.k.n) {
            Log.e("gss", "CarApkUpdateManager params:" + this.co + "  " + treeMap.toString());
        }
        if (str2.equals("zip")) {
            C0044k.eP = false;
        }
        if (!this.co) {
            com.tw.carchoose.upgrade.a.m.au("http://api.mcu.cardoor.cn/move/mcu/queryStaticResourceInfo", treeMap, new E(this));
            return;
        }
        this.cm.sendEmptyMessage(3);
        this.co = true;
    }
1

There are 1 best solutions below

4
Amit Kaushik On

First of all hash can not be reversed. So only way to understand how its created is to try possible inputs which are very contextual.

You have to reverse engineer to understand com.tw.carchoose.upgrade.a.c.p(treeMap) -> It seems it returns a string. com.tw.carchoose.upgrade.a.c.q()

Assuming these are obfuscation for this method you mentioned: com/tw/carchoose/upgrade/utils/NetworkUtils;->getSig Following information are useful TreeMap -> Sorts the keys, so when using while loop keys will iterated in order Values are appended and then constant is appended in the end.

You have to understand what each value mean and in you system what can be actual value. Then try MD5 with above approach.