CRC challenge

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm trying to reverse engineer the protocol my heating/cooling system uses. The packets are framed by time and they look like they have a crc in order to check the packet.

Here's a couple of packets (in hex):

05 31 21 FF 03 05
05 31 21 06 60 83

11 22 21 46 c0 03 00 00 00 08 16 09 01 01 00 00 94 14
11 22 21 46 c0 03 00 00 00 08 15 09 01 01 00 00 1c 14

The first byte is the # of bytes following. At a guess the crc is the last two bytes. There's no rolling code as I can get a number of identical packets. I've tried the usual suspects - CRC16 etc but I haven't tried the CRC for CAN. Is there anyone interested in solving a puzzle?

If it can't be resolved, I'll just have to assume the packets are ok and respond with 'canned' messages.

As an aside, the physical side is interesting - sort a modification of current loop. Two wires are used for power and comms - to send data a resistor is switched across the loop to modulate the current and thus the voltage level, to receive, a capacitor and a comparator is used to sense the voltage changes. Baud rate is 9600 baud asynchronous. A bridge rectifier is used so that is is polarity insensitive. I dare say the designers got fed up with installers not being able to wire up a handful of connections - with only two wires, its pretty hard to get it wrong.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you write a CRC routine where the poynomial is variable you could wrap it in a for() loop and run through all 65536 potential polynomial values.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Tried all 16-bit variants (CCITT, CAN, CRC-16, DNP, DECT, 10-DIF) on this one, couldn't get a match on your strings.
http://www.zorc.breitbandkatze.d...

/Jesper
http://www.yampp.com
The quick black AVR jumped over the lazy PIC.
What boots up, must come down.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
    uint16_t
    crc16_update(uint16_t crc, uint8_t a, uint16_t poly)
    {
	int i;

	crc ^= a;
	for (i = 0; i < 8; ++i)
	{
	    if (crc & 1)
		crc = (crc >> 1) ^ poly;
	    else
		crc = (crc >> 1);
	}

	return crc;
    }

Something like this running poly through 0..65535 and each time running 'a' through the test data. You also probably want to see the difference between starting 'crc' at 0x0000 or 0xffff:

uint16_t crc;
uint16_t poly;
uint8_t i;
uint8_t data[] = { 0x11, 0x22, 0x21, 0x46, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x08, 0x16, 0x09, 0x01, 0x01, 0x00, 0x00 };

for (poly=0; poly; poly++) {
 crc = 0xFFFF;
 for (i=0; i<0x11; i++) {
   crc16_update(crc, data[i], poly);
 }
 if (crc = 0x1494) {
   printf("eureka, poly=0x%04X", poly);
 }
}

That further raises the questions - is the byte count included (0x11 or 0x10?) and is the CRC stored little or big endian (0x1494 or 0x9414)

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So change the if to if((crc==0xaabb) || (crc==0xbbaa))?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You also want to run with 0x0000 instead of 0xffff as the initial CRC. This differs in CRC-16 and CRC-CCITT for instance.
Actually, you'd want to run with all values in between too.
There's sometimes a final XOR with a value (which may be reversed), adding another 32k variants.

/Jesper
http://www.yampp.com
The quick black AVR jumped over the lazy PIC.
What boots up, must come down.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the ideas guys. I'll have to fire up the c compiler under Windows or maybe under Linux? Not often that I write 'c' in this environment. We'll try the brute force technique and see if we get a hit - thanks Cliff for the sample code - means I have to think less.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
//unsigned char data[18] = {0x11,0x22,0x21,0x46,0xc0,0x03,0x00,0x00,0x00,0x08,0x16,0x09,0x01,0x01,0x00,0x00,0x94,0x14};
unsigned char data[16] = {0x11,0x22,0x21,0x46,0xc0,0x03,0x00,0x00,0x00,0x08,0x15,0x09,0x01,0x01,0x00,0x00};

//unsigned char data[8] = {0x01,0x03,0x00,0x00,0x00,0x0a,0xc5,0xcd};
//unsigned char data[3] = {0x31,0x21,0xff};
int main(int argc, char* argv[])
{
int i;
unsigned short poly,crc;

	printf("Begin\n");
	printf("Sizeof char %04x\n",sizeof(unsigned int));
	for (poly = 0; poly < 65535 ; poly++ )
		{
		crc = 0x0000; //preload crc
		for (i = 0; i < 16; i++)
			{
			crc16(&crc,data[i],poly);
 //			printf("Result %02X %04X\n",i,crc);
			}
		if (crc == 0x141c)
			{
			printf("Woo Hoo %04X \n",poly);
			}
		if (crc == 0x1c14)
			{
			printf("Hoo Woo %04X \n",poly);
			}
		if (crc == 0)
			{
			printf("CRC == 0\n");

			}
		}
		printf("End\n");
return 0;
}


void crc16 (unsigned short *crc,uint8_t a, unsigned short poly)
{
int i;

 *crc ^= a;
 for (i=0; i < 8; i++)
	{
	if (*crc & 0x0001)
		{
		*crc >>= 1;
		*crc ^= poly;
		}
	else
		{
		*crc >>= 1;
        }
	}

}

I massaged Cliff's code a little but I don't seem to have got any hits at the moment. It takes very little time to do the brute force search - nothing like having a GHz processor at one's disposal.
I verified the code using a known modbus packet and the modbus crc poly 0xa001.
I haven't tried Jesper's suggestions yet, nor fully explored the start byte in the message offset.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Does the manual say 'crc'? It might be a simple checksum, or 'end around carry'. The whole thing was probably written by an engineer that's a self taught programmer rather than a comp sci grad.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

part of the problem is also knowing over what range of bytes the CRC is calculated. [I suggest trying with and without the length byte]

As for finding the start value, you can do that on a second pass.. If you can find a poly that always results in the same value for result [with a fixed start value, usually 0x0000 or 0xffff], you can then simply scan through all possible start values until you get to 0x0000 as the result.

Also as you're calculating, don't include the CRC bytes as we don't know the storage order of them. Insted simply compare aganst them.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I tried a few of the obvious approaches without success. It is possible that no checksum is included, depending on the nature of content. Hard to believe they would implement a hardcore CRC, assuming their products aren't susceptible to industrial settings, OR I suppose if a bad packet would start a fire etc.

Have you googled for some clues? Include manufacturer and "serial protocal packet" or something like that. Might even call or email support. Can't hurt.

C: i = "told you so";

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Going back to the original data:

11 22 21 46 c0 03 00 00 00 08 16 09 01 01 00 00 94 14 
11 22 21 46 c0 03 00 00 00 08 15 09 01 01 00 00 1c 14 

It certainly would seem to confirm that it is 16 bit little endian in that only the difference of a 16 or 15 in the middle of the data apparently leads to 0x1494 or 0x141c. But when you then carry this "discovery" across to:

05 31 21 FF 03 05 
05 31 21 06 60 83

Could the difference in one byte between FF and 06 really lead to a crc difference as large as 0x0503 versus 0x8360 ? If this is shift and xor rotating polynomial it just seems that too much changed between those for just a single data byte change?

I wonder if this is more like a hash? (maybe just taking the lowest 16 bits?) But if it's that way round the the 0x11 data checksums then seem too close??

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Could the difference in one byte between FF and 06 really lead to a crc difference as large as 0x0503 versus 0x8360 ?

Yes it could, because the CRC is the remainder of a division by the polynomial n. So it's quite possible that a difference of only a single bit could change the CRC by as much as n - 1.

correction -
Since the division is done modulo 2, the largest difference in remainders for a polynomial of order n (ie, the biggest term is X^n) is (2^n - 1).

Last Edited: Tue. May 4, 2010 - 12:40 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Based on the longer packet, if it were CRC, the last two bytes should look wildly different even if there is only two exchanged bits in the message (0x15 vs 0x16) or then that is just pure coincidence that the other byte stays same. Just my feeling, I may be wrong here.

How about Fletcher's checksum or Adler's checksum?

Do you have any shorter packets to work with, or just more different packets? Preferably more those that have just one different byte, or even just one different bit?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, I let my computer go at this today, running:

- Both the short examples
- With and without the length byte
- CCITT and CRC-16
- LSB and MSB first
- straight and inverted results
- CRC bytes checked both ways round
- All seeds from 0 to 0xFFFF.

Every combination produces 2 results - the given CRC bytes in opposite orders - for different seed values, but in no case does the same seed work for both strings.

Another approach might be to run it again calculating the CRCs for both strings at the same time, using each possible seed, and see if there's any seed where they match. That would discover the proper seed and CRC algorithm if there's a constant being XORd with the result. I wish I'd thought of that earlier.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jepael, I have a number of packets to work with of various sizes. I posted the smallest one and one of the larger ones just to give a range to work with. I'm guessing the check code is a CRC due to the amount of change in the check code due to a difference of 1 in one byte. I too find I get a match but the polynomial is different for the different packets. It could be the manufacturer has gone to some length in hiding the check sequence or has 'invented' some method. One would tink the CRC would be the best choice in this instance, for me at least, it would be my choice. And being lazy, I'd just pick a usual one. The payload doesn't seem encrypted as I have decoded the time packets. The first byte being the length, the second and third bytes I think are source/destination addresses. I haven't confirmed this. There are four devices on the bus - a cooler, a heater, the master controller and a slave controller. I want to replicate the slave controller function so I can have the PC act as a slave controller. Since the controls are minimal - on/off,heat/cool and set temp, the range of packets are limited so 'canning' them is doable. The manufacturer doesn't have a PC interface in their offerings, so I'm not trying to replicate something that is already available. I can post more data packets if there is interest and many thanks to all that have offered ideas and done tests.

I could try to contact the manufacturer, but I expect they would've outsourced the design and production to a third party and probably aren't interested in having other things control their units. Their temperature sensor is two degrees too optimistic as well - this I reported to them, but got no further than their service/support group who can't seem to get past the concept of working/not working. I've tried four of their controllers and they're consistantly wrong!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Silly question but what happens if you send a packet with a corrupt checksum? You never know, they may have included a CRC generator "just in case" then in the end not bothered to actually check it. ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I get the feeling that whoever designed this had a fair idea of what they were doing. If it was a crappy pcb and hardware design, then I'd be suspicious of the software. Also, they've used a ST micro (ST7) so they have no worries using a more specialist product. As I mentioned previously, I can send 'canned' messages (ones I've already captured) as the commands I need to emulate are pretty simple on/off style messages. There's the temperature set messages, so I'll have to capture the whole range! With these little problems, a little insider knowlege certainly helps.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A good place to look for obscure CRCs is here http://regregex.bbcmicro.net/crc-catalogue.htm

And here's a link to a Perl script to brute-force the parameters to a CRC: http://regregex.bbcmicro.net/crcbfs.pl.txt