Search code examples
reverse-engineeringchecksumxorcan-buscrc

Reverse engineer CRC/checksum algorithm in CAN messages


I have a device that is exchanging messages on a CAN bus. I am trying to reverse engineer the protocol being used.

I can decode the protocol (or most of it), but I am struggling to reverse engineer the checksum/CRC being used (without it, the device does not accept it as a valid message). This CRC is at the "application" layer, CAN has its own CRC, but this is the data/payload transmitted by CAN (17 * 8 byte messages, for a total of 136 bytes).

An example message:

07 55 AA AA 00 AA 12 94 
37 5A 00 00 2E 02 5C 03 
3A 02 0A 78 00 60 22 26 
02 00 00 00 00 DC 00 50 
4B 64 B8 0B E0 2E 70 17 
84 25 08 78 1E 0A 78 00 
8C AF B4 69 00 54 24 D4 
24 0A 70 10 19 00 00 06 
CC 0C E8 03 B0 04 51 44 
14 2C 01 88 13 28 00 0A 
00 C0 00 C0 80 C8 0B C0 
25 C0 11 C3 00 C0 00 58 
00 00 C0 0F C0 8A C0 09 
C2 10 C1 07 C2 00 C0 92 
CB 00 3A C7 0F DF 52 14 
00 00 00 00 51 00 D2 05 
00 00 00 ED 6D 00 00 00 

More messages here: https://pastebin.com/uPsMNfyq

The CRC? seems to quite obviously be 'ED 6D' in the last 8 bytes. I don't know where the message starts or ends, the first 8 bytes seem to be some sort of preamble, but they do change sometimes. The same for line 16 - it seems to be some sort of trailing header, but it does change sometime. So the checksum/CRC (assuming this is what it is) could cover some part of the message.

I tried several tools, among which:

  • delsum while trying all combinations of algorithm/width/init value (0-255) and start/end offsets of 0-16
  • find_CRC
  • crcbeagle

None of which yielded any results.

Any help/ideas would be highly appreciated.

Update: Based on the comments here, I crafted two more messages where the parts modified are the earliest (byte 14) and the latest (byte 119) bytes I'm aware how to modify: here.

I reversed the crc (6DED) I also ran delsum trying algo = 0x1021, different start/stop bytes and different init values. I got a few results that look good (still running), but unfortunately none of these seem to verify other messages CRC.


Solution

  • Those two bytes are indeed a 16-bit CRC. It is a forward (not reflected) CRC with the polynomial 0x1021, and the CRC is stored at the end in little-endian order.

    It is a little odd to store a forward CRC in little-endian order. Normally that would be in big-endian order, so that a CRC calculation including the CRC bytes would be a constant for messages with a correct CRC. That may be why it's not being found by the other tools you tried.

    It is not possible just from the examples you provided to determine where exactly the CRC computation starts or ends, since the only the 41st, 59th, and 64th bytes in the messages vary. Those only bound the bytes that participate, so definitely those from the 41st to just before the CRC are part of it, but likely many before the 41st.

    It is also not possible to determine the initial value and final exclusive-or without knowing those bounds, and even if you knew the bounds, you could not determine both of those parameters since all of the messages are all of the same length.

    Though you mention that this is on a CAN bus, this is not a CAN CRC, since those CRCs are 15 bits, 17 bits, and 21 bits.

    For the provided examples, it is possible for the CRC calculation to start at any of the offsets 0..40. For each of those offsets, there exists an initial CRC value, with a final exclusive-or of zero, that will give the correct CRC. Here are those initial values:

    0 -> 0x5188
    1 -> 0xb233
    2 -> 0xbec9
    3 -> 0x9bb5
    4 -> 0x9372
    5 -> 0xc1da
    6 -> 0x07cd
    7 -> 0x8f94
    8 -> 0x375a
    9 -> 0x5a00
    10 -> 0x0000
    11 -> 0x0000
    12 -> 0x0000
    13 -> 0xc5ac
    14 -> 0x05ab
    15 -> 0x60dc
    16 -> 0x80c5
    17 -> 0xc391
    18 -> 0x586d
    19 -> 0x17b7
    20 -> 0x2a49
    21 -> 0xcc28
    22 -> 0x5c66
    23 -> 0xf959
    24 -> 0x6392
    25 -> 0xee87
    26 -> 0x9be0
    27 -> 0xd2d2
    28 -> 0x393f
    29 -> 0x987a
    30 -> 0x7240
    31 -> 0x1ed5
    32 -> 0x7c0a
    33 -> 0x4cb4
    34 -> 0x116a
    35 -> 0x4ec3
    36 -> 0xdb61
    37 -> 0xe638
    38 -> 0x6044
    39 -> 0x5631
    40 -> 0x69e5
    

    It may be interesting that the initial value would be zero for offsets 10, 11, and 12. Those cannot be distinguished, since the examples all have zeros at offset 10 and 11, and since a zero does not change a CRC with an initial value of zero. So if we assume that the initial and final values should be "nice" somehow, then 10, 11, and 12 may be good candidates, as their initial values and final exclusive-or's are zero.

    Though no one can promise that the world will be nice.

    One could also pick a different final exclusive-or value and get another, different set of initial values. All of the combinations generated, starting at their associated offsets, will give the CRC in the message for all of the example messages.