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 theURB 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 wLength
value 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