• Reverse Engineering LPC’s Device Firmware Upgrade Protocol

    Monday, November 11, 2013

    11/11/2013— Jia Huang

    Device Firmware Upgrade (DFU) allows a device’s firmware to be changed over USB. The alternative is having to use an external device such as a JTAG programmer. Setting up an external programmer is costly so we wanted to expose a DFU method for upgrading Tessel’s firmware.

    In order for DFU mode to actually work, the device has to:

    1. go into USB boot mode
    2. have the host (any computer) send it the firmware using the DFU protocol

    Tessel had USB boot mode enabled about 3 revisions ago because we knew we would eventually want DFU. The current revision of Tessel goes into USB boot mode if the two pins by the RAM chip are bridged. In this mode, Tessel enumerates as a USB device and is ready for host communication.

    So great, #1 is already solved. 50% of the way there! Time for a celebratory browse of /r/aww.

    And for #2, there’s a tool called dfu-util that handles host side communication for DFU. BOOM! Solved! Back to /r/aww!

    Except that it didn’t work.

    Confused and a bit annoyed, I scrounged around a bit and found this thread where NXP support says:

    When the LPC18xx and LPC43xx family of parts boot up over USB they send a DFU descriptor up to the host but they do not support the full set of DFU functions. They can only download an image into RAM and jump to it. That’s it.

    To implement a DFU driver you will need to create an image that contains this driver and download and run it as a second stage

    Oh. Well. That sucks. Then it goes on to say:

    The utility that downloads the image when the parts are booting up over USB is a C# application that sits on top of WinUSB. Source code to this application can be obtained with an NDA. Contact your FAE to do this.

    What? I have to sign an NDA? No way.

    But it did give me 2 important pieces of information – there is a protocol specific way to write to flash, and that there’s already an existing (though proprietary) tool which works on Windows.

    So breaking it down, the proper way to write new firmware over USB is:

    1. Tessel goes into USB boot mode
    2. LPC’s first stage DFU ROM is loaded into internal RAM
    3. Do something as a second stage to put on firmware

    I didn’t know what LPC’s first stage DFU ROM does, but I did know that it had to accept messages over USB somehow. I didn’t know much about USB, so I went and checked out how USB packet transfers work. The cliffnotes version can be summed up in this image:

    A USB device can have multiple configuration types (though having more than 1 is rare). Each configuration type has multiple interface descriptors. Each interface as multiple endpoints. Endpoints are what you actually talk to the USB device over.

    Now I just have to check out what endpoint I need to talk over, write to that, and presumably the LPC DFU ROM will write that information to flash. So I whipped up a quick Python script that gave me all this information:

    import sys
    import usb
    import usb.core
    import usb.util
    # find our device
    dev = usb.core.find(idVendor=0x1fc9, idProduct=0x000c)
    # was it found?
    if dev is None:
      raise ValueError('Device not found')
    # set the active configuration. With no arguments, the first configuration     will be the active one
    for cfg in dev:
      print "bLength", cfg.bLength
      print "bDescriptorType", cfg.bDescriptorType
      print "wTotalLength", cfg.wTotalLength
      print "bNumInterfaces", cfg.bNumInterfaces
      print "bConfigurationValue", cfg.bConfigurationValue
      print "iConfiguration", cfg.iConfiguration
      print "bmAttributes", cfg.bmAttributes
      print "bMaxPower", cfg.bMaxPower
      for (counter, intf) in enumerate(cfg):
        print "\t Interface number ", counter
        print "\t bLength", intf.bLength
        print "\t bDescriptorType", intf.bDescriptorType
        print "\t bInterfaceNumber", intf.bInterfaceNumber
        print "\t bAlternateSetting", intf.bAlternateSetting
        print "\t bNumEndpoints", intf.bNumEndpoints
        print "\t bInterfaceClass", intf.bInterfaceClass
        print "\t bInterfaceSubClass", intf.bInterfaceSubClass
        print "\t bInterfaceProtocol", intf.bInterfaceProtocol
        print "\t iInterface", intf.iInterface

    It gave out the following:

    Wait what, there’s no endpoints? How do I talk to it then? At this point I went and watched an episode of Adventure Time because goddammit what does this mean?

    I then re-read that USB site I linked to earlier and saw that

    Endpoint descriptors are used to describe endpoints other than endpoint zero. Endpoint zero is always assumed to be a control endpoint and is configured before any descriptors are even requested

    Nice. So I want to talk over endpoint zero because that’s the only possible endpoint that’s available. This meant that writing to firmware over USB was now:

    1. Tessel goes into USB boot mode
    2. LPC’s first stage DFU ROM is loaded into internal RAM
    3. The first stage DFU ROM is listening to endpoint 0 for the right commands
    4. The second stage writes to endpoint 0 with the proper protocol and actual firmware data

    At this point I booted into Windows and downloaded the proprietary LPC tool for DFU. The setup for it looks like this

    Here we have the first stage ROM (Algo), and the second stage program (a 160 byte length of repeating 0xDEADBEEFs). The address field is for the location in flash memory that we’re writing to. The microcontroller used on Tessel is the LPC1830. The 1830 doesn’t have any internal flash, which means all of our code is stored on Tessel’s SPIFI flash chip. The SPIFI flash is memory mapped at 0x1400 0000.

    The input fields for this tool told me a lot. I should expect at least 4 DFU transactions (one for the Algo, one for erasing the flash, one for writing the flash, and one for reading the flash back to me). Somehow the protocol had to embed 0x1400 0000 (probably during flash writing and reading) and 0x00020000 (probably during erase).

    I opened up a software based USB packet analyzer and tracked the packets as I ran the Windows tool. There were a few boring setup packets, but here’s the first interesting one

    That 0x14 byte looks promising. Looks like that section are the bytes that tell it where to erase. And the 0x02 area is the size of a region to erase. Immediately following this came a reply

    The next 2 DFU commands had these in the payload

    See that 0xA0 byte? 0xA0 in decimal is 160. So during a write command it sends over the number of bytes it needs to write every time. The only other major difference is the beginning 0x08 byte vs 0x07 for erase. So the first byte is probably which command to execute.

    And the reply from the device

    Last was the read call:

    And the device response:

    And following this was another packet that had the 160 byte 0xDEADBEEF data in it.

    Now with this information we can pretty much figure out that the protocol is sending a packet of 16 bytes which has the format

    aa 00 00 00 bb bb bb cc cc cc 00 0B 01 43 18

    Where aa is the protocol (0x07 for erase, 0x08 for writing, 0x09 for reading), bb is the memory location and cc is the length.

    I then switched back to my Mac and sent over payloads that looked exactly the same with the exception of a payload of 16 bytes of 0xDEADBEEF. Checking the memory location over JTAG gave me this:

    Success! All this just so I didn’t have to contact NXP and sign a freaking NDA. Worth it.


    #tessel #technical machine #jialiya huang #jia huang #nxp #reverse engineering #hacking #startup #jtag #dfu #company culture #usb #firmware #driver #code

August 2018

January 2018

July 2017

February 2017

November 2016

October 2016

September 2016

August 2016

July 2016

June 2016

April 2016

March 2016

February 2016

November 2015

September 2015

August 2015

July 2015

June 2015

May 2015

March 2015

February 2015

January 2015

December 2014

November 2014

October 2014

September 2014

August 2014

July 2014

June 2014

May 2014

April 2014

March 2014

February 2014

January 2014

December 2013

November 2013

October 2013

September 2013

August 2013

July 2013