Dumping the bootloader from Trezor / BWallet

Now that we know a bit about the firmware running on the Trezor and BWallet and can compile our own and flash that to the device, Let's learn a bit more about the memory layout of the device.

From memory.h:

flash memory layout:
name | range | size | function
-----------+-------------------------+---------+------------------
Sector 0 | 0x08000000 - 0x08003FFF | 16 KiB | bootloader code
Sector 1 | 0x08004000 - 0x08007FFF | 16 KiB | bootloader code
-----------+-------------------------+---------+------------------
Sector 2 | 0x08008000 - 0x0800BFFF | 16 KiB | metadata area
Sector 3 | 0x0800C000 - 0x0800FFFF | 16 KiB | metadata area
-----------+-------------------------+---------+------------------
Sector 4 | 0x08010000 - 0x0801FFFF | 64 KiB| application code
Sector 5 | 0x08020000 - 0x0803FFFF | 128 KiB | application code
Sector 6 | 0x08040000 - 0x0805FFFF | 128 KiB | application code
Sector 7 | 0x08060000 - 0x0807FFFF | 128 KiB | application code
===========+=========================+============================
Sector 8 | 0x08080000 - 0x0809FFFF | 128 KiB | N/A
Sector 9 | 0x080A0000 - 0x080BFFFF | 128 KiB | N/A
Sector 10 | 0x080C0000 - 0x080DFFFF | 128 KiB | N/A
Sector 11 | 0x080E0000 - 0x080FFFFF | 128 KiB | N/A

metadata area:

offset | type/length | description
--------+-------------+-------------------------------
0x0000 | 4 bytes | magic = 'TRZR'
0x0004 | uint32 | length of the code (codelen)
0x0008 | uint8 | signature index #1
0x0009 | uint8 | signature index #2
0x000A | uint8 | signature index #3
0x000B | uint8 | flags
0x000C | 52 bytes | reserved
0x0040 | 64 bytes | signature #1
0x0080 | 64 bytes | signature #2
0x00C0 | 64 bytes | signature #3
0x0100 | 32K-256 B | persistent storage
 

The bootloader is the first code to run when the device boots.  It checks the signatures on the firmware, alerts the user if they do not match, and starts the firmware ('application code' section)

While the firmware is user updatable and new versions are distributed by the wallet manufacturers for users to be able to inspect the bytes that that are flashing onto their device, the bootloader is written onto the device in the factory and there is no easy way to inspect the running code.

There have been reddit comments claiming that you could create a firmware that dumps the bootloader.

So let's test it!

I modified the firmware source to change the functionality of the get_entropy command to return 1024 bytes of memory at a given offset, and not prompt for a button press:

diff --git a/firmware/fsm.c b/firmware/fsm.c
index b91f9fa..c99889c 100644
--- a/firmware/fsm.c
+++ b/firmware/fsm.c
@@ -238,19 +238,22 @@ void fsm_msgFirmwareUpload(FirmwareUpload *msg)
 
 void fsm_msgGetEntropy(GetEntropy *msg)
 {
+#if 0
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "send entropy?", NULL, NULL, NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Entropy cancelled");
layoutHome();
return;
}
+#endif
RESP_INIT(Entropy);
- uint32_t len = msg->size;
- if (len > 1024) {
- len = 1024;
- }
- resp->entropy.size = len;
- random_buffer(resp->entropy.bytes, len);
+ uint32_t offset = msg->size;
+ //if (len > 1024) {
+ //len = 1024;
+ //}
+ resp->entropy.size = 1024;
+ //random_buffer(resp->entropy.bytes, len);
+memcpy(resp->entropy.bytes, (void *)(0x08000000 + offset), 1024);
msg_write(MessageType_MessageType_Entropy, resp);
layoutHome();
 }

I compiled and flashed to the device.

To get the bootloader bytes from the hardware device, I used the following python script, modified from the example script in the python-trezor repo:

#!/usr/bin/python

from trezorlib.client import TrezorClient
from trezorlib.transport_hid import HidTransport

def main():
# List all connected TREZORs on USB
devices = HidTransport.enumerate()

# Check whether we found any
if len(devices) == 0:
print 'No TREZOR found'
return

# Use first connected device
transport = HidTransport(devices[0])

# Creates object for manipulating TREZOR
client = TrezorClient(transport)

bootloader = ""

offset = 0

for x in range(32):
print("Dumping offset: %i" % offset)
bootloader += client.get_entropy(offset)
offset += 1024

client.close()

f = open("bootloader.bin", "wb")
f.write(bootloader)
f.close()

if __name__ == '__main__':
main()

And it works!

Stay tuned for a brief analysis next time.

There certainly are 'better' ways of doing this, but this was the method I came up with first.

Standard warnings apply, don't muck around with this type of modifications on a device that contains value.

If you like this, consider contributing here: 1Adq8SP8WBWJGHyq8N3bGty8n1m9A3ms81