to be presented at Breakpoint Melbourne 2012,
continuing Emulating USB Devices with Python,
with thanks Sergey Bratus and the Dartmouth Scooby Crew.
Ever since breaking the MSP430's UART BSL back in '07, I've loved attacking bootloaders, particularly those in Masked ROM. A good bootloader exploit exposes the firmware of anything using that chip, drastically reducing the amount of work I need to do on a given target. As an alternative when a bootloader exploit isn't available, I've found it handy to reverse engineer firmware updater applications to get firmware images.
Toward that end, I'm happy to announce that USB Device Firmware Update emulation is working on the Facedancer Board, so you can emulate DFU devices in order to catch firmware updates as they are sent from a USB host. In many cases, this will require a bit of patching for your specific target, but it's damned handy when you haven't got the time to reverse engineer a firmware updater.
In this brief article, I will explain how the Device Firmware Update (DFU) protocol works under the hood, by walking you through the one that I wrote for the Facedancer hardware. As an example target, we will be catching the firmware update for one of Mike Ossmann's Project Ubertooth boards by emulating one well enough to fool the ubertooth-dfu tool that Jared Boone has contributed to that project.
A Child's Guide to USB Device Firmware Updates, Suitable for Adults
USB DFU is a protocol for reflashing devices. By recording and replaying such an update, it is possible to port a firmware update utility to a second operating system, to patch a device's firmware, or to extract a copy of firmware for reverse engineering. If none of these things interest you, feel free to stop reading.
First and foremost, you should understand that the DFU mode is usually a secondary function of a USB device. In emulating such a device, you might need to emulate enough of its legitimate protocol that the host believes that (1) the device is the device that it intends to reprogram and that (2) the device's firmware is out of date and needs to be replaced. Exceptions include devices with a DFU recovery mode.
Second, you should know that the host might attempt to read back from the device, such as to verify that an erasure was successful. As every dialect of DFU seems to do this slightly differently, you might need to patch your implementation to support such features.
Facedancing as USB DFU
The Facedancer acts as a minimal USB Device Firmware Update emulator with the goodfet.maxusbdfu client. Typically, the command needs to be told which style of chip to emulate, where to save the output, and, optionally, what to use as the prior firmware for read attempts.
To emulate a typical victim with your Facedancer, just run 'goodfet.maxusbdfu foo bar' where foo is the hexadecimal Vendor ID and bar is the Product ID. Then plug the Victim end of your Facedancer into the target machine and order a firmware update, the blocks of which will be printed as hex to stdout.
First, we need to know the Vendor ID and Product ID of our target. These are given for the default firmware by 'lsusb' as FFFF:0004. If you are lucky, most commonly with low-volume devices, you'll find a VID/PID pair that comes from the chip manufacturer, such as 0483:DF11 for an STM32. Sometimes the device enumerates differently for DFU than for general use, so expect surprises here.
The default USB listing for the Ubertooth is below. Note that by default it doesn't show any DFU support. Support only appears when the device is put into flashing mode with 'ubertooth-util -f'.
When switched into DFU mode, the device changes its USB device descriptor to indicate DFU support. Be sure to remember this when reverse engineering your own devices, as they might support DFU but not advertise it.
A Tourist's Phrasebook for DFU
In order to read the goodfet.maxusbdfu source code, it's handy to know at least the basics of the protocol. In this section, I'll give you an informal description of it.
DFU consists of SETUP queries, which have a standard set of header parameters. Some chips implement extra commands, particularly when they have too large an address space for the limited offsets allowed by the UPLOAD (2) and DNLOAD (1) commands. Generally, the bmRequestType will be 0xA1 and the bRequest will be one of the following.
The GETSTATE (0x05) command will often come first. The ten status from the Bluetooth's DFU client are as follows, but you can often get by with always returning the dfuIDLE (0x02) response. GETSTATE always wants a single byte as its reply, and as with the other DFU commands, all of this runs over the Setup endpoint as a Class request.
#From the ubertooth-dfu source code.
Additionally, you'll need to support GETSTATUS (0x03) to let the host know the UPLOAD and DNLOAD requests have completed successfully. This one is really easy, just return six bytes of zeroes in response to any request.
The DNLOAD (0x01), like all others, is over Endpoint 0. Its data payload consists of the data to be written, but the address gets to be a bit complicated. Rather than give an absolute address, DFU clients merely provide a 16-bit block number in the wValue field of the Setup request. On the Ubertooth's NXP LPC1756 chip, the address is simply the base address of flash memory plus 256 times the block number. Other chips, such as the STM32, have an extra command that specifies the base address, but these commands are non-standard and will need to be implemented specific to the device.
Recap and Basic Usage
If you've followed along so far, don't worry about being a little lost. Let's step back a bit and actually capture a firmware image, using the default script. In the next section, we'll get back to the nuts and bolts in order to capture a slightly trickier update.
In one window, start the DFU emulator on your Facedancer with 'board=facedancer11 goodfet.maxusbdfu ffff 0004'. You'll see the device warm up and then appear on lsusb listings of the victim machine.
Finally, send a DFU update to our fake Ubertooth board by running 'sudo ./ubertooth-dfu --write bluetooth_rxtx.dfu'. You should see packets scroll across the screen that look like the ones below. Pipe them to a file and you'll have a record of everything that would've been written into the device, enough to make a patch or begin reverse engineering with IDA.
Complications of Entering DFU Mode
Thus far, we've been emulating a device that is *already* in DFU mode, but in the real world, few devices ship that way.
For example, the following is an error message caused by using the naive DFU emulator script presented earlier with a VID:PID of 1d50:6000. The update script is failing because it orders the USB device to enter DFU mode, but the Facedancer doesn't know how to respond. When emulating closed-source devices, you'll run into the same issue.
In order to patch this issue, I looked at the emulator's log to see that it blindly accepted a vendor request without knowing what to do.
On a real Ubertooth device which appears as 1d50:6002, command 19 causes the board to disconnect and launch the DFU application, after which it reappears as ffff:0004. On more complicated devices, you might need to reply with a version number less than the one you wish to receive.
On many devices, such as those newfangled iPods and iPhones, a DFU recovery mode can be entered by holding a particular key combination. To emulate those devices, just hold the key combo and use lsusb to find the right settings for your Facedancer.
Complications of Non-Standard Extensions
If you've dealt with bootloaders before, you'll notice that quite a bit is missing from the DFU protocol as I've described it here. There's been no mention of any way to write to an address except by its block number, nor any mention of commands to erase the device or to enable protective modes. That's because these features are not standard; they are implemented differently for every host.
The STM32, for example, implements special features as writes to block 0000. If you see `BLOCK 0000 : 41' in your log, that means that the host has ordered the device to erase all of Flash memory, leaving only the bootloader that is in masked ROM. A write of 'BLOCK 0000 : 21 ef be ad de' orders the device to execute code at 0xdeadbeef.
Finally, you'll run into trouble with the DFU states, as some clients demand particular states at particular times. You can recognize this condition when goodfet.maxusbdfu repeatedly logs "Returning state of XX." Just patch the relevant code to provide the expected status, and all should be well.
In keeping with Mr. Ossmann's strongly-worded request, I humbly ask you to solder up a Facedancer and join me in emulating all sorts of nifty devices.
As usual, patches should be sent to myself or the goodfet-devel mailing list. PCBs are available free or at cost, as described on the Ordering Page of the GoodFET Project. Assembly instructions can be found on the Facedancer11 Page.