-
Notifications
You must be signed in to change notification settings - Fork 34
Reverse Engineering A USB HID RFID Reader Writer Part 2
In the first part we captured the following payloads:
Read operation
01 00 00 00 00 00 08 00 aa 00 03 25 00 00 26 bb
Beep
01 00 00 00 00 00 08 00 aa 00 03 89 01 01 8a bb
Write operation
01 00 00 00 00 00 1f 00 aa 1a 21 00 01 01 02 4d 49 96 02 d2 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fb bb
The three payloads have a couple of things in common.
- Character 0xAA at position at the end of the header
- Character 0xBB at the end of the payload
Those two are control codes used to mark the end of the header (start of message [EOM]) and the end of the text (end of message [EOM])
- A value at position 1 after EOM.
This number is the length of the message:
-- Read message length (0x03 --> 3)
[25 00 00]
-- Beep message length (0x03 --> 3)
[89 01 01]
-- Write message length (0x1a --> 26)
[21 00 01 01 02 4d 49 96 02 d2 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00]
- A value before the EOM
This is the payload CRC. We have already seen this in the write payload, but as we will see in the next section it is present and calculated in every payload.
In the first tutorial I made the following assumption:
CRC = 0xb9 XOR cid XOR uid_b3 uid_b2 uid_b1 uid_b0
And for the example used there:
CRC = 0xb9 ^ 0x4d ^ 0x49 ^ 0x96 ^ 0x02 ^ 0xd2
where 0xb9 was a hardcoded "initial value".
This worked, but It was not correct. That initial value is actually the result of xoring the first and the last bytes of the payload, along with the length:
Data:
[21 00 01 01 02 4d 49 96 02 d2 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00]
Length: 1a
0x1a ^ 0x21 ^ 0x01 ^ 0x01 ^ 0x02 ^ 0x80 = 0xb9
So, basically, the CRC routine will look like this:
CRC = XOR([data_length] + [data])
For that particular example would be:
0x1a ^ 0x21 ^ 0x01 ^ 0x01 ^ 0x02 ^ 0xb9 ^ 0x4d ^ 0x49 ^ 0x96 ^ 0x02 ^ 0xd2 ^ 0x80 = 0xfb
Analog procedure for the beep and read operations:
Data (beep):
[89 01 01]
Length: 3
CRC = 0x03 ^ 0x89 ^ 0x01 ^ 0x01 = 0x8a
Data (read):
[25 00 00]
Length: 3
CRC = 0x03 ^ 0x25 = 0x26
Now that we know how to properly calculate the CRC for any payload data, we could try to send to the device every possible command (1 byte) and check for the expected response. Let's use the writing routine explained in first part as an example:
Control Transfer Set Request
bmRequetType = 0x21
bRequest = 9
wValue = 0x301
wIndex = 0
wLength = 256
Data = 01 00 00 00 00 00 08 00 aa 00 03 89 05 01 8e bb
Control Transfer Get Request
bmRequetType = 0xa1
bRequest = 1
wValue = 0x302
wIndex = 0
wLength = 256
Response Data: 03 00 00 00 00 00 00 00 02 00 02 00 80 82 03
Control Transfer Set Request
bmRequetType = 0x21
bRequest = 9
wValue = 0x301
wIndex = 0
wLength = 256
Data = 01 00 00 00 00 00 1f 00 aa 1a 21 00 01 01 02 4d 49 96 02 d2 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fb bb
Control Transfer Get Request
bmRequetType = 0xa1
bRequest = 1
wValue = 0x302
wIndex = 0
wLength = 256
Response Data: 03 00 00 00 00 00 00 00 02 00 02 00 80 82 03
The above includes a write command and a beep command, and summarizing it does the following:
1 - Set feature report 1 (beep command)
2 - Read from feature report 2
3 - Set feature report 1 (write command)
4 - Read from feature report 2
#2 and #4 return the following sequence of bytes:
03 00 00 00 00 00 00 00 02 00 02 00 80 82 03
And if you set feature report 1 containing an invalid CRC you will get this response:
03 00 00 00 00 00 00 00 02 00 02 00 8F 8C 03
So, we could iterate through every possible 1 byte length command and check the value at position 12:
from rfidhid.usb_hid import HID
from rfidhid.core import RfidHid
from time import sleep
def main():
try:
# Try to open RFID device using default vid:pid (ffff:0035)
rfid = RfidHid()
except Exception as e:
print(e)
exit()
# Initialize device
print('Initializing device...')
rfid.init()
sleep(2)
cmds = []
payload = [0x00] * 0x03 # Set length to 3
payload[0x01] = 0x01
payload[0x02] = 0x01
for cmd in range (0x00,0xff):
payload[0x00] = cmd
buff = rfid._initialize_write_buffer(payload)
# Write Feature Report 1
response = rfid.hid.set_feature_report(1, buff)
if response != rfid.BUFFER_SIZE:
raise ValueError('Communication Error.')
# Read from Feature Report 2
response = rfid.hid.get_feature_report(2, rfid.BUFFER_SIZE).tolist()
cmd_found = ""
if response[12] != 143 :
cmd_found = "CMD FOUND! " + hex(cmd)
cmds.append(cmd)
print('CMD: ' + hex(cmd) + ' response: ' + str(response) + ' ' + cmd_found)
sleep(0.02)
print('Found ' + str(len(cmds)) + ' commands:')
print([hex(x) for x in cmds])
if __name__ == "__main__":
main()
The execution of the above script returns these results:
Found 3 commands:
['0x21', '0x25', '0x89']
So, it looks like there are no other supported commands available (at least for my device)
The beep payload data looks like this:
[89 01 01]
First value is the command, and the other two are parameters. So, we could expect that changing those parameters would affect the beep (e.g. length, repetition). Using a similar approach as the one used for trying to find available commands, we could iterate through every possible parameters combination. I have already tried that, but I didn't get any different result (just one short beep).