2

I am attempting to reverse engineer some proprietary J1939 CAN traffic so that I can remotely control some actions on a vehicle. I have collected a number of traces covering the events I want to control and have identified the controlling messages for several, but the payloads have some kind of authentication/checksum that I have not been able to figure out and was hoping someone might recognize what is going on.

I have so far attempted some different things like summing set bits (both data and ID) and a few quick CRC calculators, but I haven't had any luck. This is outside of my normal skillset, so apologies if there is something obvious I'm missing.

The checksums are in the last (8th) byte of the message payloads. There is appears to be a message counter used in the calculation as the value increments without any changes to the other data bytes. From what I can tell, the lower nibble increments by one each message and then resets on overflow.

The upper nibble also increments (though with only three bits), but the value will change with the payload. The counter also skips a value each iteration. The value is different between the two iterations that complete before the lower nibble overflows, but appears to increment by one the second iterations UNLESS the number skipped is 7, in which case both 7 and 0 are skipped. Note that I am not certain that the 7/0 skip is consistent across payloads nor if it is the only time multiple digits are skipped.

I have provided some data samples below (I have others I can provide) with specific notes immediately before them. If anyone recognizes this pattern or if there is anything to clarify or data to look for, please let me know!

Data Samples

This sample illustrates the behavior of both nibbles of the checksum byte. This pattern repeats until one of the other data bytes changes. Of note is that message 0x0CFF9780 has the exact same checksum value and data bytes during this sample EXCEPT for byte 7, which is 0x39 instead of 0x00 (e.g., the first message is 0CFF9780 E8 03 E8 03 00 64 39 10). This appears to be a coincidence as the checksums do differ with different payloads (see following sample).

J1939_ID  Data_bytes-------------
0CFF9880  E8 03 E8 03 00 64 00 10
0CFF9880  E8 03 E8 03 00 64 00 21
0CFF9880  E8 03 E8 03 00 64 00 32
0CFF9880  E8 03 E8 03 00 64 00 53
0CFF9880  E8 03 E8 03 00 64 00 64
0CFF9880  E8 03 E8 03 00 64 00 75
0CFF9880  E8 03 E8 03 00 64 00 06
0CFF9880  E8 03 E8 03 00 64 00 17
0CFF9880  E8 03 E8 03 00 64 00 28
0CFF9880  E8 03 E8 03 00 64 00 39
0CFF9880  E8 03 E8 03 00 64 00 4A
0CFF9880  E8 03 E8 03 00 64 00 6B
0CFF9880  E8 03 E8 03 00 64 00 7C
0CFF9880  E8 03 E8 03 00 64 00 0D
0CFF9880  E8 03 E8 03 00 64 00 1E
0CFF9880  E8 03 E8 03 00 64 00 2F

This sample is the data for 0x0CFF9780 after its data bytes (bytes 3 and 4) change from the above sample. The data for 0x0Cff9880 is the same as the first sample during this time frame.

J1939_ID  Data_bytes-------------
0CFF9780  E8 03 D0 07 00 64 39 30
0CFF9780  E8 03 D0 07 00 64 39 41
0CFF9780  E8 03 D0 07 00 64 39 52
0CFF9780  E8 03 D0 07 00 64 39 63
0CFF9780  E8 03 D0 07 00 64 39 74
0CFF9780  E8 03 D0 07 00 64 39 05
0CFF9780  E8 03 D0 07 00 64 39 16
0CFF9780  E8 03 D0 07 00 64 39 37
0CFF9780  E8 03 D0 07 00 64 39 48
0CFF9780  E8 03 D0 07 00 64 39 59
0CFF9780  E8 03 D0 07 00 64 39 6A
0CFF9780  E8 03 D0 07 00 64 39 7B
0CFF9780  E8 03 D0 07 00 64 39 0C
0CFF9780  E8 03 D0 07 00 64 39 1D
0CFF9780  E8 03 D0 07 00 64 39 2E
0CFF9780  E8 03 D0 07 00 64 39 4F

This sample captures the upper nibble skipping both 7 and 0 on its second iteration.

J1939_ID  Data_bytes-------------
18FF9980  03 00 00 00 00 00 00 32
18FF9980  03 00 00 00 00 00 00 43
18FF9980  03 00 00 00 00 00 00 54
18FF9980  03 00 00 00 00 00 00 75
18FF9980  03 00 00 00 00 00 00 06
18FF9980  03 00 00 00 00 00 00 17
18FF9980  03 00 00 00 00 00 00 28
18FF9980  03 00 00 00 00 00 00 39
18FF9980  03 00 00 00 00 00 00 4A
18FF9980  03 00 00 00 00 00 00 5B
18FF9980  03 00 00 00 00 00 00 6C
18FF9980  03 00 00 00 00 00 00 1D
18FF9980  03 00 00 00 00 00 00 2E
18FF9980  03 00 00 00 00 00 00 3F
18FF9980  03 00 00 00 00 00 00 10
18FF9980  03 00 00 00 00 00 00 21

These are two separate samples that are minor variations of the above sample that only have byte 1 change value.

J1939_ID  Data_bytes-------------
18FF9980  11 00 00 00 00 00 00 20
18FF9980  11 00 00 00 00 00 00 31
18FF9980  11 00 00 00 00 00 00 42
18FF9980  11 00 00 00 00 00 00 53
18FF9980  11 00 00 00 00 00 00 64
18FF9980  11 00 00 00 00 00 00 75
18FF9980  11 00 00 00 00 00 00 06
18FF9980  11 00 00 00 00 00 00 27
18FF9980  11 00 00 00 00 00 00 38
18FF9980  11 00 00 00 00 00 00 49
18FF9980  11 00 00 00 00 00 00 5A
18FF9980  11 00 00 00 00 00 00 6B
18FF9980  11 00 00 00 00 00 00 7C
18FF9980  11 00 00 00 00 00 00 0D
18FF9980  11 00 00 00 00 00 00 1E
18FF9980  11 00 00 00 00 00 00 3F

18FF9980  01 00 00 00 00 00 00 70
18FF9980  01 00 00 00 00 00 00 01
18FF9980  01 00 00 00 00 00 00 12
18FF9980  01 00 00 00 00 00 00 23
18FF9980  01 00 00 00 00 00 00 34
18FF9980  01 00 00 00 00 00 00 45
18FF9980  01 00 00 00 00 00 00 56
18FF9980  01 00 00 00 00 00 00 77
18FF9980  01 00 00 00 00 00 00 08
18FF9980  01 00 00 00 00 00 00 19
18FF9980  01 00 00 00 00 00 00 2A
18FF9980  01 00 00 00 00 00 00 3B
18FF9980  01 00 00 00 00 00 00 4C
18FF9980  01 00 00 00 00 00 00 5D
18FF9980  01 00 00 00 00 00 00 6E
18FF9980  01 00 00 00 00 00 00 1F

Finally, this is one long sample that has a number of byte changes in it. Note that, unlike previous samples, this one does NOT repeat.

J1939_ID  Data_bytes-------------
0CFF9880 E8 03 E8 03 00 64 00 10
0CFF9880 E8 03 E8 03 00 64 00 21
0CFF9880 E8 03 E8 03 00 64 00 32
0CFF9880 52 04 71 03 00 64 00 73
0CFF9880 52 04 71 03 00 64 00 04
0CFF9880 52 04 71 03 00 64 00 15
0CFF9880 52 04 71 03 00 64 00 26
0CFF9880 52 04 71 03 00 64 00 47
0CFF9880 52 04 71 03 00 64 00 58
0CFF9880 52 04 71 03 00 64 00 69
0CFF9880 79 05 B4 03 00 64 00 1A
0CFF9880 10 06 CF 03 00 64 00 2B
0CFF9880 10 06 CF 03 00 64 00 3C
0CFF9880 47 06 CF 03 00 64 00 3D
0CFF9880 47 06 CF 03 00 64 00 4E
0CFF9880 47 06 CF 03 00 64 00 5F
0CFF9880 47 06 CF 03 00 64 00 40
0CFF9880 47 06 CF 03 00 64 00 51
0CFF9880 47 06 CF 03 00 64 00 72
0CFF9880 6C 06 CF 03 00 64 00 23
0CFF9880 6C 06 CF 03 00 64 00 34
0CFF9880 6C 06 CF 03 00 64 00 55
0CFF9880 8B 06 CF 03 00 64 00 16
0CFF9880 8B 06 CF 03 00 64 00 27
0CFF9880 8B 06 CF 03 00 64 00 38
0CFF9880 8B 06 CF 03 00 64 00 49
0CFF9880 8B 06 CF 03 00 64 00 5A
0CFF9880 AA 06 CF 03 00 64 00 6B
0CFF9880 AA 06 CF 03 00 64 00 7C
0CFF9880 AA 06 CF 03 00 64 00 0D
0CFF9880 AA 06 CF 03 00 64 00 1E
0CFF9880 CA 06 CF 03 00 64 00 7F
1
  • 1
    It doesn't look like a checksum to me, more like two sequences. One device would transmit a message with a sequence number in one nibble, and the replying device responds with the next number, and it's own sequence number in the other nibble. Missing ones could be dropped packets or out-of-sequence packets that get filtered out. Apr 14, 2023 at 16:45

1 Answer 1

3

Found the calculation in a newer version of the J1939 standard than I originally had access to. The manufacturer reused the following equation from SPN 4207:

Checksum = 
(Byte1 + Byte2 + Byte3 + Byte4 + Byte5 + Byte6 + Byte7 + 
message counter & 0x0F + 
message ID low byte + message ID mid low byte + message ID mid high byte + message ID high byte)

Message Checksum = (((Checksum >> 6) & 0x03) + (Checksum >>3) + Checksum) & 0x07
1
  • 1
    How can the checksum be greater than 0x07 since the very last operation is & 0x07?
    – Dhoop
    May 7, 2023 at 12:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.