Challenge string calculation

Post date: Nov 21, 2019 4:58:01 PM

The main showstopper in communicating via Microlink is the challenge string that the UPS and PC use to authenticate. After an initial run through a significant part of the message space, the UPS sends messages 0x7e and 0x7f and continues again from message ID 0x00. If the PC does not send a message 0x7e with the right 4 byte answer soon after, UPS communication halts. At this point one needs to wait a long time or reset the UPS by unplugging it completely to be able to communicate again.

After a lot of experimenting and repeating the challenge string sequence it now seems to me that the UPS generates the a 2-byte challenge and puts this in message 0x7e at bytes 8-10 (or maybe 8-12, but 2 bytes seems to work for now). Let's call this password_1. This value changes after every communication reset.

For example:

UPS: 7e 00 00 00 00 00 00 00 00 f6 27 00 00 00 00 00 00 33 30

PC : 7e 0c 04 4d 3e 9b 60 90 59

UPS: 7e 00 00 00 00 00 00 00 00 29 a7 09 24 4d 3e 9b 60 44 b7

The PC then answers the challenge by combining password_1 (f6 27) with other fixed numbers that were announced by the UPS in the previous messages: A 2-byte value taken from message 0x00, an 8 byte value from message 0x00 (the header), the unit's serial number (14 bytes) from message 0x40 and finally password_1. The result of this operation is the 4 byte value (4d 3e 9b 60) captured above.

The operation is straightforward: all the bytes of the above are added together, with a modulo operator avoiding the overflow.

The first 2 bytes of the answer have no effect and are ignored, they also have a random distribution. The last 2 bytes are the result of the modulo addition.

def calculate_challenge(self):

'''

Calculate challenge from actual known ups state

'''

b0 = self.ups_state['header_raw'][3]

b1 = self.ups_state['header_raw'][4]

for header_byte in self.ups_state['header_raw']:

b0 = (b0 + header_byte) % 255

b1 = (b1 + b0) % 255

for serial_nb_byte in self.ups_state['serial_nb_raw']:

b0 = (b0 + serial_nb_byte) % 255

b1 = (b1 + b0) % 255

for pw1_byte in self.ups_state['password_1'][0:2]:

b0 = (b0 + pw1_byte) % 255

b1 = (b1 + b0) % 255

challenge = bytearray([1, 1, b0, b1])

return challenge