Show Notes

236 - Bypassing Chromecast Secure-Boot and Exploiting Factorio

The key vulnerability discovered is a relative heap out-of-bounds write in the parsing of MDL files within CS:GO.

This occurs in datacache/mdlcache.cpp where studiohdr_t->studiohdr2index is directly read from the MDL file and used to write a value without bounds checking. The issue arises from the SetVirtualModel function, where an unchecked studiohdr2index from the file is used to write data, potentially leading to access to unmapped memory. For exploitation, an attacker can set studiohdr2index to a large offset, causing a crash during the SetVirtualModel call.

To exploit the vulnerability remotely, the attacker-controlled server can enforce bounds checks for a custom MDL through the sv_consistency configuration. The client, upon connecting, downloads and loads the custom MDL to perform server-enforced size checks, triggering the vulnerability and corrupting the client’s memory. This does depend on the user having the sv_allowupload configuration option turned on which is not the default option, but is frequently required by community servers so it is not an uncommon situation.

A fun but simple buffer overflow in Factorio. When loading save files, the game will load a PropertyTree from the file. It will pre-allocate space for the file data before reading it in by taking the size from a field in the header (data_length), but it will add one. This addition can overflow the size integer to zero, which factorio’s new[] allocator will take to 1. The original unmodified size will be used for the copy, which overflows a large amount of data into the buffer. Exploitation of this issue was straightforward as the binary is compiled without Position Independent Code (PIE), and so a function pointer can be hijacked and set to a fixed gadget to kick off a ROP chain.

The vulnerability in the __io_uaddr_map() function of the Linux kernel involves the incorrect handling of multi-page regions imported from userspace. This function is intended to map a physically contiguous region of memory from userspace into the kernel’s linear mapping area. While this is fairly deep into some of the kernel’s memory management the fundamental issue I think can be understood more generically.

  1. Incorrect Verification of Constrains. The intend is that only contiguous memory will be imported using this function. To enforce this is checks the pointer for the first and last memory pages to be imported to ensure they are equal. Assuming that if they are equal all the pages in the middle will be also, but this is a constraint easily violated by user-land memory.
  2. Misunderstand how compound pages are represented by the kernel. Without going too deep into the specifics here, there is a misunderstanding of what the expected/good case looks like for importing multiple contiguous pages. It expects as described in the first point that if it is contiguous then the page pointer will be the same (its expecting that the page will be one of the “super-pages” containing multiple pages, but in reality its returning normal pages, and each page gets its own pointer. Meaning it will reject actual cases of contiguous memory.

While these problems are specific to the kernel I think you can apply this to other targets just as a reminder to actually check your assumptions. Its very easy to read code and see the developer did in-fact write a check for something, but that doesn’t mean the check is actually correct.

The result of this is that in the io_uring code when working wiht this imported pages, because it expects it is contiguous memory it will access it by the address of the first page plus an offset that will go into the next pages. Allowing writes into adjacent, non-related memory even if the pages themselves are marked as read-only.

A secure boot bypass in Chromecast with the Google TV (CCwGTV)’s 1080p revision. This bypass sort of involves two issues, a hardware fault injection and a software logical bug, and this is due to mitigations and efforts Google has made since the exploit chain that was released against the 4K model previously.

eMMC Fault Injection In order to get a u-boot shell, they needed a delay during bootloader execution. Google has made this more difficult in software by setting CONFIG_AUTOBOOT_DELAY=-2. What they discovered was that by shorting the embedded MultiMedia Card’s (eMMC) DAT5 pin at a specific time in the boot, they could have the boot process pause to allow a u-boot shell like before.

Logic bug in update functionality Another attack Google mitigated was the attacker changing the lock environment variable value to one that disabled signature checks in the boot process. To protect against this, Google now only allows the lock environment variable to be changed on development boards, which is checked via One-Time Programmable (OTP) fuses. Luckily, the researchers discovered a bug in the update functionality where it can disable Android Verified Boot (AVB), or more specifically set a flag that allows AVB verification errors to pass through and still return a success code.

This is because the AVB check had an if condition, if (is_device_unlocked() || !strcmp(upgradestep, "3")), which if true would set the AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR flag. It’s suspected this is a logical operator misuse where OR shouldn’t have been used here. This is significant because upgradestep ultimately comes from the upgrade_step environment variable, which attackers can control with a u-boot shell. By simply setting upgrade_step=3, AVB can be bypassed with no indication the boot integrity has been subverted.

Persistence Another interesting thing they noticed is that the Bootloader Control Block (BCB) was not checked or signed. The BCB is basically passed directly into the u-boot shell, and so by placing the setenv upgrade_step 3 command in ther , they could disable AVB on the subsequent boot. The init script would just continuously set that variable up for each sequential boot to root the device persistently.