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