The Titan M chip was put onto Pixels in 2018. This chips main purpose is to reduce the attack surface for attackers. This chip is on a separate SoC that runs its own special firmware and communicates to the main chip via the SPI bus. The point of the chip is to hold secret information, such as a hardware backed keystore.
The firmware runs a lightweight open source operating system called
Embedded Controller. The OS acts as a Real Time Operating System (RTOS) with only the concepts of
tasks. It only have a fixed stack size and no heap. Additionally, it has Nx but no other binary protections, such as ASLR or stack canaries.
The authors decided to fuzz the different services offered by the Titan M chip for memory corruption vulnerabilities. They tried two approaches: black-box fuzzing and emulation based fuzzing. The black-box fuzzing was simple to do since the tasks use ProtoBuf, allowing for the built in libprotobuf-mutator to be used. ProtoBuf definitions have to be around somewhere in order for this to be usable. They found a few bugs with this but because they couldn't just hook GDB up to the OS, it was much harder to find deep bugs.
Emulation based fuzzing was more fruitable on this project, since the firmware was publicly available. After reverse engineering the firmware some more, they found the proper place to put the inputs in order to mutate the proper things for fuzzing. To emulate and fuzz, they used AFL++ and their Unicorn emulation mode. One issue with emulation based fuzzing is that hardware-dependent functions must be hooked to have no functionality in order to still run.
After fuzzing for a while, they found a crash in the call to ImportKey. The parameters were simply taking a large index and writing the value 1 to this. This vulnerability looks small but can be triggered multiple times. Additionally, since the memory is completely static and this is a relative write, lots can be done with this.
Before they wrote the exploit, they had to find a reasonable way to debug this... without GDB or some debugger. The chip has UART debug output on it. If they could run code that would print to this log, it would be possible to learn some information about the system. Attacking these chips is hard not because of the complex vulnerabilities but because of the non-standard exploitation environment providing other challenges.
What do we even overwrite with a single 1 that would be useful? After writing Ghidra scripts looking for good targets they found the structure KEYMASTER_SPI_DATA, which contains information about the messages going back to Android. By overwriting a pointer to this structure from 0x192c8 to 0x101c8, later incoming requests will be written to this location! Since this is a valid address, this gives us a much better primitive for writing.
They noticed that writing a valid code address to 556 bytes after the payload for a KeyMaster operation allowed them hijack control flow. This was tested by force printing logs to the UART console. From there, they wrote a ROP chain with a complicated stack pivoting gadget.
The only way we could return our leaked bytes back to Android was to copy them in the response of an SPI command. To do this, they needed to be in the context of the handling, which was essentially in a jump table. To make this work, they used the previous calls to setup a stack frame with a SINGLE gadget to move the stack. By doing this, they could write to a location where the data wouldn't be changed between execution of tasks. Eventually, another call could be made, using the previous data as the final ROP chain.
The final ROP chain called memcpy with the user-provided arguments, allowing them to read in the Keymaster SPI response buffer. Then they could jump back to the Keymaster stack, like the normal command handler would have done, to return the data. This allowed them to dump all of the secrets on the chip. Overall, great post on vulnerability discovery and exploitation!