5

So, I wanted repurpose some old boarding gate scanners and I'm trying to make use of their commands.

Now, I have a dump from an actual boarding gate PC and I've noticed that the commands only work if I send them as they are on the dump. If I change even a single byte, the scanner rejects the command. After some Googling, I came across the term 'CRC' and now I understand why that is the case.

I played around with some CRC calculators, but the scanner seems to have its own algorithm and I have trouble figuring it out.

For instance, the command that displays a message on the scanner's display is 'AD;MG#P#ATESTMESSAGE'.

Here's an example from the dump:

02 30 80 41  44 3B 4D 47   23 50 23 41  4E 4F 54 20  .0.AD;MG#P#ANOT
49 4E 20 55  53 45 0D 42   0D FF 03 3A  12           IN USE.B...:.

I noticed that the same exact command is sent in one more way that's slightly different:

02 31 80 41  44 3B 4D 47   23 50 23 41  4E 4F 54 20  .1.AD;MG#P#ANOT
49 4E 20 55  53 45 0D 42   0D FF 03 3B  92           IN USE.B...;.

Although the message is the exact same, the difference in the command header seems to have an effect on the last two bytes.

All command headers either start with '02 30 80' or '02 31 80' depending on the response header of the scanner. Kind of like 'ping-pong' (I couldn't think of a better way to describe this).

I have tried decompiling the scanner's firmware but I can't seem to locate the method where it checks for the CRC.

Here's a few more command pairs in case they help.

Any help is greatly appreciated!

P.S: I'm a completely new to all this, in case you couldn't tell.

1 Answer 1

4

These are simple XOR checksums. Here's how they work using a short message as an example:

STX |<------------- data -------------->|s1 |s2 

 02  30  80  41  44  3b  43  57  ff  03  66  e4 

The values s1 and s2 are calculated separately over only the data portion; the start of text (STX) character is not used for the check bytes. Here's pseudo-code with all quantities being 8-bit values.

s1 = 0
s2 = 0
for (each b in data)
    s1 = s1 ^ b
    s2 = ror(s2) ^ b

The ror here stands for "rotate right" which rotates the 8-bit quantity to the right by one bit:

         +---+---+---+---+---+---+---+---+
Before:  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
         +---+---+---+---+---+---+---+---+

         +---+---+---+---+---+---+---+---+
After:   | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
         +---+---+---+---+---+---+---+---+

How I figured it out

I noticed that, ignoring the last two bytes, each pair of messages differed by a single bit, b0 (the low bit). The s1 byte also differed by only the low bit, so I guessed something like a sum or XOR. Doing an XOR over all bytes except the first (02) and the last (s2) yielded 0 for all samples, so that was s1 figured out.

For s2, the pairs of message also differed in only a single bit, but the position was different for each pair, so I assumed it was a linear operation and I guessed that there was some rotation happening that would shift the position of the changed bit depending on the length of the message. After a few guesses, I hit upon the algorithm described above.

Also the changing bit 30 or 31 was probably a "toggle bit." Many serial protocols employ one. The idea is that for each message sent, the bit changes so that way it's possible to tell whether the message is a duplicate or a new message. Any retransmitted message would leave the bit unchanged; only new messages change the state.

In Java

I don't much like Java, but if that's what you're using, this is an implementation that works.

public class MyClass {
    public static byte ror(byte c) {
        return (byte)(((c >> 1) & 0x7f) | (c << 7));
    }
    public static byte[] getXOR(byte[] data) {
        int s1 = 0;
        int s2 = 0;
        for (byte b : data) {
            s1 = (s1 ^ b);
            s2 = ror((byte)s2) ^ b;
        }
        return new byte[]{(byte)(s1), (byte)(s2)};
    }
    
    public static void main(String args[]) {
      byte[] data = new byte[]{0x31,(byte)(0x80),0x41,0x44,0x3B,0x43,0x57,(byte)(0xFF),0x03};
      byte[] x = getXOR(data);

      System.out.println(String.format("%x %x", x[0], x[1]));
    }
}
6
  • Thank you so much for your response, Edward! I just made this quick test function in Java. I have been using data from the dump to test it and the s1 byte seems to always match the one in the dump. However, I'm not having that much luck with the s2 byte. Any ideas as to what I'm doing wrong?
    – Gavouli
    Nov 30, 2022 at 22:56
  • Java uses 32-bit numbers, not 8-bit so your rotation is rotating through 32 bits instead of 8. You will have to write your own function to do that.
    – Edward
    Nov 30, 2022 at 23:32
  • That would be a 'byte' in Java. I tried shifting as a byte but s2 still seems to be incorrect. Any more ideas?
    – Gavouli
    Dec 1, 2022 at 0:04
  • Java treats all numbers as signed, which is a design flaw IMHO. You might be able to use >>> instead of >> to make it do the right thing anyway.
    – Edward
    Dec 1, 2022 at 0:42
  • It works! Thank you ever so much for your help, Edward! I wasted my two-week annual leave trying to figure this out. I can't thank you enough! Best I can do is this. <3 Have a good one! :)
    – Gavouli
    Dec 1, 2022 at 13:00

Your Answer

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

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