From Wi‑Fi Access to Root: Reverse Engineering a $50 CarPlay Dongle

12 min read Original article ↗

Louis Erbkamm

Press enter or click to view image in full size

Recently I got my hands on a cheap wireless CarPlay dongle, specifically the “Car Play Herilary C-4”. These dongles are designed to plug into a car’s USB port and bridge Wireless CarPlay from an iPhone to the car’s wired CarPlay head unit.

I was curious about the device’s insides. What software is it running, and how does this device work at a deep technical level? How much can I reverse engineer before I reach a dead end?

Turns out, in less than five hours I figured out an attack vector, which allows flashing a modified firmware image and establishing a root web shell without requiring physical access to the device.

Physical Teardown & Board Inspection

Even though I can already easily connect to the WiFi Access Point the device is hosting, which by the way has a fixed non-changeable password which can be looked up by simply searching for the SSID on the internet, I wanted to start at the hardware side of things first and get a closer look at what’s inside.

Press enter or click to view image in full size

CarPlay Dongle PCB

The plastic case of the dongle is held together by four screws and reveals a fairly simple embedded system PCB:

  • Allwinner V831 SoC (main processor)
  • AIC8800 WiFi/Bluetooth combo chip
  • A Winbond 25Q128BVEA SPI NOR flash chip (16 MB, stores all firmware)
  • A USB-C connector (plugs into the car)
  • A small tactile button labeled “KEY2”
  • Three exposed pads labeled “UART DEBUG”

So far so good, the three UART pads are the most interesting discovery for me. Their purpose is to provide a serial debug port primarily for engineers during development, but this should usually be restricted on production devices. If that is not done properly, these pads can expose a boot/debug console.

UART & Boot Console

The three UART test points provide the following interfaces:

  • GND (Ground)
  • TX (Transmit)
  • RX (Receive)

To access them on my computer, I need a physical interface, which in my case is a FTDI232 (FTDI USB-UART) — a $3 board which physically connects to the test points and converts UART TTL signals to USB.

Press enter or click to view image in full size

FTDI232

Since the pins on the device’s PCB are not labelled, I had to use a multimeter to identify GND first. One probe on a known board ground pin, the other probe touching each of the three pads. The pad that beeps with the multimeter set to continuity mode is GND.

The remaining two pads are TX and RX, which can be figured out by trial and error. In the end, the wiring should be as follows (crossover: TX connects to RX and vice versa):
FTDI GND → Dongle GND
FTDI TX → Dongle RX
FTDI RX → Dongle TX

I then opened a serial terminal to the FTDI’s COM port via PuTTY with a baud rate of 115200 (standard for Allwinner SoC devices) on my computer to listen for the incoming data.

Press enter or click to view image in full size

Serial UART Connection

With the UART connection established, powering on the dongle immediately returned output on the serial terminal. The full boot sequence is visible and includes:

  1. U-Boot bootloader output (built 2022–09–02, timestamp 09/02/2022–18:42:57)
  2. Linux kernel boot messages (kernel 4.9.118, ARMv7)
  3. Init system startup (BusyBox init, /etc/init.d/rcS)
  4. Application startup (custom services, btapp, httpd, etc.)
  5. A root shell prompt

This is a typical embedded Linux system, and the console output even shows a root shell prompt.

But remember when I said that this access is typically locked down on production devices? In this case, I could receive data but could not send any input. So UART is indeed configured as “read-only” and does not provide an interactive shell.

The UART boot log was still insightful to me. It revealed the exact kernel version and build configuration, the init process and startup scripts being executed, as well as services being launched (rcpservice, lylinkapp, httpd).

Press enter or click to view image in full size

UART DEBUG connection setup

WiFi Access Point

Since the UART path is locked down, the next thing I investigated was the WiFi access point which the dongle starts as part of its normal operation. This is how it communicates with the iPhone for wireless CarPlay.

The apparently hardcoded password is an issue in itself, anyone who knows the default password (which is the same on every device of this model) can connect to the dongle’s network.

After connecting to the WiFi AP, the dongle’s web interface can be accessed via its static IP, which is documented in the user manual. It provides the current firmware version, a factory reset option, log upload functionality (uploads to Alibaba Cloud OSS), and a firmware update option.

Press enter or click to view image in full size

Web Interface Network Traffic

Looking through the network traffic of the web interface, the JavaScript files and their content have references to WeUI (Tencent’s mobile CSS framework), custom logic (cpbox.js), and the Alibaba Cloud OSS SDK (aliyun-oss-sdk.min.js). It queries an Alibaba endpoint in order to check for new OTA firmware versions.

Press enter or click to view image in full size

Alibaba OTA Update Endpoint

This version.json file on the Alibaba Cloud OSS bucket is publicly readable and contains the current firmware version number and a direct download link to the firmware file.

Downloading and extracting the firmware

The update.img file is approximately 5 MB in size. The firmware format is SWUpdate (SWU):

  1. The first line of the file is an MD5 hash of the CPIO data that follows
  2. After that, the CPIO newc archive begins (magic bytes “070702”)
  3. The CPIO archive uses the CRC variant (070702 vs. 070701) with checksums
  4. Each CPIO entry has a 110-byte ASCII hex header followed by the filename and file data, all padded to 4-byte boundaries

To understand its structure, I wrote an extraction script in Python. The script detects and skips the MD5 prefix line, parses the CPIO newc headers (110 bytes of ASCII hex fields: inode, mode, uid, gid, nlink, mtime, filesize, dev, rdev, namesize, checksum), extracts each file entry, and identifies file types by magic bytes (SquashFS, uImage, xz, gzip, etc.).

Then I extracted the customer SquashFS image using unsquashfs, which yielded 89 files in 13 directories: the complete filesystem of the “customer” partition, which is mounted at /mnt/customer/ on the device.

This is great news! But why? Because this means that the update firmware file is not encrypted in any way, and there’s a chance we can modify it, repack it, and flash the manipulated firmware to the dongle device — if there’s no signature verification done by the device.

Firmware Analysis: What’s inside?

The extracted filesystem reveals the full software stack: partitions, binaries, splash screen images, the web interface (including a CGI backend), and much more.

Besides spending a bit of time looking through the file system out of pure curiosity, my main focus was searching for existing vulnerabilities that could allow me to gain more privileges on the device.

File System Analysis

The CGI backend seemed to be the most valuable entry point. It hosts logging and upload endpoints, and after taking a closer look I was able to confirm that the upload endpoint indeed was for firmware files that are then automatically flashed by the system. Anyone on the WiFi network can upload firmware to the device.

I also found the root password hash in /etc/shadow, a DES crypt hash of a well-known default password, trivially crackable. On top of that, a hardcoded Alibaba Cloud access key used for uploading device logs to the cloud, and CORS enabled on all origins with no authentication whatsoever.

Building the CGI web shell

Since we now identified an entry point for uploading a firmware image, I made two modifications to the firmware:

  1. I modified the .png files which I assumed are streamed to the car’s head unit when no phone is connected to the dongle
  2. To gain interactive root access to the device, I appended a CGI web shell to the startup script.

Press enter or click to view image in full size

CGI Web Shell

The CGI script is created at runtime in /tmp/ (tmpfs), not baked into the read-only SquashFS, since I had issues presumably caused by Windows CRLF line ending issues or incorrect Unix permissions.

It waits up to 20 seconds for httpd to create the /tmp/ota/www/cgi-bin/ directory and runs as root. The misconfigured CORS header allows access from any origin.

Repacking the firmware

After implementing the web shell and modifying the .png files, the previously extracted firmware needed to be repacked into the SWU format for flashing.

A repacking script automates this for me:

  1. Use mksquashfs via WSL (since I use Windows) to rebuild the customer partition. The parameters match the original firmware (e.g. block size, no extended attributes, all files owned by root)
  2. Create a CPIO newc CRC (070702) archive containing
    a. sw-description
    b. customer (the rebuilt SquashFS image)
    c. kernel (unchanged from original)
    d. “TRAILER!!!” (CPIO end marker)
  3. Prepend MD5 checksum ([MD5 hash of CPIO data] + “ update.swu\n” + [CPIO archive])

This matches the format that SWUpdate on the device expects.

Flashing the modified firmware

Now this is the part with the sweaty hands. With the modified update.img built, flashing it to the dongle should hopefully be trivial with the completely unauthenticated CGI upload endpoint.

With our new update.img file ready and my computer connected to the dongle’s WiFi, it’s a one-liner command to upload the firmware to the dongle.

curl -F “file=@modified_firmware/update.img” http://192.168.1.101/cgi-bin/index.cgi?id=upload

That’s it. One curl command. The device starts blinking for roughly two minutes, and the serial UART connection indicates when the flash is done.

The index.cgi CGI handler receives the multipart POST and saves the uploaded file to /tmp/update.img. The SWUpdate daemon running on the device picks up the file, validates the MD5 checksum, extracts CPIO entries (sw-description, customer, kernel), writes the customer SquashFS to /dev/by-name/customer (mtdblock5) and the kernel to /dev/by-name/boot (mtdblock2).

One power cycle later, the dongle comes back up with the WiFi AP, now running our modified firmware. In the worst case, the Allwinner V831’s FEL mode provides a hardware-level recovery path via USB. Success!

Press enter or click to view image in full size

Dongle running our modified firmware

Gaining root shell access

After flashing the modified firmware and reconnecting to the WiFi, the CGI web shell is available at http://192.168.1.101/cgi-bin/cmd. It can be queried via curl:

curl -X POST -d “uname -a” http://192.168.1.101/cgi-bin/cmd

Press enter or click to view image in full size

Web shell in action

This is unauthenticated root command execution on the device.

Using this attack vector, a threat actor located outside of the vehicle and within Wi‑Fi range, can perform an attack to gain persistent root access to the device in less than a minute, considering they have a prepared modified firmware.

Connecting to the WiFi AP and uploading the firmware via the already available CGI endpoint is a matter of seconds. The flashing process is then performed automatically by the device. The implemented web shell is just a demonstration of what can be done.

Observations and vulnerabilities found

This device implements minimal security controls and lacks several key protections.

Press enter or click to view image in full size

Hacked device in action
  1. Hardcoded WiFi Password
    It cannot be changed through any interface. Every device of this model uses the same password. Anyone nearby can connect.
  2. No Web Authentication
    The entire web interface (including firmware upload!) has zero authentication. Anyone on the WiFi network can upload arbitrary firmware.
  3. Unauthenticated Firmware Upload
    One curl command, that’s all it takes to flash arbitrary firmware. No signature verification, no code signing, no secure boot. The only validation is an MD5 checksum (which the attacker controls).
  4. Default Root Password
    The root password is — without revealing it here — a default password. DES crypt, trivially crackable. Though there’s no SSH/telnet by default, the password is there.
  5. Hardcoded Cloud Credentials
    An Alibaba Cloud access key is embedded in the binaries. This key is used for uploading device logs to Alibaba Cloud OSS.
  6. CORS Wildcard
    The CGI endpoints return “Access-Control-Allow-Origin: *”, allowing any website to make cross-origin requests to the dongle’s web API.
  7. Counterfeit MFi Authentication
    The device uses clone/counterfeit MFi authentication chips instead of genuine Apple MFi-certified hardware. This is both a legal and security concern.
  8. No Firmware Signing
    Firmware updates are not cryptographically signed. The SWU format only uses MD5 (not even HMAC, just plain hash). Any modified firmware that has a valid MD5 prefix will be accepted and flashed.
  9. All Services Run as Root
    Every process runs as uid 0. There’s no privilege separation or sandboxing.
  10. No firewall, no iptables
    There is no firewall on the device and iptables isn’t even installed. Every service binds to 0.0.0.0, meaning all 10+ TCP ports are wide open to any client on the WiFi network with no filtering.
  11. Information Disclosure
    The /cgi-bin/index.cgi?id=logcat endpoint returns gzipped system logs to anyone who asks. The id=host endpoint returns device serial numbers, firmware version, etc.
  12. No Privacy Disclosure for Log Uploads
    The web interface uploads device logs on user request, including dmesg output, serial numbers, and WiFi MAC addresses, to an Alibaba Cloud OSS bucket in China, using a hardcoded access key baked into the firmware. There is no privacy policy, and no indication to the user of what data is being sent or where.

Responsible Disclosure

This research was performed on my own device that I purchased and own. No third-party devices or networks were affected. The vulnerabilities documented here are all local (require WiFi proximity to the device).

Disclosure to the manufacturer was attempted. Compile paths left in the firmware binaries point to possibly a company or developer name, but that entity does not appear to have a public facing website or security contact. The product brand “Herilary” appears to be a white-label reseller with no clear connection to the actual hardware manufacturer. Without a viable contact, responsible disclosure was not possible.