Skip to content
This repository has been archived by the owner on Dec 9, 2021. It is now read-only.

Protocol

* * * A S H * * * edited this page Feb 4, 2016 · 4 revisions

Protocol

Packet format

ofs size contents
+00 2byte message id, see Protocol Messages
+02 3byte payload length
+05 2byte client version
+07 encrypted message

Protocol

  • The client initializes 2 rc4 streams, with key: basekey+"nonce"

    • len(+"nonce") bytes are skipped initially.
    • One stream for client->server, and one stream for server->client communication.
    • the basekey is: fhsd6f86f67rt8fw78fw789we78r9789wer6re
    • XOR your payload against the key stream (see the old scramble function from cocutils.py for an example of how to do it).
  • The client connects to port 9339 ( or 843, 1863, 3724, 30000 ) of the server gamea.clashofclans.com

    • note: the server seems to be hosted in the amazon cloud.
  • The client sends a Login packet

    • When logging in for the first time, the uid fields are set to 0
    • this packet contains the 'client seed' value
    • NOTE: the masterhash has to be correct, if not you will get a LoginFailed message, containing the updated fingerprint.json contents.
  • The server responds with a Encryption packet, containing the server random value.

  • The client calculates a new nonce by scrambling the serverrandom with the client seed, using the algorithm listed below.

  • The client creates 2 new rc4 streams, now with key: basekey+servernonce

  • Then the server continues with a LoginOk message

  • Followed by the gamestate in a OwnHomeData message.

import struct
def lshift(num, n):
    return (num*(2**n))%(2**32)
def rshift(num, n):
    highbits= 0
    if num&(2**31):
        highbits= (2**n-1)*(2**(32-n))
    return (num/(2**n))|highbits
def isneg(num):
    return num&(2**31)
def negate(num):
    return (~num)+1
class prng:
    def __init__(self, seed):
        self.seed= seed
    def next(self):
        mask=0x100000000
        v3= self.seed if self.seed else 0xffffffff

        v3 ^= lshift(v3,13)
        v3 ^= rshift(v3,17)
        v3 ^= lshift(v3,5)

        self.seed= v3
        if isneg(v3):
            v3= negate(v3)
        return v3%0x100

def scramble(data, seed):
    rng= prng(seed)
    return "".join(chr(ord(c)^rng.next()) for c in data)

in version 7.x the scramble function has changed to:

class scramble7prng:
    def __init__(self, seed):
        self.ix= 0
        self.buffer= [ 0 for i in range(624) ]
        self.seedbuffer(seed)
    def dumpbuffer(self):
        for x in self.buffer:
            print " %08x" % x,
        print
    def seedbuffer(self, seed):
        for i in range(624):
            self.buffer[i]= seed
            seed= (1812433253 * ((seed ^ rshift(seed, 30)) + 1)) & 0xFFFFFFFF
    def getbyte(self):
        x= self.getint()
        if isneg(x):
            x= negate(x)
        return x % 256
    def getint(self):
        if self.ix==0:
            self.mixbuffer()
        val= self.buffer[self.ix]
        self.ix = (self.ix+1) % 624

        val ^= rshift(val, 11) ^ lshift((val ^ rshift(val, 11)), 7) & 0x9D2C5680;
        return rshift((val ^ lshift(val, 15) & 0xEFC60000), 18) ^ val ^ lshift(val, 15) & 0xEFC60000;
    def mixbuffer(self):
        i=0
        j=0
        while i<624:
            i += 1
            v4= (self.buffer[i%624] & 0x7FFFFFFF) + (self.buffer[j]&0x80000000)
            v6 = rshift(v4,1) ^ self.buffer[(i+396)%624]
            if v4&1:
                v6 ^= 0x9908B0DF
            self.buffer[j] = v6
            j += 1


def newscramble(serverrandom, seed):
    prng= scramble7prng(seed)
    for i in range(100):
        byte100= prng.getbyte()
    return "".join(chr(ord(c)^(prng.getbyte()&byte100)) for c in serverrandom)
Clone this wiki locally