Investigating the ID4Motion Digital Cluster

The ID4Motion Digital Cluster is an aftermarket solution adding a modern digital display to older BMW and Honda cars. I was interested in how the system works, and investigated its hardware and software design, especially as it pertains to security of the system.

Introduction

The ID4Motion Digital Cluster for BMW started out as an indiegogo project in 2020, and has since become established as a small business serving the BMW, Honda and Custom car community.

Initial Investigation

1. Disassemble the ID4Motion cluster. Do not undo the screws behind the screen. They have spacers separating the screen from the remainder of the mount, and do not give you access to the main PCB. You need to drill out the rivets holding the rear tray to the rest of the mount.

2. Inside you will find the main PCB and a Ka-Ro TX-8133 module. Two points are covered with white epoxy, the Microcontroller and an Ethernet PCB mount.

3. Hardware

SoDIMM under the Black Heatsink: MCIMX6U7CVM08 – CTPL1942 – TX6-LVDS
The SoDIMM Module is made by KaRo Electronics, and consists of an i.MX 6 DualLite MCU with 4GB of eMMC.

MCU Covered in Epoxy: PIC24HJ256
There is an ICSP header located just above the MCU in the picture above, connected to the PIC.
The two 8 pin SOIC chips located above the MCU are a 24L1025E EEPROM and DS1302N real time clock.

SGTL5000 drives the small speaker

There are also two ADV7180 chips for the video inputs despite the fact that only one video input is populated on this board.

4. The PIC32 microcontroller is easily dumped using the provided ICSP (Nicely labelled as well, and the pinout matches the labels). The PIC Micro does not have code protection enabled, which allows the full hex to be dumped. A rudimentary analysis shows some I2C and CAN functions, but nothing related to any licensing. The related 24LC is connected via I2C and appears to hold a a copy of the Microcontroller code, either used as a backup or during updating when a new firmware is provided by the Ka-Ro module.

Not sure the purpose in protecting the part number of this Microcontroller, when: Code protection isn’t enabled, ICSP isn’t disabled, and it appears to just be a hardware interface between the vehicle and the Ka-Ro module, either due to the boot time or latency of the module when handling CAN-bus messages.

5. The ethernet port pinout matches the reference design on the Ka-Ro main page, so a vertical through-hole port must be purchased to match. An example is the Wurth Electronik port, part number 74990101241.

Soldering in the port, and connecting to an RJ-45 allows an SSH connection. A quick NMAP shows that this is the only common port in use. A brief attempt at brute force on “root” using hydra didn’t lead to any access. (We don’t even know if “root” is a valid user, but it turns out that this was a good guess).

More value in the epoxy protecting the solder points, but the the port isn’t physically disabled on the board or logically disabled in the Ka-Ro module. (Kernel module still exists)

Two enticing start points highlighted by epoxy, two strike-outs. Lets move onto the module itself.

Attacking the KaRo Module

The first thing to do is get some sort of interface access on the Ka-Ro module.

1. Luckily the Ka-Ro module has a UART port, and when connected gives debug output. On the downside, the interrupt has been set to 0 (or -1) which doesn’t allow keyboard interrupt at the UBoot bootloader. Watching the boot messages gives some pause, as the Ka-Ro is built around the i.Mx series of MCUs, which includes the ability to have both encrypted storage media as well as a chain of trust. (Can be restricted so it only boots signed code)

2. The PCB has a “bootmode” button labelled, and the module datasheet shows a “bootmode” on pin 8 which is described as: “Boot mode select H: Boot from NAND / L: Boot from UART/USB”. Pulling this pin low allows for a custom UBoot to be loaded to the module over the UART connection.

3. The Ka-Ro UBoot code was downloaded from github. A basic compile was loaded to the module using imx-usb. Success! The module boots the custom version of UBoot sent via UART (Booting to Linux fails, as the offset and environment setup does not match).

Use minicom to monitor serial port, interrupt loading of u-boot.
Send the custom UBoot to the module using: ./imx_usb ./u-boot.bin

4. UBoot was modified using the following code changes to allow EXT4, TFTP, and BLOCK devices. During menuconfig, “do not store environment settings” was set. This allow us to navigate the eMMC through UBoot, and upload files from the eMMC to the TFTP server.

5. Navigate the eMMC partition to see what we have.

Start a TFTP server on the host and send files from the KaRo module

Host: uftpd -n -o ftp=0,tftp=69,writable /var/tftp/
Client: setenv serverip 192.168.0.100
setenv ipaddr 192.168.0.245

6. As UBoot is able to navigate the full eMMC partition, the Flash is not encrypted. A full dump of the MMC could be performed using a hot air rework station to dump the MMC…but I’m not that great at desoldering/soldering BGA successfully. Individual files could be uploaded using tftp, but we are limited in the size which can be loaded into memory and transferred across through tftp…and even scripting it would be slow.

7. Instead, /etc/shadow was uploaded to the TFTP. We can see that our former suspicions on the use of the “root” user are correct. A more thorough attempt (couple of days on a 2080) to crack the hash with Hashcat was unsuccessful, so the root password is not a standard English or Dutch word, modified with a common ruleset. All is not yet lost. The shadow file can be modified with a hash of a password we do know. “openssl passwd -6 -salt uJBUc.QG MyPassword” makes a hash which can replace the password for the root user. (The same salt is used to reduce the number of changes needed to the shadow file)

8. The basic process is:

– Use minicom to monitor the serial port
– Use imx-usb to load the new UBoot
– Once you hit “enter” on imx-usb, switch tabs to minicom to interrupt the boot
– Set the environment server ip and local ip addresses using “setenv serverip” and “setenv ipaddr”
– Start a TFTP server on your workstation. (uftpd was used)
– Put the shadow file in memory from the eMMC: load mmc 0:2 0x12009000 /etc/shadow
– Copy the shadow file from memory to the tftp server: tftpput 0x12009000 27c 192.168.0.1:shadow
– Modify the shadow file on the workstation, copy it to an SD card, and connect the SD card to the Ka-Ro module.
– Copy the modified shadow file to memory: load mmc 1:1 0x12008000 /shadow_mod2
– Verify the contents were loaded to memory correctly: md 0x12008000
12008000: 746f6f72 2436243a 55424a75 47512e63 root:$6$uJBUc.QG
– Write the modified shadow file to eMMC: ext4write mmc 0:2 0x12008000 /etc/shadow 27c
– Verify the contents written to drive match: load mmc 0:2 0x1200b000 /etc/shadow
TX6DL U-Boot > md 0x1200b000
1200b000: 746f6f72 2436243a 55424a75 47512e63 root:$6$uJBUc.QG

9. The moment of truth. Remove the wire on pin 8 pulling to ground and allow booting from NAND. Allow Yocto to fully boot, and see if you can login.

10. Now that we are in, we can dump each partition to a tgz onto a USB drive. (Usually you don’t want to do this on a running system, but I’m not interested in a perfect backup, all I really want are the files, and this is the easiest way)

We can avoid losing root by following common techniques like: Creating a new (privileged/sudo) user, adding ssh keys to the authorised keys file for root (ssh copy id), installing a second ssh server on a non-standard port.

Software Disassembly

1. The files we are concerned about are located in /home/root, specifically

/home/root/sut -> Software Update Tool. Well-written in rust. The binary is stripped, and all debug symbols have been removed.
/home/root/package/libs/libammolite.so -> Encryption, decryption, and license check routines are here. C++ using the QT library, the binary is not stripped and has all debug symbols.

2. The way the license system works:

– The current license is held in an “ftr” file. It is an XOR-encrypted JSON file, which stores all licensed features.
– A license update is performed by copying a “ctm” file to a USB. The “ctm” is read by Ammolite (not sut). If the “ctm” version is newer, and an “ids” field matches the installed VIN, the installed license is updated.
– The “ctm” file is XOR-encrypted (different XOR seed than the FTR). Once processed through the XOR, the result is a JSON file with a Base64 payload. The Base64 payload includes an IV and AES CTM encrypted file. Once AES decrypted, the resulting JSON license file can be processed.
ctm -> XOR Decrypt = JSON -> Base64 of payload = IV and AES -> AES CTM decrypt = JSON license file.
– A software update is performed by copying a “jdm” file to a USB. The “jdm” file is an RSA signed file. (Hash of the file must match the 256 byte signature, decrypted using the pre-installed public key). This means a custom software update (should) not be possible without the appropriate private key.
– The JDM is an AES ECB encrypted tar.gz file. (Given the size of the update, ECB makes sense). Unlike libammolite and the ctm file, extracting the AES key from the “sut” binary was significantly more effort. (Extracting the AES key from libammolite was trivially easy, given it was a defined string)

Analysis

Overall, a decently well protected system, lacking the most common vulnerabilities for an embedded system; However, it could have increased the difficulty with a few minor/moderate changes.

** Key recommendation to ID4Motion** Add a digital signature to the CTM file, so fake/modified license files cannot be generated

1. The Good

– UBoot was protected, not allowing keyboard interrupt. Most embedded systems still have the default three second interrupt enabled on the UART.
– The root password was decently strong, not able to be easily cracked, even given the hash. Many embedded systems use a simple root password, easily broken once the hash is obtained.
– The software updates (jdm) are protected using public/private key signing. Even if I created a modified update, properly encrypted using the AES-ECB and XOR, an update (should) fail given that I cannot sign it correctly.
– The software update tool by Mr. Buurlage is written in a memory-safe language, and stripped, increasing the difficulty for debugging.

2. The Bad

– In production, the PIC Micro could have (should have) had code protection enabled and JTAG disabled in the configuration bits. This doesn’t prevent the MCU from being dumped, but increases the difficulty.
– The Ka-Ro module could have used encrypted memory and chain of trust. There are tradeoffs: Use of encrypted memory increases write wear on the flash, and chain of trust increases both the complexity of the build and difficulty of repair in the field. However, they both make it much more difficult to gain access to the software in the module, as compromise of UBoot would only give unencrypted access to the /boot partition.
– Client-facing software should not be running as root. If the ssh user or client-facing software is compromised, running as a less-privileged user adds another barrier to actually modifying the software. In this instance, however, there wouldn’t be much added value. I am not looking to compromise the system, but analyse the client-facing software.
– The updates themselves are not PKI protected, allowing a malicious actor (moi) to generate license files. A software update could add signing to the CTMs, similar to the JDMs.
– FTR files found in the JDM include a lot of information about ID4Motion customers and employees, including the last 7 of their VIN and the nickname for their car. An example from the FTR included in the E90 JDM 04.01.00 is as below. (This has changed as of 04.02.00. The FTR included in the JDM only has information on ID4Motion employee systems)

– The same AES key is used across all models and versions, meaning compromise of a single model allowed for decryption of the software updates for all other models. (Compromise of E60/90 allowed analysis of E38/46/53, Honda, and Universal). The use of a pragma or macro to select the correct AES key for the desired vehicle model during compilation is not difficult; However, if one model is compromised, the use of different AES keys does not stop the effect on that model, but it does mitigate risks and increase the difficulty. (For each model, the hacker must find a user, extract the software and get the AES key). This mitigation is extremely easy to implement at the corporate level, and adds a lot of additional complexity for a hacker to compromise an entire product line.

ID4Motion Developer Options Enabled

ID4Motion Toolkit

I thought it would be interesting to see if the encryption/decryption could be reimplemented. I chose to write a keygen in QT C++ since the ID4Motion system (Ammolite) is written in QT.