I bought Hyundai Tucson 2020 two years ago and recently I found great series of blog posts on how to hack Hyundai Ioniq 2021 by greenluigi1. Unfortunately, the methods described there didn’t work for me. My car is running the previous generation of D-Audio which is quite different from D-Audio 2V described by greenluigi1. For reference, these are the exact versions of the firmware/software which I have:
I also found the password protected Engineering Mode which appears by tapping 5 times on the left from the Update button and 2 times on the right:
However, none of the publicly available passwords worked (1200, 2400, 3802, current time, etc.).
So I decided to download the firmware and try to reverse it by myself.
The firmware is available from update.hyundai.com. You have to download and install Navigation Updater and then select your car model. The updater downloads all the files and prepares an SD card or USB drive. For my Hyundai it downloaded 23GB of data which includes both navigation updates and firmware. In the root folder of the USB drive there is
2018_20_Tucson_EU.ver file which lists all of the files in the update:
+|22Q1|TLFL.EUR.SOP.V126.220421.STD_M|HM|2018_20_Tucson_EU|1477|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur|vr.img|10|623073973|97644544|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update|checksum|10|-755847668|1888|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update|info.ini|10|-2103668610|256|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update|update.ini|10|-1246074861|1883|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\gps|gps.inf|10|-1353122717|69|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\gps|gps_module.bin|10|-1514928853|561740|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\micom|micom.inf|10|1086259435|68|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\micom|micom_sw.bin|10|-468017694|1048452|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\system|qb_data.sparse.img|10|-143168397|6160556|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\system|snapshot.sparse.img|10|475782429|53428308|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\system|update_package.zip|10|-678170682|227494562|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\system|vr.inf|10|2034194806|58|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\system|vr.md5|10|-2044697631|41|1 2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\system|vr.sha|10|-189486074|137|1 ...
For each file we have its directory, name, CRC32 (as signed 32bit int) and size. I quickly figured out that the most interesting file is
2018_20_Tucson_EU\DAUDIOPLUS_M\eur\tlfl\update\system\update_package.zip which is an encrypted zip:
I tried to unzip using the password discovered by greenluigi1 (
ahqltmTkrhk2018@@) but it didn’t work.
Cracking the zip password
I decided to try the same cracking tool as greenluigi1 which is bkcrack. It implements a known plaintext attack discovered by Eli Biham and Paul C. Kocher. The attack requires at least 12 bytes of plaintext. My encrypted zip contains two zip files. So what would be a good known plaintext candidate? The ZIP file header of course! This is how it looks like:
Another significant advantage is that both zip files in
update_package.zip are stored without compression (there is no point to compress files which are already compressed). This can be verified with the following command:
$ bkcrack -L update_package.zip bkcrack 1.5.0 - 2022-07-07 Archive: update_package.zip Index Encryption Compression CRC32 Uncompressed Packed size Name ----- ---------- ----------- -------- ------------ ------------ ---------------- 0 ZipCrypto Store fbe57f09 227492981 227492993 update.zip 1 ZipCrypto Store b4be54fe 1203 1215 otacerts.zip
Now we just need to guess 12 bytes of either
otacerts.zip file header to perform the attack. There are not too many options for the first 10 bytes. The first 4 bytes are fixed (
504b0304). The version bytes are either
0a00 in most of the cases. Assuming the nested zips are not encrypted, the general purpose bit flag should be
0000. The compression method is either
0000 (store) or
0800 (deflate). So we have the following variants for the first 10 bytes:
plain1.bin: 504b 0304 1400 0000 0000 plain2.bin: 504b 0304 1400 0000 0800 plain3.bin: 504b 0304 0a00 0000 0000 plain4.bin: 504b 0304 0a00 0000 0800
We need two more bytes and the candidates are either file modification time or file modification date. The time is stored in seconds divided by 2 precision which gives us 24*60*30=43200 possibilities. Assuming the file modification happened in the last 5 years, we have 5*365=1825 possibilities for the date field. Clearly, it’s more efficient to brute force the date field. However, a single run of this attack takes 30min on my laptop which makes brute force unfeasible. It’s time for another wild guess. Both
otacerts.zip have “21 April 2022” as file modification date. Let’s assume the modification date of the first file entry in
otacerts.zip is also “21 April 2022”. This date is encoded in MS-DOS format as
9554. Now we have 12 bytes of plain text but they are not contiguous because we have skipped the file modification time. Fortunately,
bkcrack supports specifying an offset with the
-x option. The first run of the attack with
plain1.bin failed. However, with
plain2.bin I have successfully recovered the keys:
$ bkcrack -C update_package.zip -c otacerts.zip -p plain2.bin -x 12 9554 bkcrack 1.5.0 - 2022-07-07 [15:03:29] Z reduction using 3 bytes of known plaintext 100.0 % (3 / 3) [15:03:29] Attack on 1677473 Z values at index 6 Keys: 850725d9 64202f01 143f9452 2.8 % (47031 / 1677473) [15:04:05] Keys 850725d9 64202f01 143f9452
Extracting the zip files:
$ bkcrack -C update_package.zip -c update.zip -k 850725d9 64202f01 143f9452 -d update.zip $ bkcrack -C update_package.zip -c otacerts.zip -k 850725d9 64202f01 143f9452 -d otacerts.zip
otacerts.zip contains a single X509 certificate modifed on 21 April 2022 (yes, I was very lucky!):
update.zip contains all of the interesting stuff:
Reversing the firmware
Apparently, my car is running Android and
system.ext4 is the root file system.
Let’s mount and look inside:
$ mkdir /tmp/car $ sudo mount -t ext4 -o loop system.ext4 /tmp/car $ ls -la /tmp/car total 140 drwxr-xr-x 14 root root 4096 Jan 1 1970 . drwxrwxrwt 25 root root 65536 Aug 11 15:40 .. drwxr-xr-x 2 root root 4096 Apr 21 10:50 app drwxr-xr-x 2 root 2000 4096 Apr 21 10:46 bin -rw-r--r-- 1 root root 6020 Apr 21 10:26 build.prop drwxr-xr-x 11 root root 4096 Apr 21 10:50 etc drwxr-xr-x 2 root root 4096 Apr 21 10:35 fonts drwxr-xr-x 2 root root 4096 Apr 21 10:50 framework -rw-r--r-- 1 root root 1912 Apr 21 10:35 key_3000000.psr -rw-r--r-- 1 root root 1913 Apr 21 10:35 key_921600.psr drwxr-xr-x 8 root root 8192 Apr 21 10:46 lib drwx------ 2 root root 4096 Jan 1 1970 lost+found drwxr-xr-x 4 root root 4096 Apr 21 10:35 media drwxr-xr-x 7 root root 4096 Apr 21 10:38 usr drwxr-xr-x 4 root 2000 4096 Apr 21 10:35 vendor drwxr-xr-x 2 root root 4096 Apr 21 10:35 wifi drwxr-xr-x 2 root 2000 4096 Apr 21 10:45 xbin
Pretty much all of the applications are the in
/app folder. Each app has both .apk and .odex, e.g:
$ ls -l /tmp/car/app/HKMC_EngineerMode.* -rw-r--r-- 1 root root 2124079 Apr 21 10:50 /tmp/car/app/HKMC_EngineerMode.apk -rw-r--r-- 1 root root 1630136 Apr 21 10:50 /tmp/car/app/HKMC_EngineerMode.odex
$ java -jar baksmali-2.5.2.jar deodex -a 17 -d /tmp/car/framework -o engmode /tmp/car/app/HKMC_EngineerMode.odex $ java -jar baksmali-2.5.2.jar deodex -a 17 -d /tmp/car/framework -o swupdate /tmp/car/app/HKMC_SwUpdate.odex
Engineering mode password
The password for the engineering mode is obtained in the
getKeyString() method of
It takes the last digit of the current year and then it does a resource lookup:
These are the corresponding resource strings:
This year’s password is 2702. I tried it on the car and it worked:
Software update password
Even though I can extract firmware updates with the keys derived with
bkcrack, I was curious to find out the password for
A simple grep for
"update_package.zip" leads to the
getPassword() method of
The static password “+Ekfrl51Qkshsk#@zpdhkdWkd~-f” didn’t work. In my case the password was derived from the concatenation of the following system properties:
ro.product.model=daudioplus ro.product.brand=hyundai ro.product.name=tlfl_eu ro.product.device=daudiopluslow_tlfl_eu ro.product.board=daudio ro.product.cpu.abi=armeabi-v7a ro.product.cpu.abi2=armeabi ro.product.manufacturer=mobis ro.product.locale.language=en ro.product.locale.region=GB
SHA512Checking() method computes SHA512 digest of the given string and returns the result as uppercase hex string.
Doing this twice and taking a substring:
SHA512Checking(SHA512Checking(tmp1)).substring(10, 38) = "15DAA85C8D44B3979CD152A387F6"
The password is 15DAA85C8D44B3979CD152A387F6
I am pretty sure that I can create a firmware update with backdoor and get a root shell. But there is a small chance for something to go wrong and brick my car, so I am not going to do this for now :) I have some ideas how to exploit the kernel with the information from the engineering mode, so I will try this first. The ultimate goal, of course, is to play Doom on my car screen.