Smiko - Writeup

Written with love by hannah :3

Some Context

Several months ago, I was chatting with Writable and some friends about the Sh1mmer patch. The patch had been on stable for almost a year, and was a Cr50-level patch that basically prevented the modification of FirmWare Management Parameters (FWMP) if the developer switch AND the the recovery switch were engaged, which was always true when inside the RMA Shim environment. At the time, this solution worked very neatly. It prevented users from getting into developer mode, and a check was added in the FRE screen on the OOBE setup screen that double-checked FWMP, indicating the device had been enteprise enrolled before and would ignore the value of check_enrollment in the vital product data.

Some solutions were found to work around this, including disabling Write-Protection to set the GBB flags to a value that ignores FWMP (patched R114), allowing booting into devmode to properly remove it, abusing tpm-less keys to preload encrypted userdata to trick the device into removing FWMP and skipping the FRE check (patched R120), abusing overflows in the ChromeOS recovery system to gain code execution in verified recovery to properly remove FWMP (patched R125), etc.

To a fault, all of these bypasses were workarounds to the original patch in the Cr50 that stopped unenrollment in the first place. As time went on though, I began to wonder if there was any way one could get rid of the original patch. If you've been paying attention to Coolelectronics's blog "Breaking-Cros", you probably remember this line when they discussed the R111 Sh1mmer patch:

"Since that's a firmware level patch in the TPM itself it can't be downgraded"

Well, that line got me thinking. Control over the Google Security Chip would mean full unenrollment, as the GSC has direct access to flash controllers, the hardware WP state, FWMP kernver and fwver values, and more. It was that line that sparked an idea that led to a series of events including drama, lots of crashes, and the discovery of 3 massive vulnerabilities in the H1 and D2 Google Security Chips. How? Well, grab a drink and a snack, and take a seat.

The Actual GSC Security Model

In order to understand the actual vulnerabilities, we need to understand our limitations and what we can and can't do.

The Google Security Chip is a standalone 32-bit ARMv7-based System-on-Chip Microcontroller completely seperate from the main CPU. Unlike the main CPU, it always gets power unless the battery dies, and only resets when explicitly told to (usually by gsctool during updates).

When it resets, the SoC's main core, the Cortex-M3, reads the initial Stack Pointer and Program Counter from 0x0 and 0x4 respectively, and copies them into the CPU registers. These are addresses to the starting stack location and program entrypoint of the BootROM, short for Boot Read-Only-Memory. As the acronym suggests, the BootROM cannot be modified by any means, and is responsible for handling the very first stage of the GSC's boot process. Because of its immutability, the BootROM is kept closed source, highly confidential, and during the boot process the RO firmware outright hides it. More on that later.

The BootROM reads the Flash region of the chip at the offsets 0x40000 and 0x80000 in memory, and checks both for a valid signature, starting with whichever is newer. If the signature is valid and the payload hasn't been tampered with, the BootROM jumps to its address, effectively transferring complete control to it.

The RO firmware, now completely in control from the BootROM, does something very similar, reading from 2 offsets: 0x44000 and 0x84000, each corresponding to an RW_A or RW_B firmware. It validates it the same way the BootROM validates the RO firmware, and jumps to either firmware offset accordingly, depending on which is newer.

Hunting for Exploits

With everything in mind, the GSC by default seems like a very secure system. Only Google has the keys that can sign firmware images for it, and all other keys don't work at all and any modifications to the firmware image will get detected in the signature check and firmware image will be entirely rejected.

So, what can we do? We can't run unsigned firmware, and we can't change anything, so is this a dead end? Not quite. See, buffer overflows exist. Simply put, a buffer overflow is a vulnerability in low-level programming languages like C and C++ where the program attempts to copy data into a specific destination expecting it to be a specific size, but it never actually checks if it is that size, allowing attacker-controlled data to be written out-of-bounds. When an overflow occurs, it basically writes whatever it's trying to write into the buffer... past it. In low-level programming, buffers are always stored in either the Heap or the Stack, and directly preceding those locations in memory is the CPU registers. Because buffer overflows don't stop writing when they reach the end of the buffer, this effectively lets us modify the CPU registers, and this is where things get interesting.

With the ability to modify the CPU registers however we want, 2 big values stick out for ARM32: Stack Pointer and Program Counter. Stack Pointer, as the name suggests, is a pointer to the stack of the running program, and by setting it we can jump to arbitrary programs in memory, as long as execution is unlocked. Program Counter is a pointer to the active instruction in memory, and changing it effectively let's us run whatever function we want, as long as it's in executable memory.

Unfortunately, execution is locked by the RW Cr50 firmware after it gets control, so there's no way we could just immediately jump to a different program by changing the Stack Pointer. As for Program Counter, executable memory is defined as any code inside the active program, so we can't just copy code into the Stack and try to set Program Counter to its address. But with a chain of overflows in the program, we could change Program Counter to the address of internal functions like memcpy and memset, and set the the other CPU registers like r0-r12 to change its parameters.

So, needless to say, a buffer overflow would be incredibly powerful inside the Cr50, as with just 2 functions we can manipulate all of the memory inside the GSC, and control of memory like that effectively lets us do whatever we want. So I went searching for anything potential overflows.

RMASmoke

While I was searching for buffer overflows in the Cr50 source tree, Writable revealed that he had been holding on to something: RMASmoke. He revealed he had an out-of-bounds write in the Cr50 that could allow for the manipulation of the RMA auth code, and more. After a long while of searching for it and coming up empty handed, he eventually gave me a copy, and I was able to start researching.

The bug involved an issue in a specific implementation of TPM2 used by Cr50. When you attempt to read NV memory from the Cr50, it expects it to be no larger than 1 kilobyte in size. But, you can actually create NV spaces 2 kilobytes in size, and if you attempt to read whatever data you put in it for more than 1 kilobyte, then there's your buffer overflow.

I quickly got to work rewriting Writable's proof-of-concept into a full command line utility and started thinking of things we could do with this. Since my goal was to run custom code, the thing that immediately jumped out to me was the BootROM.

The BootROM is unique in the sense that it doesn't use any forms of memory protections. What this means is if we had an overflow in it, we could set Stack Pointer to any program in memory, we could set Program Counter to run any code even if it's not in executable memory, and more. Because of this risk, the BootROM source code is kepy highly confidential, and payloads of it are basically nonexistant.

But the BootROM's payload is in every H1, right? We just need to find a way to get it off the chip and onto the host. And conveniently, we now have a buffer overflow that can call any function in Cr50 with whatever parameters we want!


Getting the BootROM

With a buffer overflow on the Cr50, the hunt for the BootROM began. At this point the RMASmoke bug report had gone public, but nobody really knew how to use it, and most people completely ignored it, so thankfully we weren't at a race with anyone. Since we can call any function, immediately the first 2 that stuck out were memset and memcpy. With those 2 functions alone we could basically control all memory inside the H1.

With some clever pattern generation and a software simulator for the TPM2 compiled for ARM32, we can analyze the overflow as it occurs, and determine the exact number of bytes we need to write before we reach the very start of the registers. From there using a convenient python script from the EC repo to analyze the Cr50's stack, we can find the exact location and value of Program Counter and what to set it to to run the desired function.

To be finished :3

Credits and Special Thanks

- Hannah: Finding the initial vulnerability, building the payloads and injectors
- Katelyn: Fixing various bugs, helping dump the ROM, testing
- OlyB: Insight on the Cr50 firmware, fixing Hannah's spelling errors
- Evelyn: Helping decompile things, Insight on Chromebook hardware
- Kayla: Helping compile & decompile things
- Leah: Helping compile things
- Writable (unaffiliated): Discovering the exploit that let us dump the BootROM

Support & Updates

FAQ Home Page Titanium Network HavenOverflow Source Code