0

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;
    }
  • As you have HTTP requests with a recent date I assume you can also execute the app. If this is the case another option would be to hook the signature creation method or the relevant digest methods using https://frida.re. Then you would get the input data that is used for generating the hash. That can makes it easier to understand what is hashed there. – Robert Jul 24 '22 at 15:44
  • @Robert Thanks for the information. Is there a how-to for this and how to use it? – fork_stacker Jul 29 '22 at 11:17
  • For hooking crypto methods like hash you may find [Objection](https://github.com/sensepost/objection) useful, as far as I remember it already contains the necessary hooking code. For Frida and Objection + Android there should be several tutorials available, just search for those keywords. – Robert Jul 29 '22 at 11:56

1 Answers1

0

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.

Amit Kaushik
  • 642
  • 7
  • 13
  • Well I did a little more research. `com.tw.carchoose.upgrade.a.c.p(treeMap)` and `com.tw.carchoose.upgrade.a.c.q()` are declared together in a Java class called `com.tw.carchoose.upgrade.a.c`. I'd like to share the code here, but it's a bit too long for the comment field... – fork_stacker Jul 31 '22 at 07:05
  • `public class c { public static String p(Map map) { Map s = s(map); StringBuffer stringBuffer = new StringBuffer(); for (Map.Entry entry : s.entrySet()) { stringBuffer.append(((String) entry.getKey()) + URLEncoder.encode((String) entry.getValue())); } return stringBuffer.toString(); }` – fork_stacker Jul 31 '22 at 07:09
  • `public static String q(String str) { try { String bigInteger = new BigInteger(1, MessageDigest.getInstance("MD5").digest(str.getBytes())).toString(16); return bigInteger.length() % 2 != 0 ? "0" + bigInteger : bigInteger; } catch (NoSuchAlgorithmException e) { return ""; } }` – fork_stacker Jul 31 '22 at 07:10
  • `public static Map s(Map map) { if (map == null || map.isEmpty()) { return null; } TreeMap treeMap = new TreeMap(new o()); treeMap.putAll(map); return treeMap; } public static boolean t() { return Looper.getMainLooper() == Looper.myLooper(); } }` – fork_stacker Jul 31 '22 at 07:12