Black Arrow eWallet signature checking

The Black Arrow eWallet will run signed Trezor firmware, and displays on the screen that it is Trezor firmware:

 Bootloader appears to check and affirm Trezor-signed firmware images.  The button mappings are correct.

Bootloader appears to check and affirm Trezor-signed firmware images.  The button mappings are correct.

 This works, but is probably a terrible idea.

This works, but is probably a terrible idea.

The eWallet does run unsigned firmwares, displaying the standard warning before continuing.

Remember, Black Arrow has NOT yet released source code for either their firmware or their bootloader.  It appears that the eWallet can confirm that signed Trezor firmwares are running in a trustworthy fashion, but without source andor bootloader reversing, we cannot be sure.  Black Arrow claims to be open source.  This is currently a lie.

Whether or not you agree with their business plan, BWallet has never misrepresented their product.  (As for as I know)

 

A little more on the Black Arrow eWallet.

I had a couple more minutes tonight, so I was able to break one open and briefly look at its guts.

The Black Arrow eWallet has an STM32F205RGT6 rather than the STM32F205RET6 that the Trezor and the BWallet have.  This means that the eWallet has twice the flash storage on the chip.  1MB vs 512k.

Clearly, Black Arrow has filled that extra space with nefarious HCF opcodes.

Also, the pcb is super thin, and the OLED's ribbon cable is folded pretty sharply when in the case.  There are some test pads scattered around on the board, but no clear debugging header.

That's all for tonight.  I'll try loading some Trezor firmwares on it soon.

I have yet to see Black Arrow post source code, or even firmware binaries.  So if I attempt writing a Trezor firmware on it, I'll have no way to get back to its factory state.

Black Arrow eWallets arrive

The eWallets just arrived, and I don't have time right now for any real analysis, I snapped a couple photos and I'll leave some initial thoughts at the end:

  The eWallets come in a non-shrinkwrapped, non-tamper sealed box.

The eWallets come in a non-shrinkwrapped, non-tamper sealed box.

 The back of the box says ledwallet.com, where there is nothing.

The back of the box says ledwallet.com, where there is nothing.

 Micro USB cable?!  The documentation is clearly a copy & paste from Trezor

Micro USB cable?!  The documentation is clearly a copy & paste from Trezor

 Black Arrow branding.  The build quality is decent enough.

Black Arrow branding.  The build quality is decent enough.

 From the boot procedure, it is clear that the eWallet is NOT running a bootloader and firmware compiled by SatoshiLabs (Trezor)

From the boot procedure, it is clear that the eWallet is NOT running a bootloader and firmware compiled by SatoshiLabs (Trezor)

So, Black Arrow shipped me products in a timely fashion that have not (yet) caught on fire.

Initial impressions leave me confidant that they have made modifications and customizations to Trezor code that they have not yet published.

Come on Bitcoin hardware industry, you need a better understanding of what 'open source' hardware (and software!) really means.

Stay tuned for a more thorough analysis and teardown in the coming days.

(donations accepted here: 1Adq8SP8WBWJGHyq8N3bGty8n1m9A3ms81 )

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

 

Making the buttons work on a BWallet running Trezor firmware

In the previous article, we demonstrated that the buttons on a BWallet are indeed wired differently than on a Trezor.

This means that loading a signed Trezor firmware onto a BWallet isnt very useful.  The 'unofficial firmware' warning pops up and the buttons do not work.

Perhaps the 'next best thing' would be to use the Trezor source code, modify only the lines to map the CPU pins to the buttons on the BWallet and compile ourselves.

So let's test it!

The lines to modify are in buttons.h:

-#define BTN_PIN_YES    GPIO2
-#define BTN_PIN_NO     GPIO5
+#define BTN_PIN_YES    GPIO10
+#define BTN_PIN_NO     GPIO12

Then compile as normal.

This creates the firmware image, but what gets flashed to the device requires a header on top that contains some metadata, including the signatures.  Because we have no way of creating signatures that either a BWallet bootloader nor a Trezor bootloader would validate, we can steal the header from a firmware that Trezor distributes.

Here's a terrible python script that does just that (and breaks out the header fields for no good reason):

import sys
import binascii
import struct

with open(sys.argv[1], 'r') as f:
data = f.read()

header = data[:256]
firmware = data[256:]

assert len(header) + len(firmware) == len(data)

(magic, codelen, idx1, idx2, idx3, flags, reserved, sig1, sig2, sig3) = struct.unpack("4sissss52s64s64s64s", header)

print "magic =\t%s" % binascii.hexlify(magic)
print "codelen =\t%s" % codelen
print "idx1 =\t%s" % binascii.hexlify(idx1)
print "idx2 =\t%s" % binascii.hexlify(idx2)
print "idx3 =\t%s" % binascii.hexlify(idx3)
print "flags =\t%s" % binascii.hexlify(flags)
print "rsvd =\t%s" % binascii.hexlify(reserved)
print "sig1 =\t%s" % binascii.hexlify(sig1)
print "sig2 =\t%s" % binascii.hexlify(sig2)
print "sig3 =\t%s" % binascii.hexlify(sig3)

with open("bzor.bin", "rb") as g:
payload = g.read()
g.close()

with open("bzorwithheader.bin", "wb") as h:
h.write(header + payload)
h.close()

passing in a valid trezor firmware:

$ python fwheaderswap.py trezor-1.2.1.bin
magic =54525a52
codelen =161276
idx1 =01
idx2 =02
idx3 =03
flags =01
rsvd =00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
sig1 =7e0606dde7849e5af70975c277f9cd9f1eee1d6e811f6e4ebe6b606c83fbce7403bb4145da1594718e06b13a291068a577f738b56612c01f4275040ae3eb96e3
sig2 =19812e876e6022813a957066422741c34a2cbe5a262d402aebfd654db56f8626d1d9d2b8c8a858a978ceff253909b7e3e7d9fab6aaf3338d3b113bfbd3425528
sig3 =a716ae2e4894bf9ef31c5536256df2c23b95bfacbf06f958f6fe8d9f33d841c044d6c6322a7f6504b9da208db79c19b81a1044c9e3d399b8c20455e3b3ca5acd

I should probably have fixed the magic bytes right in the script, but I did this separately like I did in the last post.

This yields this firmware that behaves exactly like a Trezor, but on a BWallet.   Let's test the buttons:

$ python cmdtr.py get_entropy 32

<clicks the button>

5fd6cd915ef841630d023a1ecbf6b2e1046d0b82e8dab80c2cf76a7919e781f4

And the buttons work!

This BWallet-Trezor hybrid will alert on boot that the firmware is 'unofficial' because the signature in the header was not created by a BWallet developer on the firmware we are running.  Instead, I pulled the code directly from the Trezor repo, made a trivial change to support the BWallet button mappings, and compiled it myself.

It is true that when it boots up and warns that the firmware is not official, I have no way of telling if its running my compiled firmware, or if an evil hacker made modifications to my BWallet when I wasnt looking.

My solution?  I won't leave my BWallet in a place where an evil hacker can get to it.

Also, I'm aware of this risk and will make informed, conscious decisions about how I use it.

Feel free to use the firmware above for yourself.  But be aware that if you do choose to use it, you are trusting that I'm not an evil hacker :)  I provide no warranty, guarantee, or promises about what will or won't happen if you choose to use it.  

I recommend you follow the steps above and compile your own BWallet-compatable Trezor firmware.

blah blah, consider donating for more hardware wallet analysis: 1Adq8SP8WBWJGHyq8N3bGty8n1m9A3ms81

Running Trezor Firmware on BWallet

The Trezor devs claim that you can run Trezor firmware on the BWallet, but due to differences the the hardware, the buttons will not work.

So let's test it!

I'm using the cmdtr.py code from python-trezor that you can use to flash firmware images to trezors.

$ python cmdtr.py firmware_update -f trezor-1.2.1.bin

Firmware fingerprint: 0f8685ee46632162b549eb22b99a1e4e013d6796ae536ea6acb877a491f564f6

True

 

Cool, no problems there.

However, when re-plugging the BWallet:

 But it works just fine on a Trezor. . .

But it works just fine on a Trezor. . .

"Firmware appears to be broken"

Let's find out why.

On line 119 of the BWallet bootloader code:

void check_firmware_sanity(void)
{
int broken = 0;
if (memcmp((void *)FLASH_META_MAGIC, "BDXW", 4)) { // magic does not match
broken++;
}

....
  
if (broken) {
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Firmware appears", "to be broken.", NULL, "Unplug your BWALLET", "and see our support", "page: mybwallet.com");
system_halt();
}
}

So the bootloader is checking for the first bytes of the firmware to be the string "BDXW", and the Trezor firmware starts with "TRZR".  This is easy enough to fix in your favorite hex editor (in vim, :%!xxd, make your edits, and :%!xxd -r to return and then save):

$ xxd trezor-1.2.1.bin |head
0000000: 5452 5a52 fc75 0200 0102 0301 0000 0000TRZR.u..........
0000010: 0000 0000 0000 0000 0000 0000 0000 0000................
0000020: 0000 0000 0000 0000 0000 0000 0000 0000................
0000030: 0000 0000 0000 0000 0000 0000 0000 0000................
0000040: 7e06 06dd e784 9e5a f709 75c2 77f9 cd9f~......Z..u.w...
0000050: 1eee 1d6e 811f 6e4e be6b 606c 83fb ce74...n..nN.k`l...t
0000060: 03bb 4145 da15 9471 8e06 b13a 2910 68a5..AE...q...:).h.
0000070: 77f7 38b5 6612 c01f 4275 040a e3eb 96e3w.8.f...Bu......
0000080: 1981 2e87 6e60 2281 3a95 7066 4227 41c3....n`".:.pfB'A.
0000090: 4a2c be5a 262d 402a ebfd 654d b56f 8626J,.Z&-@*..eM.o.&

$ xxd MODtrezor-1.2.1.bin |head
0000000: 4244 5857 fc75 0200 0102 0301 0000 0000BDXW.u..........
0000010: 0000 0000 0000 0000 0000 0000 0000 0000................
0000020: 0000 0000 0000 0000 0000 0000 0000 0000................
0000030: 0000 0000 0000 0000 0000 0000 0000 0000................
0000040: 7e06 06dd e784 9e5a f709 75c2 77f9 cd9f~......Z..u.w...
0000050: 1eee 1d6e 811f 6e4e be6b 606c 83fb ce74...n..nN.k`l...t
0000060: 03bb 4145 da15 9471 8e06 b13a 2910 68a5..AE...q...:).h.
0000070: 77f7 38b5 6612 c01f 4275 040a e3eb 96e3w.8.f...Bu......
0000080: 1981 2e87 6e60 2281 3a95 7066 4227 41c3....n`".:.pfB'A.
0000090: 4a2c be5a 262d 402a ebfd 654d b56f 8626J,.Z&-@*..eM.o.&

 

Then cmdtr.py to push your modified firmware:

$ python cmdtr.py firmware_update -f MODtrezor-1.2.1.bin 
Traceback (most recent call last):
File "cmdtr.py", line 400, in <module>
main()
File "cmdtr.py", line 392, in main
res = args.func(cmds, args)
File "cmdtr.py", line 185, in firmware_update
raise Exception("Trezor firmware header expected")
Exception: Trezor firmware header expected

Failed.  cmdtr.py is checking for a 'TRZR' in the firmware header, but we just changed that.  Comment out the check in firmware_update:

def firmware_update(self, args):
if not args.file:
raise Exception("Must provide firmware filename")
fp = open(args.file, 'r')
#if fp.read(4) != 'TRZR':
#raise Exception("Trezor firmware header expected")

Now pushing the modified firmware should work:

$ python cmdtr.py firmware_update -f MODtrezor-1.2.1.bin 
Firmware fingerprint: 0f8685ee46632162b549eb22b99a1e4e013d6796ae536ea6acb877a491f564f6
True
 That's a Trezor logo on a BWallet (after an unofficial firmware warning message)

That's a Trezor logo on a BWallet (after an unofficial firmware warning message)

And after dismissing a warning about running an 'unofficial' (not signed by BWallet) firmware, the Trezor firmware (signed by Trezor) appears to be running on a BWallet.  Lets try something requiring a button press:

$ python cmdtr.py get_entropy 1024
 My eyes, the button&nbsp;does nothing!

My eyes, the button does nothing!

And the button does nothing.

So, now we've demonstrated that the BWallet isnt an exact duplicate of the Trezor; at a minimum the buttons are wired differently.

This can also be inferred from the source code, highlighted nicely by imahotdoglol:

BWallet button.h
#define BTN_PORTGPIOC
#define BTN_PIN_YES GPIO10
#define BTN_PIN_NOGPIO12

Trezor button.h
#define BTN_PORTGPIOC
#define BTN_PIN_YES GPIO2
#define BTN_PIN_NOGPIO5

But it was _possible_ that the buttons were wired to both the Trezor and BWallet pins in hardware, allowing for firmwares from either manufacturer to work.

Hopefully, I've been able to describe clearly what I've done to come to a conclusion.  My goal is to show and explain in enough detail for you to believe me, but also provide enough detail so anyone can duplicate my results for themselves.

You know, be open.  So you can exactly copy my work, if you so desire  :)

Next, I'm going to try to work around this 'button not working' problem two different ways, one in hardware and one in software.

If you enjoy this type of analysis, consider donating to me at 1Adq8SP8WBWJGHyq8N3bGty8n1m9A3ms81 .  I've purchased several Trezors and BWallets and other supplies to perform teardowns and experiments.

Also, please ask any questions I can help answer in the comments.

Thanks!