48UXP (sw) (This is the second part of a three part series. The first part described the hardware of this device; this part will describe the software / firmware that drives this thing, and the last part will document the modifications I made to the hardware and software to try to get it to do what I want.) There are three different programmable devices inside the 48UXP: - 80C32 MCU
- XC3030A FPGA
- Cypress EZ-USB FX2
Of these, only the 80C32 has nonvolatile code storage; the other two devices have their code uploaded by the client every time they are used.
80C32 firmware The 80C32 firmware is contained on an EEPROM chip inside the device; I pulled the chip and read it using my crappy Willem-clone programmer. It contains about 6K of code and 24K of random bits of data, 2.5K of which is a “default” bitstream to upload to the XC3030A. It’s responsible for transferring commands and data over an 8-bit pipe from the FX2 (or parallel port), programming the XC3030A over JTAG, driving the shift registers connected to the pin drivers, and transferring commands and data over an 8-bit pipe to the XC3030A. There’s a lookup table which takes a 1-byte command and jumps to a fixed pointer. There’s a command that lets you upload data to arbitrary places in the SRAM, and a command that causes execution to jump to code inside the SRAM; this lets them upload routines and run them. XC3030A bitstreams There are a number of different FPGA bitstreams in use here. One is hard-coded in the 80C32 firmware, and two hard-coded in the Windows client executable. The rest are contained in the resource files, which we will discuss shortly. Unfortunately, there’s no way to analyze these bitstreams; we just have to treat them as black boxes, and deduce their functionality from context. FX2 firmware The FX2 has a USB bootloader which accepts code over USB. The Windows client has two different firmwares embedded in it, and it seems to pick one or the other based on a device ID. It calls itself “EPP LabTool USB 2.0 Bridge Version 1.13″, and like the 80C32 firmware, it accepts one-byte commands. However, these one byte commands are mainly used … to send sequences of one-byte commands to the 80C32. We have layers and layers of protocol, here, and I sort of wish I had started looking at the parallel port version of this software before the USB version. That being said, there are much better tools available for capturing and analyzing USB traffic, and I don’t even have a parallel port! Windows client The thing has a Windows client — if you’d like, you can download a free “demo” copy from here, or more specifically, here. As with the Infectus software analysis I did, I used three tools: - IDA Pro, to analyze the executable — this proved to be less helpful this time than I had hoped
- SniffUsb (inside VMWare Fusion) — there are several free / open-source programs that do similar things with similar names; This version is the one that has worked best for me.
- usbsnoop2libusb.pl — this amazing perl script will take the output from the above program and make it into a C program that uses libusb to replay the transactions back to the device. I didn’t use it for that, directly, but it’s a nice base to use to develop code to filter the above output into something more readable.
All of the interesting bits are contained in a set of “resource files” (*.rez) that are unpacked by the installer alongside the main executable. It’s a binary container format, which was annoying to deal with — rather than try to explain the exact format, I refer you to the ugly Python script I cranked out to try to make some sense of it. The USB dumps ended up playing a critical role in understanding the binary file format, because I was able to modify the binary .rez file and observe the change in the data sent over USB. TDevPool: 02a9 07f8 001b 00 00ec 0076 00 001f 01 02 K9F1208U0A *48TS TDevPool: 02ba 07f8 001b 00 00ec 0076 00 001f 01 02 K9F1208U0B *48TS TDevPool: 02cb 07f8 0018 00 00ec 0073 00 001f 01 02 K9F2808U0B *48TS TDevPool: 02dc 07f8 0018 00 00ec 0073 00 001f 01 02 K9F2808U0C *48TS TDevPool: 02ed 07f8 001a 00 00ec 0075 00 001f 01 02 K9F5608U0B *48TS TDevPool: 02fe 07f8 001a 00 00ec 0075 00 001f 01 02 K9F5608U0C *48TS TDevPool: 030f 07f8 0017 00 00ec 00e6 00 006c 01 02 K9F6408U0A *44TS TDevPool: 0320 07f8 0017 00 00ec 00e6 00 006c 01 02 K9F6408U0B *44TS TDevPool: 0331 07f8 0017 00 00ec 00e6 00 006c 01 02 K9F6408U0C *44TS In order, the fields are table_index, device_class, device_type, ?, vendor ID, chip ID, ?, ?, ?, ?, chip name, adapter name. device_class is looked up in a table inside the executable, which gives a class of “TXPMemDev” and a corresponding resource file of “GDBA____.rez”. Let’s take a specific example from that file: D1b: TXPMemDev: famcode=0013 pinmap=000c 0000 ff 04200000 08 00 00 00 20 04 00 42 00 00 04200000 D1b corresponds to the device_type in the device list. 0×04200000 is the size of the chip — it’s a 64MB small-block NAND flash chip. 000c tells us which pinmap to use: Pc: TPinCode: 48 pins, 00000000 04 09 00 02 00 02 00 18 2f 01 00 04 00 00 17 19 00000010 29 06 00 08 00 10 11 12 13 1c 1d 1e 1f 03 00 1d 00000020 00 01 02 07 08 09 0a 0b 0c 0d 0e 0f 14 15 16 19 00000030 1a 1b 20 21 22 23 24 25 26 27 28 29 2d 2e 04 00 00000040 03 00 01 00 04 00 0e 00 05 00 0f 00 06 00 0b 00 00000050 18 00 07 00 2a 00 08 00 2b 00 03 00 2c 00 0c 00 00000060 2f 00 (I don’t know anything more about the format of this entry) We also know which “TFamCode” to use: M13: TFamCode: 0c 0000 002b _FUN_POWERON 0001 0033 _FUN_POWEROFF 0002 002d _FUN_DEVINIT 0003 0031 _FUN_DEVRESET 0004 002e _FUN_GETID 000a 0030 _FUN_ERASE 0017 0039 _FUN_DNLDERASE 0014 003a _FUN_DNLDPROG 0011 003b _FUN_DNLDREAD 0013 003c _FUN_DNLDBLKCHK 0015 003d _FUN_DNLDVERIFY 001b 003f _FUN_XC3030Data Here, we’re getting closer to something usable. The first column is an index into a list of functions (you can find it elsewhere in the file) — I’ve decoded it to put the function name on the right. This tells us what “WaveCode” we need to use. WaveCodes I’m assuming that “Wave” here is short for “Waveform”, meaning that these blobs tell the device how to generate the signals which need to be applied to each pin. There are three types: Type 0: Bytecode 0000 002b _FUN_POWERON W2b: TWaveCode: type = 0 data = 00000000 30 00 12 00 02 33 01 11 0f 00 11 04 00 11 01 00 00000010 03 0b 00 0b 0c 01 00 00 02 01 00 3b c0 3b ff 34 00000020 0f 11 03 01 11 08 01 11 0e 01 11 0f 01 32 40 0d 00000030 03 00 In my notes, I have this down as being some sort of interpreted bytecode, run on a statemachine inside the Windows program. Looking at it with fresh eyes and comparing it to the USB traffic, it may just be a list of 80C32 commands. Something that immediately stands out here is the ‘ff’ at 0x1e — that’s the RESET code in the NAND command set. Type 4: FPGA Bitstream 001b 003f _FUN_XC3030Data W3f: TWaveCode: type = 4 data = 00000000 da 0a ff 04 a0 36 f9 fc ff ff ff ff ff ff ff ff 00000010 ff ff ef f7 f7 f7 f7 f7 ef ef ef ef ef fe e6 fe 00000020 fe fe fe fe fd fd fd fd f5 cf ff ff ff ff ff fe [...] 00000ad0 ff ff ff ff df ff ff ff bf bf ff ff This bitstream is sent before anything else is done with the chip. It’s really the code to which the 80C32 talks, and it’s what actually drives the pins on the flash chip. Type 6: 80C32 code 0011 003b _FUN_DNLDREAD W3b: TWaveCode: type = 6 (805x) len1 = 0189 addr=6000 len2 = 0185 data = 00000000 90 f0 00 75 a0 c8 20 b2 fd 30 b5 03 02 61 23 75 00000010 a0 d8 e2 fc 54 f0 70 07 90 60 58 ec 23 23 73 ec 00000020 c3 94 7f 50 2c 75 e0 00 c0 e0 75 e0 60 c0 e0 90 [...] 00000180 d2 97 02 60 06 This code is uploaded to the 80C32′s attached SRAM chip at address 0×6000; it is then executed. We can disassemble this code to see how it works, and I ended up doing so to modify it, but we’ll get to that in the next article. I can post some annotated USB logs, if there’s interest. |