FirmwareDetails - Manouchehri/smi2021 GitHub Wiki

Summary

Firmware details and analysis for DC60

Ripping

The easiest method for obtaining the DC60 firmware can be found here: GettingStarted#Extracting_firmware

If you want to extract the firmware the hard way, just keep reading :)

The firmware can be ripped from any USB logfile, by using the information provided in the FirmwareUpload page.

If you are using the usbsnoop log in the repository, these commands will rip out the firmware for you. (The sed command removes the first two lines, if you got your log from somewhere else, you will have to check usbsnoop_firmware.log, to see that every 4th line starts with 05 ff, and maybe skip the sed step)

#> grep --extended-regexp --only-matching --regexp="([0-9a-f]{2} ){15}[0-9a-f]{2}" usbsnoop.log > usbsnoop_firmware.log

# This is optional
#> sed -i '1,2d' usb_firmware.log

# Remove the leading 05 ff
#> sed -i "s/^05 ff //g" usbsnoop_firmware.log

#> wc usbsnoop_firmware.log
484  7502 22506 usbsnoop_firmware.log

And now you have a text file with 7502 hex numbers. You will have to convert this to a binary. I've made a small program to handle this, it's in the tools folder, called text2bin.

To get this file to work with text2bin, you will have to run one more sed command:

sed -e "s/\([0-9a-f]\{2\}\)/0x\1/g" -e "s/$/,/g" -e "s/ /,/g" -i usbsnoop_firmware.log

Now, just follow the instructions in the text2bin README file!

Disassembly

After ripping the 3C firmware, you should have a binary of 7502 bytes. This file is an AVR binary, and can be disassembled with the following:

avr-objdump -b binary -m avr -EL -D somagic_firmware.bin > somagic_firmware.asm

AVR Notes

AVR 16-bit register Value
X r27 : r26
Y r29 : r28
Z r31 : r30
Stack pointer 0x3e:0x3d

Analysis

The first 4 bytes are an AVR JMP instruction that jumps to 0x19c. Then there is a lot of padding, and an unreachable JMP instruction, followed by more padding.

The data area starts at offset 0x60.

At 0x84 there is a struct that contains the whole USB device description. This is the info displayed by lsusb after the firmware is uploaded to the device. The last part of this struct is 3 USB String descriptors, and the whole struct ends at 0x155.

The main firmware program starts at 0x19c. Program description:

Start End Description
0x19c 0x1a3 Set the stack pointer to memory address 0x0fff.
0x1a4 0x1ab Set contents of memory address 0x0fbf to 0xaa.
0x1ac 0x1bd Zero contents of memory addresses 0x019c to 0x02f2.
0x1be 0x1bf Set contents of memory address 0x02f3 to 0xaa.
0x1c0 0x1d7 Copy the firmware data area from firmware offsets 0x60 to 0x19b into device SRAM memory addresses 0x60 to 0x19b.
0x1d8 0x1db Subroutine call to offset 0x866.
0x1dc 0x1dd Tight loop. Due to an endless loop at 0x8aa to 0x91d, the code at 0x91e to 0x921 that returns from the 0x866 subroutine is unreachable, thus this code is also unreachable.

Unreachable Code

I've not looked to deep into this, but i don't think all this is unreachable parts of the code. I know some of the functions are interrupt handlers, and some of the infinite loops are waiting for data or other hardware related actions -jonarne July 20. 2012

There appear to be several blocks of unreachable code:

Start End
0x2c 0x2f
0x1dc 0x1dd
0x884 0x885
0x91e 0x921
0x9e8 0xa01
0xa50 0xa6f
0xccc 0xce3
0x1212 0x1215
0x122e 0x1279
0x12da 0x12fd
0x1c62 0x1c69
0x1c9e 0x1ca3
0x1d08 0x1d25

A smal example of what the firmware does upon receiving usb data

This is based on the disassembly of the firmware for the DC60 Firmware

Let's assume the we send this usb packet from the host computer:

(This is part of a Wireshark capture, we send theese two bytes to the device just before we start capturing video)

	URB setup
		bmRequestType: 0x40
			0... .... = Direction: Host-to-device
			.10. .... = Type: Vendor (0x02)
			...0 0000 = Recipient: Device (0x00)
		bRequest: 1
		wValue: 0x0001
		wIndex: 0
		wLength: 2
	Leftover Capture Data: 0105

URB-Setup bytes:

    0x40 0x01 0x01 0x00 0x00 0x00 0x02 0x00

URB-Data bytes:

    0x01 0x05

The URB Setup is a defined standard of the USB Protocol

Mainloop

In my research, I've come to the conclusion that the main event loop of the device is looping from 0x8aa to 0x91d. In this loop at 0x8ca we find "call 0xdde".

0xdde

0xdde seems to be the basic usb handler in the device.

The usb handler function seems to be operating like a state-machine, and the current state is stored in 0x0073. Upon receiving our usb packet, we assume this state to be 0x00.

0x0073 - States
0x00 Ready
0x01 Prepared Dev-to-Host struct
0x02 Prepared Host-to-Dev struct
0x03 Unknown
0x04 Some kind of error
0x05 (Possibly?) Waiting for more data

The current state is checked at 0xdf7 if the current state is 0x00, the device will jump to 0xdfc. Here the device does the first actual check to see if it has received any usb data. This is done by checking Bit 7 of Port 0x1518. If this bit is CLEAR, the function will return to the mainloop after some cleanup.

Now, after we have sent our data, this bit will be SET, and the device will continue at 0xe04. After loading register r16,r17 with the magic number 0x0008, and register r18,r19 with 0x02d8 we call function 0x1116.

0x1116

This function takes the two 16 bit numbers as arguments, and copy r16,r17(8) bytes of data from address 0x2000 to the address pointed at by r18,r19 (0x02d8).

The data we are copying is the whole URB Setup struct that we received from the host, so a part of the device memory should look like this when this function returns:

bmRequestType bRequest wValue(LOW) wValue(HIGH) wIndex(LOW) wIndex(HIGH) wLength(LOW) wLength(HIGH)
0x02d8 0x02d9 0x02da 0x02db 0x02dc 0x02dd 0x02de 0x02df
0x40 0x01 0x01 0x00 0x00 0x00 0x02 0x00

notice that there are several 16Bit numbers in this struct

Back at the usb handler function at 0xe0e, we CLEAR Bit 7 of Port 0x1518 to notify the usb controller that we have handled the request.

I'm not sure what to make of the data between 0xe18 and 0xe29, we check the value in 0x02e9, and if it's 1, we CLEAR Bit 7 of Port 0x151C. I do suspect that 0x151C[7] is USB-OUT Data Register, and by CLEARING it, we tell the hardware that there is no valid data on the bus.

Anyhow, at 0xe2a we begin to parse the received URB Setup struct. At this point, we can only rely on the two first bytes of this struct. The remaining 6 bytes might have different meanings depending of what kind of usb message this is.

The firmware first looks at the bmRequestType which is a bit-map. First we mask Bit 5 & Bit 6, this tells us what type of request this is. The possible values are:

Standard Class Vendor Reserved
00 01 10 11

The message we send from the host, is - as we can clearly see - of type "Vendor". This causes the firmware to skip the break instruction at 0xe32, and continue executing at 0xe34.

We now check Bit 7 to see if the host is trying to send data to the device, or if the host is asking the device to send data back. If the bit is SET, the message is of type "Device to Host", and if it's CLEAR the message is "Host to Device". We can again see in the printout above that this message is of type "Host-to-Device", and this causes the firmware to take the jump at 0xe3a, and we will end up at 0xe42 with a call to a function at 0x3c2.

0x3c2

This is a rather short function, which spans only 20 lines of assembly, and return at 0x3fe.

The function is used to setup some internal structs for the further processing of the received URB. It will:

  • Load 0x02ec & 0x02ed as pointer to the address 0x01c5, this address will later contain the main data part of the URB recieved from the host.
  • Load 0x02f0 & 0x02f1 with the "wLength" value of the URB Setup struct. That is, the value currently stored in 0x02de & 0x02df, and is the size of the data expected to be loaded into 0x01c5
  • Load 0x02ee & 0x02ef with 0x0000. (I've not figured out why yet.)
  • Load 0x0072 with 0x00. (Don't know why this is either.)
  • Finaly, it will change 0x0073 (which is the current state of the URB parser function) to 0x02, telling the parser that it's parsing a "Host-to-Device" URB

When the function returns, the firmware will execute the jump instruction at 0xe46 and jump to 0xece to check if the current state is 0x05 (I'm not sure what this state is, but i suspect it is set if the device needs more data from the host before it can continue).

Well, as we just set the state to 0x02, we can assume that the firmware will take the jump at 0xed4, and we will end up at 0xeec. Here we check to see if the state is 0x04 which is only set if there is something wrong. If the state was 0x04 the function would do some cleanup, and then return.

Well, we are still at state 0x02, so we will take the jump at 0xef2, and get to the check at 0xefa. Here we check if the state is 0x01, which we know it's not, so we end up at 0x1012. We see that the state is 0x02, and this takes us to 0x101c.

0x101c

Since this part of the function is used for both Vendor & Standard requests, the first lines of this function will compare the the 16bit value in the wLength field of the URB-Setup struct to the size of the data that is stored in 0x01C5. Since we already set these two values to be the same in the function at 0x3c2, we will take the jump at 0x1030, and end up at 0x1038.

We load registers r20,r21 & r22,r23 with the wLengthvalue of the URB-Setup struct. Then we wait at 0x1044 until Bit 7 of 0x1518 is set. This is the beginning of a data copying loop.

We load the size of the Data struct we prepared in the function at 0x3c2 into registers r24,r25. (That is at this time the same as the wLength value from the URB-Setup struct we received from the Host.)

At 0x105a we take the jump because the size we are receiving from the host (2 Bytes) is less than 64 bytes. This brings us to 0x10b2.

We setup register r16,r17 & r18,r19 with the arguments needed to call the function at 0x1116 and call the function to copy 2 Bytes from 0x2000 to 0x01c5. We have now loaded 0x01 & 0x05 into 0x01c5 & 0x01c6.

At 0x10c4 we add the size of our data to the 16bit value stored in 0x02ee & 0x02ef. This number now becomes 2 since we set it to zero in the function at 0x3c2. We CLEAR Bit 7 of 0x1518 to notify the device that we have copied data out of 0x2000, and it can receive more data. Then at 0x10ea we check if we should be expecting more data from the host by comparing 0x02ee & 0x02ef with the value wLength. Since both these numbers are 2, we break out of the loop and continue executing at 0x10fa.

We have now copied the URB-Setup struct to 0x02D8 -> 0x02DF, and our two data bytes 0x01 & 0x05 to 0x1c5 -> 0x01c6. We have the size of this data in 0x02F0 -> 0x02F1, and we have a pointer to this data in 0x02EC -> 0x02ED.

0x10fa

Here, we set the state of the parser 0x0073 to 0. This means that we have copied all data out of the usb-bus and the usb-parser can wait for a new usb-message.

At the moment i cannot understand the reason for the AND instruction at 0x1100, this instruction will always return 0x00 since we already set r2 to zero, and it's just and'ed with it self. This also means that we will never take the branch at 0x1102, so we just continue executing at 0x1104.

We check Bit 7 of 0x02d, that is the URB-Setup structs bmRequestType. Since we are handeling a Host-to-Dev message, this bit is indeed cleared, so we skip the RJMP instruction, and end up at 0x110c with a call to 0x400

02 August 2012 To be continued --Jonarne