Reverse Engineering the firmware on a Kenwood DDX9903S

I bought and really like my Kenwood Excelon DDX9903S headunit. I had it in my WRX, and moved it to my LS430. It supports Android Auto and CarPlay, which I find really useful when driving.

However, it has a nag screen every time it boots up. This got me curious as to how it worked, and see if it could be patched to skip this disclaimer. I figured it probably ran Linux on a SoC, as pretty much everything does nowadays. So I grabbed the latest firmware for it (mine was already updated to it), and started probing.

S_V2_7_0008_0600_AT1.zip

Extract that and you get 3 folders under S_V2_7_0008_0600/:

BOOT_V2_7_0008_0600_release/
MAIN_V1_0_2758_0400/
SOC_V2_7_0008_0600/

In each there’s a .nfu file, which I’ve never encountered before. I ran binwalk on each:

[BOOT_V2_7_0008_0600_release]$ binwalk Boot_2.7.0008.0600.nfu
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
248776 0x3CBC8 Android bootimg, kernel size: 0 bytes, kernel addr: 0x4F525245, ramdisk size: 1226848850 bytes, ramdisk addr: 0x6C61766E, product name: "ERROR: Cannot read kernel image"
1571592 0x17FB08 ELF, 64-bit LSB shared object, AMD x86-64, version 1 (SYSV)
2358024 0x23FB08 ELF, 64-bit LSB shared object, AMD x86-64, version 1 (SYSV)
3209992 0x30FB08 ELF, 64-bit LSB shared object, AMD x86-64, version 1 (SYSV)

Surprise, surprise, it runs Android. But, I’m thinking this image is possibly just the firmware updater, and not what I am looking for.

[SOC_V2_7_0008_0600]$ binwalk Soc_V2_7_0008_0600.nfu
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
5767936 0x580300 Android bootimg, kernel size: 16823168 bytes, kernel addr: 0x80008000, ramdisk size: 0 bytes, ramdisk addr: 0x81000000, product name: ""
5784320 0x584300 Linux kernel ARM boot executable zImage (little-endian)
5800283 0x58815B gzip compressed data, maximum compression, from Unix, last modified: 1970-01-01 00:00:00 (null date)
41943040 0x2800000 Zip archive data, at least v2.0 to extract, compressed size: 14612206, uncompressed size: 14696448, name: 16dnx_2.5s_fbios_bootimg.img
56555332 0x35EF744 Zip archive data, at least v2.0 to extract, compressed size: 16765150, uncompressed size: 16842752, name: TCC893X_recovery.img
73320560 0x45EC870 Zip archive data, at least v2.0 to extract, compressed size: 210950775, uncompressed size: 1825360896, name: automotive-linux-16DDX-image.ext4
284271426 0x10F1A342 Zip archive data, at least v2.0 to extract, compressed size: 87690, uncompressed size: 524288, name: fbios_dual.bin
284359188 0x10F2FA14 Zip archive data, at least v2.0 to extract, compressed size: 168, uncompressed size: 223, name: filelist.txt
284359426 0x10F2FB02 Zip archive data, at least v2.0 to extract, compressed size: 29903609, uncompressed size: 39549952, name: qboot_ddx.bin
314263106 0x12BB4642 Zip archive data, at least v2.0 to extract, compressed size: 33193, uncompressed size: 5242880, name: sbios_dual.bin
314296995 0x12BBCAA3 End of Zip archive, footer length: 22

Here we go. A kernel with a sane size and name string, some recovery images, and what looks like a filesystem image! qboot_ddx.bin must be part of Ubiquitous’ QuickBoot software. It does boot awfully fast, so that explains that. Would be interesting to see full kernel and init output on a stereo headunit. Well, once anyway.

Running binwalk on MAIN_V1_0_2758_0400/MainFw_1.0.2758.0400.mfu didn’t output anything. Not sure what that is.

I tried unzip to extract Soc_V2_7_0008_0600.nfu. Seemed to work, though it complained about a 41MB extra bytes at the beginning. I was really just interested in the filesystem image for the moment:

[SOC_V2_7_0008_0600]$ unzip Soc_V2_7_0008_0600.nfu
Archive: Soc_V2_7_0008_0600.nfu
warning [Soc_V2_7_0008_0600.nfu]: 41943040 extra bytes at beginning or within zipfile
(attempting to process anyway)
inflating: 16dnx_2.5s_fbios_bootimg.img
inflating: TCC893X_recovery.img
inflating: automotive-linux-16DDX-image.ext4
inflating: fbios_dual.bin
inflating: filelist.txt
inflating: qboot_ddx.bin
inflating: sbios_dual.bin

Running binwalk on the .ext4 verifies it is an ext4 filesystem image. I ran binwalk on the others and nothing was notable except the presumed QuickBoot file, qboot_ddx.bin which had images, and what looks like Qt library .qml files for some type of GUI. Also a Pandora image and other images. Hmm…

I decided to mount the ext4 image to take a look.

[SOC_V2_7_0008_0600]$ sudo mount -o loop automotive-linux-16DDX-image.ext4 /mnt/temp
[SOC_V2_7_0008_0600]$ cd /mnt/temp/
[temp]$ ls
bin dev home lost+found Music proc sbin sys temp usr
boot etc IMG_DEC_WORK media nand1 RAMDISK sdcard Syslog Temp var
DB filesave lib mnt opt run share system tmp

Interesting things appear to be in: /opt/jkpf:

bin dbg_daemon_starter.sh etc kill_applications.sh lib rsc script system_init.sh

That appears to be where lots of magic happens. Whenever I’m done disassembling the rest of the car for a particularly annoying remote start installation, I’ll come back to it.

23 thoughts on “Reverse Engineering the firmware on a Kenwood DDX9903S

  1. I know its been a couple of months but I’m also very interested in where you are going with this. I found your post because I started poking around on the firmware on my DNX693s which is very similar…. I’m not a programmer though so I just started hunting for strings to begin with. Then I tried extracting the big one with 7zip but that gave me the opposite. It extracted 42mb and ignored the rest… I ended up with the linux arm kernel config. lol

    On a side note – the DNX693S and if you like the larger screen a couple of other models have Garmin GPS built in. On these models with the Garmin GPS that part is quite easy to get into and mess with. Shoot me an email and I’ll send you info on that end of it. I really would love to get rid of the annoying startup caution. The prior year models did not have that. I think it might be mandated by Apple for their Carplay…

  2. Hello,

    If you develop any decoding processes or scripts, consider adding them to a GitHub repository. I have a theory that a lot of these head units are similar and possibly interchangeable once extracted far enough. Specifically the DDX9903s might be able to run the newer firmware of the DDX9904s. Of course there might be side effects (since the 9904s doesn’t have an HDMI input), but I would imagine if done properly the process could be reverted if the main OS was loadable.

    I was personally investigating this same process myself for my DDX9702S. I wanted to see if I could leverage the firmware of the DDX9704S (with a two year newer firmware). Personally, I want the updated Android App, so some other losses in functionally might be worth it to me.

    Thanks for your post, it saved me some digging.

  3. My file structure looks like this:

    fbios_dual.bin
    filelist.txt
    picdata.bin
    qboot_ddx.bin
    sbios_dual.bin
    automotive-linux-17DDX-image.ext
    TCC893X_recovery.img

    Also take a look at this link!
    Its an open source application to open the automotive-linux-17DDX-image.ext image

    https://www.automotivelinux.org/software/download

    Now I guess I need to install a Linux distort to see what’s different.

  4. @Joe,

    I haven’t looked at it in a while, but I would be sure to post any scripts or progress.

    Two problems remain:
    -Finding the point in the binary where the warning screen is shown
    -Modifying that binary

    I found the Qt QML files where the warning is, with the help of the other commentator. But without a debugger attached it will be hard to determine what part of the binary to modify.

    If I can get access via a JTAG/UART (likely), or run the firmware in a virtual environment (unlikely), I can progress more.

  5. Supposedly the DDX9904s has a modifiable timeout for the warning screen (times out after 10 seconds), if you compared the firmware versions between these two models, maybe it would be enlightening to a way to “fix” the DDX9903s.

    I’m interested in the outcome because I am in the market for a new headunit and I’m considering both the 9903 and the 9904. The 9903 will be the winner if the nag screen can auto-time out since it still has the HDMI input (which I am interested in doing some sort of RasPi based carputer)

    • Haven’t touched it in a while, but as far as I can tell, the unit continues to function fine it just has the nag screen as visual overlay. That is, it will resume Bluetooth audio, or even Android Auto if that was used last there’s just the stupid nag over the top.

      So I’m tempted to just bork the GUI layout for that portion. Either set it offscreen or see if removing it would cause it to just move along to without it (not likely). This being possible because of the QML GUI layout format of flat files.

  6. Nick,
    I was following you along until you got to the part that you didnt know what part of the binary to modify, but then you posted later saying its working fine. Did you figure out what part to modify or did you find a way to put the DDX9904s firmware on your DDX9903s?

    • I think you are referring to my last comment where I posit that the nag screen is just an overlay over whatever actual activity is beneath it, and that I could circumvent having to patch a binary if it loads the GUI from the QML files on the filesystem by changing the width/height, x/y, or transparency, or removing the button, or whatever. I haven’t tried that. I still don’t know how get access to modify the filesystem on the device.

      I haven’t touched it but I do need to open it back up and try and fix my mic. I will see if there’s a JTAG or something exposed then.

  7. Hi,

    Based on your previous posts, I’m working on a different approach:
    I think that it is easier to mount ext4 file on a Linux PC, modify it, repack the whole firmware
    and than inject it to the radio like a normal firmware update.
    If your are for instance in version 2.3, your radio will not allow you to reflash the same firmware.
    You just have to modify the filenames and put 2.4 for the radio accept the firmware update.

    • Interesting. So the firmware just checks the filenames to determine if it’s newer and needs to update? That’s easy then. I’ll have some time during the holiday break to take a look — I need to check my microphone connection anyway.

  8. I have been digging around and found some things of interest in the DDX9018DABS V1.3.0009.2300 firmware update files.
    I seems the QT based GUI is located in approm_18ddx_hdscrn.ext4

    It seems the menu layouts are in QT .qml format in rsc/gui/qml
    My hope is to use this to make the dialog invisible. Trouble is finding out which .qml corresponds to the warning dialog.
    I have found SysSettingsSystemCameraMenuDialogRearCamMsgCfm.qml my guess is that this may be the annoying reverse camera dialog warning to check surroundings.
    My thought is that all confirmation dialogs may be labeled with “Cfm” in the name.
    This has lead me to suspect that the Initial warning could be “EntSxmDialogInitializeCfm.qml”

    I wonder if simply removing the line “msg = qsTr(“TID_3381″)” could disable the warning from being shown.

    I am not game to try and repackage the update and upload it to the head unit for fear of bricking it.
    The 40MB missing when using unzip could be critical information that is lost.

    Locale language files in compiled QT .qm format: rsc/gui/lang/output/
    All GUI images: rsc/gui/img/
    Pandora Logos: rsc/infotainment/Pandora/
    Bonus Oddity: In rsc/infotainment/Opening/CORAN.MP3

    Happy Engineering

  9. Ok I’ve got it!
    I have found the layout files for the Initial warning screen and the reverse camera warning.
    Using QT Linguist i opened the language files in approm_18ddx_hdscrn.ext4 in rsc/gui/lang/output
    Using this i simply did a ctrl+f search for “Operating this product while driving”, this immediately gave me the name of the qml layout file located in /rsc/gui/qml
    The startup warning is SysStartupCautionDialogLayout.qml
    The reverse camera warning is SysCameraSurfaceLayout.qml

    I am hopeful that the startup message can be removed by commenting out everything within JKRootLayout {
    and that the reverse camera warning can be removed by commenting out
    /* [5] 警告文 */
    /*BorderImage {

    My only trouble is putting the files into the original firmware update archive without damaging it.

    If someone knows how to put these files back in i would greatly appreciate that.
    Either that or find a way to access the head unit’s filesystem directly.

  10. I’ve figured out how to restore the original nfu image.
    The SOC nfu is an uncompressed archive that can be opened in 7zip with the “Open inside #” option. This parses the file and will detect the 41MB header, and the zip file containing all the data. To be sure i used linux to create the zip file containing the EXT4 files, as 7zip will show the system that created the zip file, this probably isn’t necessary but i like to play it safe. Once the zip file is created i used hexpad to open the header and the data file, then select all of the header file and paste it to the front of the zip file. This way when i open it in 7zip it has the same header as before, the only difference is now in the zip file having a different size and the EXT4 file having a different crc and size due to the code in the warning dialogs being changed.

    I am not game to test it out as i have no way of recovering the head unit if this fails.
    The head unit does detect the new modified update because i changed the version number incrementally by +v0.1 on BOOT MAIN and SOC folder names, and nfu file names, along with the sumlist.ini file and the updateversioncontrol.ini

    So there you have it, a complete set of instructions that seems to be promising. All thats left is for someone to actually test it.

  11. I’ve made some progress.
    I cannot get the head unit to take on the update, i think there might be something in the header data that could be the crc and zip of the zip archive.
    I managed to somehow force the update via the Developers menu where i turned on debug mode, and then pushed the SOC update with the modified SOC image, in this menu you can choose from the images you want to update and it is less strict on file naming.
    HOWEVER, this has now resulted in an update BOOTLOOP as it will not recognize the files upon rebooting. i am now trying to figure out the folder structure for the update to actually begin.

    Using QT linguist you can search for the “secret” buttons, you will find the name of the screen layout and the location of the buttons in on the screen. A lot of clicking lead me to find all the menu’s that i could find in QT. The key combos are usually 1 press of each followed by groups of 3 presses. Once you start pressing the buttons and stuff up the code you will need to click something and go back to that screen in order to reset the key press log

    Most of the menu’s are keyed into the settings menu, here’s the menu’s i have found by spamming key-presses until 5am to start you off. Please let me know if you get anything else.
    Dev Menu: BR-TL-BR-TL-BRx3-TLx3-BRx2
    Production Mode and Menu: TL-BR-TL-BR-TLx3-BRx3-TLx3
    GPS Sensor menu: BLx3-TLx3
    Bluetooth HW Test: BL-TL-BL-TLx3
    Apple Menu: Combo of BL and TL??? (Accessible from Dev menu)
    Service Menu: BR-TL-BRx3-TLx3-BRx3-TL (Same as Dev menu minus some options)

    Accessed from standby mode
    Customize menu: TLx2-BLx2-TL-BL
    In the customize menu there are apparently 4 types of txt file that it is looking for to use those options.
    WallpaperCustomize.txt.txt (NOT just one .txt, YES double .txt.txt)
    OpeningCustomize.txt
    SteRemoCustomize.txt
    DOP_Customize.txt

    Pin Code Screen:
    Bypass: TL-BL in some unknown order? I spammed this until i got through, this did not remove the lock, only bypassed it temporarily until the next battery disconnect.

    There is an internal telnet and FTP server it seems, located at 192.168.2.101:21, however QBoot must be disabled, and debug mode must be enabled.
    I assume the debug mode found in the dev menu should satisfy that, but i cannot figure out how to disable qboot.

    My next step might be to buy an eeprom programmer with a 48TSOP Adapter, the nand is a Toshiba tc58nvg1s3htai0, might be worth buying one yourself along with a few spare chips to backup your nand first.
    The internal emmc is a SANDisk SDIN8DE2-4g
    The CPU is a TeleChip TCC8974
    Keep in mind this is for the DDX9018DABS, yours may differ, although the software is near identical.
    There is also an unused 16pin ribbon connector on the main board which might be used for something, no idea really.

  12. Hi,

    Very good job to find access to Dev Menu!
    It seems to be not so easy to repack modified firmware and reflash it.
    If there is really a Telnet and FTP access, we SHOULD use it !!!
    It will be way more easy to make small changes in QT files.

    I suppose you find the info in the file “dbg_daemon_starter.sh”
    My question is: “How can we connect to this Telnet deamon ???”
    By wire network? I don’t have seen any network connector when I opened my Headunit.
    By Wifi network? I don’t think my headunit is wifi. Maybe a wifi is present with a hidden SSID.
    By USB? Possible but we should have found something like “ifconfig usb0 192.168.2.101 netmask 255.255.255.0 up”

    The file “system_init.sh” is interresting too.

    We found this:
    #RS232C
    OUTPUT=/dev/console

    And I also found this: (it is always interresting to translate chinese text…)

    # ◆ Log acquisition method with Telnet only ①
    #
    # Advantages: Logs appear in color-coded state
    # Disadvantage: You must do a Telnet connection twice.
    #
    Delete part of # (1) ↑ ‘s launch jk system
    # (2) After connecting Telnet, execute the following command and start the System application
    # System> /temp/log.txt 2> & 1 &
    # # “/Temp/log.txt” is an optional file path for outputting logs
    #
    # (3) Telnet reconnect after completion of booting, execute the following command
    # tailf -n 1000 /temp/log.txt &
    # # “- n 1000” is the number of log output lines so arbitrary

    #
    # ◆ Log acquisition method with Telnet only ②
    #
    # Advantages: Telnet connection operation requires only one time
    # Cons: logs are not color-coded
    #
    Change the part of launch jk system of # (1) ↑ to the following
    # / opt / jkpf / bin / System 2> & 1 | logger
    # (2) Telnet connection after completion of startup, execute the following command
    # logread ← The logs of the past dragged out from the point of execution
    # logread -f <- logs occasionally

    So, THERE IS REALLY a telnet access for developper!
    We should find how to have access!

  13. Damn,
    I just found in etc/shadow that root is protected by a sha512 salted password
    $6$X4iZB64DH/k$vM30mdXg4LUqerf7i5GaeBHNrEX8A6.c7eEPn.t791Kz0mquKyjKxKHaCJWlRyvddnXKiYX8N.Y1Z/YbZ5G3X.

    It will be hard to crack this one…

    • Is it possible to bring the unit up in single user mode to change the root password?
      Sounds likt it would be way easier than trying to crack that beast.

  14. The telnet / ftp servers seem to be only enabled when `/proc/cmdline` contains ‘qboot=0 dbsv=1’, as per /etc/init.d/jk_debug_service.sh (I’m looking at the firmware for the DMX7017DABS btw).
    I guess that is passed from the bootloader

  15. I changed jobs and haven’t had time to look at this thing. I’m surprised it has so much interest, though.

    I will be converting my site to a static site — but will include existing comments. Perhaps a Google group or something else would work to move this discussion to?

    • I’m running a much newer deck but interested in reversed engineering the firmware as well.

      I think we should move the group if the site will be static and perhaps leave a forwarding link. This is good info so far.

Comments are closed.