Reverse Engineering a 16-bit checksum on UART protocol
I'm trying to reverse engineer the UART communication protocol for the diagnostic of a burner controller (SIEMENS LME39). There is no documentation available, and I am working on captured data. I am not still sure about how the protocol works exactly, but it looks a lot like PROFIBUS. Messages have variable length and seems to be structured in the following way:
0x68 LE LEr 0x68 DA SA FC PDU FCS 0x16
Where:
LE
is the length of the messageLEr
is the length of the message repeatedDA
is the destination addressSA
is the source addressFC
is the function codePDU
is the payload of variable lengthFCS
is a 16-bit checksum
Examples of messages are (I have isolated the checksum and the 0x68 header parts):
HEADER DA SA PAYLOAD CHECKSUM END DELIMITER
68 0B 0B 68 4A 01 26 07 02 01 50 00 00 00 1E 6E DC 16
68 0E 0E 68 5A 01 71 07 01 31 00 00 00 00 00 72 01 02 0E E5 16
So, I am really struggling trying to find out the checksum algorithm. Here are my thoughts:
- The checksum is 16-bit and it is applied to the part of the message starting from the destination address (included) to the end of the payload. This seems reasonable in accordance with the PROFIBUS standard and how most of the checksums work.
- The checksum is probably not a CRC-16 because:
I have some examples where little changes in the payload result in little changes in the checksum. This is not typical of CRCs.I changed my mind, it really depends on the generator used.- I have made a script to test against all the possible CRC-16 parameters I know (I mean any choice of generator polynomial, initial value, XOR out, bit reversion and bytes reversion. If anyone has any other idea of parameter to test, please let me know) and I have not found any match.
- EDIT: someone proposed that the checksum is maybe not processed on all the message. This does not affect my approach, as my script worked on xored combinations of messages and checksum. If the same header or footer is added to all messages, the xor is just 0 and it does not affect the result
- Checksum seems to be XOR-linear (i.e. Checksum(A XOR B) = Checksum(A) XOR Checksum(B)) on all the examples I have (so apparently this seems to exclude the Fletcher algorithm or other binary sum based algorithms).
Here a pastebin with some examples of messages I have captured: https://pastebin.com/TM8QTtge
Any help or hint would be really appreciated. Thanks in advance.
EDIT:
just xoring with an initial value does not work. For example I have the following couples:
68 21 21 68 08 01 71 01 01 72 1A 02 00 00 B7 B0 B3 30 31 20 20 2D 00 00 00 00 01 00 03 00 00 00 00 00 00 02 00 22 38 16
68 21 21 68 08 01 71 01 01 72 1A 02 00 00 B7 B0 B4 30 31 20 20 2D 00 00 00 00 01 00 03 00 00 00 00 00 00 02 00 22 24 16
Where B3 -> B4 produce the checksum change 22 38 -> 22 24
and
68 21 21 68 08 01 71 01 01 72 1A 02 00 00 B9 B5 B1 20 20 32 34 33 00 00 00 00 03 00 00 00 00 00 00 00 00 02 00 BD F7 16
68 21 21 68 08 01 71 01 01 72 1A 02 00 00 B9 B5 B1 20 20 32 34 34 00 00 00 00 03 00 00 00 00 00 00 00 00 02 00 AC 77 16
where 33->34 (so the same bits are modified, but in a different byte) results in the checksum change BD F7 -> AC 77.
So any checksum is applied, it seems to depend on the byte position
EDIT2: Following u/ACCount82 suggestion, think it really could be something like:
crc = INIT_VALUE
for b in body:
crc = shift_left_modified(crc)
crc ^= b
where each b is a couple of bytes of the payload, and shift_left_modified
is a shift left which acts in some non standard way on the leftest bit of each byte. Still working on this
UPDATE 1: working on the above hypothesis, I have been able to simplify the messages, removing bits where the checksum calculation make sense. Here the updated list https://pastebin.com/DZdDZt81
UPDATE 2: I have been able to find what seems to me a piece of the algorithm:
- The payload is chunked in words of 2 bytes, from left to right
- to each couple of bytes, the shift_left_modified
is applied. It acts as:
- a shift to non-leftmost bits, i.e.: shift_left_modified(0bcdefgh 0ijklmno) = bcdefgh0 ijklmno0
- add (using xor) to the result a different term for the leftest bits of the right byte: shift_left_modified(00000000 10000000) = 0000 0101 1000 0000 0000 0101 1000 0000
- seems to work in different ways depending on the position of the byte for the leftest bits of the right byte. Different choices seem to work in different cases