GT06E GPS Tracker Part 1: Implementing communication protocol – WTF is CRC-ITU???


As an experiment I bought a relatively cheap GPS tracker that supported 3G (most of them at the time were 2G only).
After quick google search I found a suitable model GT06E from Concox.

The idea was I would implement my own server, as I do not trust 3rd party GPS tracking services (who would?), especially free ones ;).

I did not realise at the time what a mess the protocol is.
The “engineers” who wrote the spec for the protocol are crazy! They reinvented the wheel, which instead of tyre utilizes boots.

Ugly stuff:

So here are interesting quirks I found:

Setting server in GPS tracker to UDP has no effect, it only speaks TCP (firmware bug).

GPS tracker sends position data only when it is moving!!! This is very stupid. It also makes testing very difficult. There is no way to request the position data as if it was moving. Every time I needed to test, I had to pick it up and go for a walk.

Instead of using simple POST or even GET (with payload in URL) with HTTP protocol, the “engineers” decided to reinvent the wheel with their own protocol.
The GPS tracker mostly uses 0x7878 start sequence and 0x0d0a end sequence (sometimes it is 0x7979, fuck yeah!).
The payload also contains payload size (depending on start sequence it is either 1 byte or 2 bytes, with 0x7979 being latter), packet type, protocol payload, serial, and most importantly and what this post is about a checksum.

It is really bizarre why they decided to come up with their own protocol, which turned out to be very inconsistent between models. For example the GT06E is not exactly the same as GT06, and is very different to GT02. The way the data stored is also inconsistent, sometimes it is ASCII, sometimes it is binary, sometimes they only use portion of two bytes.

Unfortunately I could not find complete documentation for GT06E (mainly I was using GT06 protocl documentation).

The position data is bizarre. Instead of using signed ints for latitude and longitude, they use unsigned ints and then include the hemisphere as couple of bits in the course data (two bytes).

But even before we get to decoding payload we cannot communicate with the tracker unless we reply with correct checksum. They re-implemented 3 way hand shake with checksums and sequence numbers. I wonder if they are even aware how TCP works?


To reply to the handshake packet (not to be confused with TCP handshake, it all happens above that) from the tracker one needs to be able to calculate checksum for the payload.

The documentation states that the checksum is calculated using CRC-ITU algorithm. It also provides a C example of it and a magical table:

static const U16 crctab16[] =
0X0000, 0X1189, 0X2312, 0X329B, 0X4624, 0X57AD, 0X6536, 0X74BF,
0X8C48, 0X9DC1, 0XAF5A, 0XBED3, 0XCA6C, 0XDBE5, 0XE97E, 0XF8F7,
0X1081, 0X0108, 0X3393, 0X221A, 0X56A5, 0X472C, 0X75B7, 0X643E,
0X9CC9, 0X8D40, 0XBFDB, 0XAE52, 0XDAED, 0XCB64, 0XF9FF, 0XE876,
0X2102, 0X308B, 0X0210, 0X1399, 0X6726, 0X76AF, 0X4434, 0X55BD,
0XAD4A, 0XBCC3, 0X8E58, 0X9FD1, 0XEB6E, 0XFAE7, 0XC87C, 0XD9F5,
0X3183, 0X200A, 0X1291, 0X0318, 0X77A7, 0X662E, 0X54B5, 0X453C,
0XBDCB, 0XAC42, 0X9ED9, 0X8F50, 0XFBEF, 0XEA66, 0XD8FD, 0XC974,
0X4204, 0X538D, 0X6116, 0X709F, 0X0420, 0X15A9, 0X2732, 0X36BB,
0XCE4C, 0XDFC5, 0XED5E, 0XFCD7, 0X8868, 0X99E1, 0XAB7A, 0XBAF3,
0X5285, 0X430C, 0X7197, 0X601E, 0X14A1, 0X0528, 0X37B3, 0X263A,
0XDECD, 0XCF44, 0XFDDF, 0XEC56, 0X98E9, 0X8960, 0XBBFB, 0XAA72,
0X6306, 0X728F, 0X4014, 0X519D, 0X2522, 0X34AB, 0X0630, 0X17B9,
0XEF4E, 0XFEC7, 0XCC5C, 0XDDD5, 0XA96A, 0XB8E3, 0X8A78, 0X9BF1,
0X7387, 0X620E, 0X5095, 0X411C, 0X35A3, 0X242A, 0X16B1, 0X0738,
0XFFCF, 0XEE46, 0XDCDD, 0XCD54, 0XB9EB, 0XA862, 0X9AF9, 0X8B70,
0X8408, 0X9581, 0XA71A, 0XB693, 0XC22C, 0XD3A5, 0XE13E, 0XF0B7,
0X0840, 0X19C9, 0X2B52, 0X3ADB, 0X4E64, 0X5FED, 0X6D76, 0X7CFF,
0X9489, 0X8500, 0XB79B, 0XA612, 0XD2AD, 0XC324, 0XF1BF, 0XE036,
0X18C1, 0X0948, 0X3BD3, 0X2A5A, 0X5EE5, 0X4F6C, 0X7DF7, 0X6C7E,
0XA50A, 0XB483, 0X8618, 0X9791, 0XE32E, 0XF2A7, 0XC03C, 0XD1B5,
0X2942, 0X38CB, 0X0A50, 0X1BD9, 0X6F66, 0X7EEF, 0X4C74, 0X5DFD,
0XB58B, 0XA402, 0X9699, 0X8710, 0XF3AF, 0XE226, 0XD0BD, 0XC134,
0X39C3, 0X284A, 0X1AD1, 0X0B58, 0X7FE7, 0X6E6E, 0X5CF5, 0X4D7C,
0XC60C, 0XD785, 0XE51E, 0XF497, 0X8028, 0X91A1, 0XA33A, 0XB2B3,
0X4A44, 0X5BCD, 0X6956, 0X78DF, 0X0C60, 0X1DE9, 0X2F72, 0X3EFB,
0XD68D, 0XC704, 0XF59F, 0XE416, 0X90A9, 0X8120, 0XB3BB, 0XA232,
0X5AC5, 0X4B4C, 0X79D7, 0X685E, 0X1CE1, 0X0D68, 0X3FF3, 0X2E7A,
0XE70E, 0XF687, 0XC41C, 0XD595, 0XA12A, 0XB0A3, 0X8238, 0X93B1,
0X6B46, 0X7ACF, 0X4854, 0X59DD, 0X2D62, 0X3CEB, 0X0E70, 0X1FF9,
0XF78F, 0XE606, 0XD49D, 0XC514, 0XB1AB, 0XA022, 0X92B9, 0X8330,
0X7BC7, 0X6A4E, 0X58D5, 0X495C, 0X3DE3, 0X2C6A, 0X1EF1, 0X0F78,
// calculate the 16-bit CRC of data with predetermined length.
U16 GetCrc16(const U8* pData, int nLength)
U16 fcs = 0xffff;
 // initialization
fcs = (fcs >> 8) ^ crctab16[(fcs ^ *pData) & 0xff];
return ~fcs;
 // negated

I refuse to accept black boxes and magic in general, so I went digging. Turns out this is described in RFC1331 with great detail.

Disclaimer: I know nothing!

So how does this magical tables comes to be? Math!

Here is the table generation algorithm I wrote based on the sample from the RFC1331:

def gen_fcs_table():

    while True:
        while i>0:
            if v&1:
                v=( v>>1 ) ^ P
                v=v >> 1
        table.append(v & 0xFFFF)
        if b==256:
    return table

There is only one more “black box” left -> P=0x8408.
What we need to know is that the polynomial chosen for this CRC algorithm is x**0 + x**5 + x**12 + x**16.
The process of going from x**0 + x**5 + x**12 + x**16 to 0x8408 is described in detail on wikipedia

The simplest way I see it is following:
Map each power in polynomial on 0..16 sequence:

  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16

so it looks like this (basically 1 under 0, 5, 12, and 16):

  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
 1  0  0  0  0  1  0  0  0  0  0  0  1  0  0  0  1

For this particular implementation we group the result in nibbles of 4 bits from left side (and throwing away remainder):

 [1  0  0  0 |0  1  0  0 |0  0  0  0 |1  0  0  0] 1

Which then is converted in binary, which results in 0x8408.

What is left is to implement the actual checksum algorithm:

def get_fcs(data, table):
    for i in data:
        fcs=(fcs >> 8) ^ table[(fcs ^ b) & 0xFF]
    return fcs ^ 0xFFFF

From this point it is possible to assemble a simplest reply, in this fashion:


def tx_login(serial,prot,start):
    if start == ADV_START:
    check= get_fcs(payload,fcs_table)
    return pre+payload+post

to be continued…

2 thoughts on “GT06E GPS Tracker Part 1: Implementing communication protocol – WTF is CRC-ITU???”

  1. What server software are you trying to pair it with? I’ve got a couple of 2g trackers hooked up to GPSGate (Windows software). One using a protocol called XeXun, the other GoSafe.

    When I obtained my 2nd, and didn’t know what it “spoke” A quick question to the support at gpsgate told me what it was. They’d seen it all. Might save you reinventing the wheel.

    1. I actually wrote my own, there is no interface yet (hate “web programming”). Been too lazy to write part 2, 3, etc …

      Regarding software, for me it is imperative it is open source and Windows free.
      I definitely do not trust 3rd party/cloud solutions, paid or otherwise, thus I decided to write my own.

      It is very basic, written in python, multiprocess TCP server that properly communicates with the GPS unit (batch mode not supported yet), and shoves the location data into a mysql database. It is capable of capturing other data. I have built most of functions I could get documentation on, it can send commands to the unit as well (similarly how one does it via texts).
      All it needs an API (flask?) and some interface to talk to it.

      I enjoy these sort of reverse engineer exercises, it gives great insight on how badly is everything made ;). I am not saying I would have done better, but humans are crap at coding.

Leave a Reply

Your email address will not be published. Required fields are marked *