Executing a Chromecast Exploit – Times Three

Chromecast with Google TV (1080P) Secure-Boot Bypass

Introduction: Implications of These Findings

This piece details the development of a chain of three exploits intended to allow an individual to run a custom OS/unsigned code on the Chromecast with Google (CCwGTV) 1080P.

Security researchers Jan Altensen, Ray Volpe, and I developed this chain of vulnerabilities as a group effort.

Regarding the impact itself, it’s important to consider it from different perspectives. For hobbyists, the option to install a custom operating system is now available. However, to the average end user who doesn’t customize their device extensively, the primary concern lies in potential pre-installed malware or spyware affecting the device.

Especially when purchasing from third-party resellers, such as eBay, where these products often undergo frequent resale due to flash sales, caution is advised. Many TV boxes circulating in these markets have been found to be intentionally pre-infected with malware, as highlighted in various videos that have recently been posted on YouTube.

For those seeking a secure option, the Chromecast is generally considered a safer choice. Nevertheless, it’s important to note that even with well-secured devices, there are potential vulnerabilities. By utilizing the vulnerabilities discussed in this blog, an attacker could pre-install malware or spyware while the device falsely reports as secure to the user while actively eavesdropping on their communications.

Another concern involves the remote control that remains paired with the device, equipped with a built-in microphone. It is possible that the Chromecast could initiate the microphone remotely, posing a privacy risk. With control over the remote, an attacker could intercept communications by exploiting the always-connected nature of the remote.

Beyond audio interception, the risk extends to capturing login credentials for various applications. Keyloggers could record all input, including passwords, presenting a significant threat to user privacy and security. Some of these vulnerabilities appear to affect multiple Nest devices, including Google Chromecast, Nest Wi-Fi Pro, and some Google Home series products. In fact, any smart product with an Amlogic chip is affected. These products include Android TVs, IP cameras, WIFI APs, routers, smart TVs, automotive components, smart speakers, and more.

These vulnerabilities exist in Chromecast with Google TV (1080P) devices not yet updated to the December 2023 build.

Standard Disclaimer: Any potential damage to your device by this exploit is not the responsibility of the authors of this paper.  

Background: The Original and Next-Generation Secure-Boot Bypass

In 2021, Jan Altensen and I released a persistent secure-boot bypass (sabrina-unlock) for the original CCwGTV (Chromecast with Google TV).

In September of 2022, Google released a “Full HD” (1080p) model of Chromecast with Google TV, rebranding the original CCwGTV as the Chromecast with Google TV 4K.

Given that the names of these devices are so similar, we’ll refer to them by their internal codenames, with the 4K model being “sabrina” and the 1080P model being “boreal”.

sabrina utilized a high-end Amlogic S905D3G (SM1 family) chipset, while boreal utilizes a much lower end, but much newer Amlogic S805X2G (S4 family) chipset. This decision was likely made to hit a specific price point; and unlike SM1 series chips, the S4 series supports AV1 hardware decoding.

So, of course we had to purchase a few to see if we could support it in LineageOS like we do sabrina.

Inquiring with Google ultimately led to the legally-required GPL license releases of kernel/modules/u-boot source code.

The Nest team has since migrated to utilizing GoogleSource Git repositories instead of Google Drive tarball releases. All Nest products with relevant OSS licensed code can be found in that device’s specific manifest here.

Mitigations Taken by Google

The first hurdle to overcome is that boreal’s development team learned from our work on its 4K sibling.

Both the injection vector in the form of the BootROM exploit, and the persistence method in unlocking the bootloader by writing to the env partition.

The BootROM vulnerability is no longer viable, as this board utilizes a much newer BootROM version, and even has a fancy new low-level interface tool called adnl that replaces the old update tool.

The ability to persistently write the env partition to change the lock and unlock_ability variables is mitigated by “force locking” the device, which I discuss in more detail below.

Understanding How We Gained Initial Access

Firstly, we need an access venue to u-boot, or a similarly-privileged injection vector.

UART was easy to identify, though Amlogic got creative with their baud-rate, setting it to 921600, in contrast to previous Amlogic SoC’s which utilize 115200.

Unfortunately, Google has hardened both of the useful access venues UART offered before:

  • u-boot’s CONFIG_AUTOBOOT_DELAY is set to -2, which should prevent us from interrupting the boot-sequence to access a u-boot shell over serial (more on that later…)
  • Android’s console service is not started, and we have no privileged venue from which to start it, so we can’t access an Android shell over serial either.

We were stuck here for some months, until another researcher surfaced with precisely the injection vector we needed.

By strategically shorting the eMMC D0 pin during boot when the device reaches a specific stage of u-boot, we can get u-boot to gracefully pause the progression of the boot sequence and create an artificial boot-delay that allows us to access a u-boot shell.

Given it was just shorted out, the eMMC is rightfully in an unusable state. This state can be fixed simply by running Amlogic’s useful built-in tool amlmmc rescan 1.

We initially thought that the device would be easy to unlock from here – but there turned out to be more to the story.

“The Device is Force Locked”

We then tried to set the lock variable by hand as we did on sabrina. To discern what to set it to, we again looked at the LockData struct:

typedef struct LockData {
        uint8_t version_major;
        uint8_t version_minor;
        uint8_t unlock_ability;

        /* Padding to eight bytes. */
        uint8_t reserved1;

        /* 0: unlock    1: lock*/
        uint8_t lock_state;

        /* 0: unlock    1: lock*/
        uint8_t lock_critical_state;

        /* 0: enable bootloader version rollback 1: prevent bootloader version rollback*/
        uint8_t lock_bootloader;
        uint8_t reserved2[1];
} LockData_t;

---- u-boot/include/emmc_partitions.h

So, just as we did on sabrina, we ran setenv lock 10100000 from u-boot shell, then attempted to continue the boot process.

Unfortunately, Google really learned from the last go around. We can’t usefully set variables relevant to the lock state, as when you try to set them, the runtime check loops and states “The device is force locked”, and won’t progress the boot process any further.

Let’s take a look at how “force locking” is handled:

static AvbIOResult read_is_device_unlocked(AvbOps* ops, bool* out_is_unlocked)
{
    if (get_board_variant() != BOARD_VARIANT_DEV) {
        printf("The device is force locked\n");
        *out_is_unlocked = false;
        return AVB_IO_RESULT_OK;
    }

---- u-boot/cmd/amlogic/cmd_avb.c

During the AVB (Android Verified Boot) process, if get_board_variant doesn’t return BOARD_VARIANT_DEV, the device will forcibly report that it is locked regardless of the current env values.

Diving a bit deeper into how get_board_variant works:

int get_board_variant(void)
{
#ifdef CONFIG_DEBUG_BOARD_VARIANT_PROD
        return BOARD_VARIANT_PROD;
#else
        static int board_variant = BOARD_VARIANT_UNKNOWN;

        if (board_variant == BOARD_VARIANT_UNKNOWN) {
                if (IS_FEAT_DIS_NORMAL_DEVICE_ROOTCERT_0() == 0 &&
                    IS_FEAT_DIS_DFU_DEVICE_ROOTCERT_0() == 0)
                        board_variant = BOARD_VARIANT_DEV;
                else  
                        board_variant = BOARD_VARIANT_PROD;
        }

        return board_variant;
#endif
}

---- u-boot/board/amlogic/s4_t211/s4_t211.c

The board will only identify as BOARD_VARIANT_DEV when IS_FEAT_DIS_DFU_DEVICE_ROOTCERT_0 equals 0.

int IS_FEAT_DIS_DFU_DEVICE_ROOTCERT_0(void)
{
        // Do double-read to prevent hardware glitch attack
        if (OTP_BIT_CHECK(FEAT_DISABLE_DFU_DEVICE_ROOTCERT_0))
                return 1;
        if (OTP_BIT_CHECK(FEAT_DISABLE_DFU_DEVICE_ROOTCERT_0) == 0)
                return 0;
        return 1;
}

---- u-boot/arch/arm/mach-meson/s4/aml_efuse.c

Unfortunately, IS_FEAT_DIS_DFU_DEVICE_ROOTCERT_0 utilizes OTP_BIT_CHECK, which directly reads an EFUSE, which is a one-time writable software-programmable fuse that cannot be rewritten.

This situation means that there is no direct way to disable signature checks on the device from the u-boot shell.

Now, in theory, from here we could just dump u-boot out of memory, calculate function offsets, and byte-patch u-boot in memory to bypass this signature check issue, but that would be a messy process that ultimately wouldn’t help make it persistent – and we shortly discovered something much more useful anyway.

Amlogic’s Upgrade Process is Strange, but (Accidentally) Useful

Let’s take a look at how the actual AVB checks are handled:    

avb_init();

    upgradestep = env_get("upgrade_step");

    if (is_device_unlocked() || !strcmp(upgradestep, "3"))
        flags |= AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR;

    if (!strcmp(ab_suffix, "")) {
        for (i = 0; i < AVB_NUM_SLOT; i++) {
            if (requested_partitions[i] == NULL) {
                requested_partitions[i] = "recovery";
                break;
            }
        }
        if (i == AVB_NUM_SLOT) {
            printf("ERROR: failed to find an empty slot for recovery");
            return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT;
        }
    }

    if (!strcmp(ab_suffix, ""))
        result = avb_slot_verify(&avb_ops_, requested_partitions, ab_suffix,
            flags,
            AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, out_data);
    else
        result = avb_slot_verify(&avb_ops_, requested_partitions_ab, ab_suffix,
            flags,
            AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, out_data);

    if (!strcmp(upgradestep, "3"))
        result = AVB_SLOT_VERIFY_RESULT_OK;

    return result;
}

---- u-boot/cmd/amlogic/cmd_avb.c

For those of you who want to challenge yourself, look closely, the bug isn’t that hard to spot.

If you asked, “what is upgradestep”, you’re on the right track.

The issue here is a logical operator misuse:

if (is_device_unlocked() || !strcmp(upgradestep, "3"))

This function is intended to temporarily bypass AVB during step 3 of AML upgrades, but can be maliciously used to disable AVB while leaving the device in a state that it believes it is secure, and locked.

Now, as we noted earlier, is_device_unlocked will always unconditionally return false unless we have a DEV unit.

But upgradestep is simply controlled by the following function:

upgradestep = env_get("upgrade_step")

---- u-boot/cmd/amlogic/cmd_avb.c

So, we can simply setenv upgrade_step 3, then saveenv, and continue the boot process by running run storeboot.

By utilizing this command, u-boot will not only allow AVB verification errors (AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR), but also return a result of success (AVB_SLOT_VERIFY_RESULT_OK).

This result means that Android won’t believe it is unlocked, or that AVB checks have been violated/skipped – it will report as secure, and not wipe user data like typical bootloader unlock would for data security purposes.

At this point, we could utilize the eMMC fault injection and the upgradestep bug to temporarily boot any kernel image we chose, but that process is “tethered” as with each boot, upgradestep is reset forcibly by u-boot, even if it’s written to the env partition.

Making the Permanent Bypass of Secure-Boot Persistent

If we want to run a full custom operating system, we need to find a method to permanently bypass secure-boot.

Now, we noted that u-boot lacks any whitelist or sanity checking surrounding data passed to it via Android’s misc partition BCB (Bootloader Control Block) data.

The BCB usually passes critical boot-state data, such as reboot-reason like rebooting to recovery for a factory data reset or bootloader mode, etc.

u-boot interprets the data in the BCB as direct u-boot shell commands to be executed in the preboot stage of u-boot.

This inherent trust can be abused to feed malicious u-boot commands into the boot-sequence via root access Android-side.

So, by dumping the misc partition, then examining and carefully writing data into the BCB to set the upgrade_step variable to 3, we can make the secure-boot bypass persist to at least the next boot, as shown below:


At this point, we can violate AVB on the next boot, and therefore introduce our own init script to continually set that variable at every boot, or more conveniently, root with Magisk, and introduce a module to do it easily for the end user.

This action means that as long as the BCB data isn’t cleared forcibly by the user, the device will bypass secure-boot persistently.

A few caveats to this method:

  • Factory Data Resetting, issuing reboot recovery, reboot bootloader, or anything that would modify the BCB will reset this value, and AVB will be enforced on the next reboot, meaning you will need to re-do the eMMC fault injection and re-set the variable.
  • There are no full factory images for boreal, so backup everything via an ADB root shell before modifying anything on the eMMC.

This process was enlightening. LineageOS currently boots, but as it is still broken, we’re hoping there is an ETA for improvement in the near future.  

How to Perform the Exploit on Your Own

We significantly overcomplicated the eMMC fault injection and UART for convenience and testing purposes. Here is an annotated diagram of our setup:

The bottom of the board:

The top of the board:

In short, we wired a header for UART to the side of the device, wiring both Rx and Tx from the exposed pins, and creating a ground by smearing solder from the nearby ground pad.

We then wired the eMMC D0 pin to a physical switch that we installed on the board for ease of use.

The final product is shown below:

It isn’t as difficult to execute this exploit as one might expect, as it can be performed with the following supplies:

  • A host machine with USB
  • FTDI UART adapter (or any equivalent)
  • 2* female to male jumper wires OR something like a PCBite kit
  • 1* male to male jumper wire OR a paper clip

The only hard part of the exploit chain is the eMMC fault injection, so we’ve written up some instructions to help simplify it for the end user:

Hook the FTDI UART adapter to your machine and open a serial terminal with the baud rate set to 921600, and “Hardware Flow Control” disabled.

Next, wire the pins labeled Rx on the board -> Tx on the adapter, and Tx on the board -> Rx on the adapter. At this point, you should be able to view full boot logs on power on.

Now, prepare a ground wire from the adapter (or a paper clip that is seated on a reasonable ground), and put it in a position that allows you to tap the D0 pin.

For some reason, it is easier to get a u-boot shell if the HDMI port is not connected, so unplug HDMI at this time.

Now, plug the device in, and shortly into the boot process you will see something akin to the following message via UART:

2d-eye soc_vref 0039 0039 0039 0032 0034 0041 0037 0038 0035 0034 0040 0040 0036 0038 0036 0038 0038 0035 0036 0033 0032 0032 0035 0035 0034 0035 0035 0032 0033 0032 0032 0036 0032 0032 0032 0035 0035 0047 0035 0042 0035 0044 0035 0040 average_value_dec 0035 0765 mv
2d-eye dram_vref average_value_dec 0000 vref_ave_voltage 0720 mv range_0 0720 mv range_1 0540 mv

Have in mind a series of three presses done in a single sequence, each about a half-second apart.

The first of the presses in this series should be done at the very tail end of the delay of the above-referenced text, or at the very beginning of when the text starts moving again, leaning toward the latter of these two.

One of the three presses should drop you into a shell that shows:

boreal#

If you see errors about earlier bootloader stages failing to load, or other errors, the presses were done too soon, but the device should reboot on its own so that you can try again safely.

Demo Video

Video demonstrations of the exploit being performed here and the expected output here.

Disclosure Timeline

  • Early Q1-2023 – Initial chain of 3 exploits developed.
  • Mid Q2-2023 – Initial report filed with Google.
  • 07-JUN-2023 – Google flagged the issue as “Triaged” and stated that the issue may qualify for the Android and Google Devices Security Reward Program.
  • 08-AUG-2023 – We offered to provide a pre-hacked device as a demonstration.
  • 16-AUG-2023 – Google alerts us that the VRP team awarded us with a bug bounty for the vulnerability.
  • 29-AUG-2023 – Google asks that we delay disclosure until the end of 2023 to allow the multiple low-level components to be patched by the relevant vendors.
  • 05-SEP-2023 – Google schedules a video call to discuss the proposed mitigations with us.
  • 07-SEP-2023 – We discussed mitigation proposals with Google, and they offered to sponsor our team to attend hardwear.io NL 2023. We were also alerted that Thomas Roth of HexTree.io reported a similar eMMC fault injection bug on boreal around the same time we did. The disclosure date is set for 15-NOV-2023.
  • Q3-2023 – The bug bounty pays out.
  • 10-OCT-2023 – Google’s VRP team asks us to extend the disclosure deadline to mid-December. We agreed on 06-DEC-2023, the day after the December ASB (Android Security Bulletin) is posted.
  • Early DEC-2023 – A build that patches all 3 vulnerabilities and enables ARB was deployed via OTA for boreal.
  • 06-DEC-2023 – This writeup was published and CVE’s were published by MITRE.

CVE Tracking

CVE-2023-48424 – eMMC fault injection (attributed to both our team and the HexTree.io team
CVE-2023-48425 – AVB (upgradestep) vulnerability in u-boot
CVE-2023-6181 – Proper whitelisting of BCB commands passed by the user-space

Credits

  • Theorizing, developing, and chaining together these vulnerabilities into an exploit chain: Nolen Johnson (npjohnson), Jan Altensen (Stricted), and Ray Volpe (Functioner).
  • QA: Philip Valvo Schnotalla.

Acknowledgments

We extend special thanks to Angelina Sosa and the VRP team at Google. All are responsive and helpful, and enable security researchers to do this important work. Also, we can’t thank Google enough for the opportunity to attend hardwear.io.

You can attain relevant source code for the device here: U-Boot & Linux

Prev
Next
Shares

2023 Security Operations Threat Report

X