├── .gitignore ├── .gitmodules ├── README.md ├── coc ├── __init__.py ├── client │ ├── __init__.py │ ├── crypto.py │ ├── endpoint.py │ ├── factory.py │ └── protocol.py ├── crypto.py ├── protocol.py └── server │ ├── __init__.py │ ├── crypto.py │ ├── endpoint.py │ ├── factory.py │ └── protocol.py └── proxy.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "coc/message"] 2 | path = coc/message 3 | url = git@github.com:clugh/coc-messages-python.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Warning:** With version 8.212.12, Supercell has started banning accounts for the use of third party software. I am unsure what, if any, checks are in place that might reveal the use of this proxy, so continue use at your own risk. See [here](http://supercell.com/en/safe-and-fair-play/) for more info. 2 | 3 | # coc-proxy 4 | 5 | Run with: 6 | 7 | python3.5 proxy.py 8 | 9 | ## Installation 10 | 11 | 1. Install dependencies. 12 | 2. Patch the [Clash of Clans APK](https://apkpure.com/clash-of-clans/com.supercell.clashofclans) with [coc-patcher](https://github.com/clugh/coc-patcher) and install. 13 | 14 | ## Dependencies 15 | 16 | Install `twisted` with: 17 | 18 | python3.5 -m pip install twisted 19 | 20 | Install `pynacl` with: 21 | 22 | python3.5 -m pip install pynacl 23 | 24 | Install `pyblake2` with: 25 | 26 | python3.5 -m pip install pyblake2 27 | -------------------------------------------------------------------------------- /coc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clugh/coc-proxy/fc59fb4cf5fb9b6706612ee3231bd00fac1eabad/coc/__init__.py -------------------------------------------------------------------------------- /coc/client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clugh/coc-proxy/fc59fb4cf5fb9b6706612ee3231bd00fac1eabad/coc/client/__init__.py -------------------------------------------------------------------------------- /coc/client/crypto.py: -------------------------------------------------------------------------------- 1 | from nacl.public import PublicKey 2 | from coc.crypto import CoCCrypto, CoCNonce 3 | 4 | 5 | class CoCClientCrypto(CoCCrypto): 6 | 7 | def __init__(self, factory): 8 | self._factory = factory 9 | self.keypair() 10 | self._serverkey = PublicKey(bytes.fromhex("01c98c143a840d92ee656996dad5af41de5d1b8ebb289081368b5cfda9bd4a30")) 11 | self.beforenm(self.serverkey) 12 | 13 | @property 14 | def serverkey(self): 15 | return bytes(self._serverkey) 16 | 17 | @property 18 | def clientkey(self): 19 | return bytes(self._pk) 20 | 21 | def decryptPacket(self, packet): 22 | messageid = int.from_bytes(packet[:2], byteorder="big") 23 | unknown = int.from_bytes(packet[5:7], byteorder="big") 24 | payload = packet[7:] 25 | if messageid == 20100 or (messageid == 20103 and not self.session_key): 26 | if messageid == 20100: 27 | self.session_key = self.server.session_key = packet[-24:] 28 | return messageid, unknown, payload 29 | elif messageid in {20103, 20104}: 30 | nonce = CoCNonce(nonce=self.encrypt_nonce, clientkey=self.clientkey, serverkey=self.serverkey) 31 | ciphertext = payload 32 | try: 33 | message = self.decrypt(ciphertext, bytes(nonce)) 34 | except ValueError: 35 | print("Failed to decrypt the message (client, {}).".format(messageid)) 36 | self.server.loseConnection() 37 | return False 38 | else: 39 | self.decrypt_nonce = self.server.encrypt_nonce = message[:24] 40 | self.k = message[24:56] 41 | return messageid, unknown, message[56:] 42 | else: 43 | ciphertext = payload 44 | message = self.decrypt(ciphertext) 45 | return messageid, unknown, message 46 | 47 | def encryptPacket(self, messageid, unknown, payload): 48 | if messageid == 10100: 49 | return messageid, unknown, payload 50 | elif messageid == 10101: 51 | nonce = CoCNonce(clientkey=self.clientkey, serverkey=self.serverkey) 52 | message = self.session_key + self.encrypt_nonce + payload 53 | ciphertext = self.encrypt(message, nonce) 54 | return messageid, unknown, self.clientkey + ciphertext 55 | else: 56 | message = payload 57 | return messageid, unknown, self.encrypt(message) 58 | -------------------------------------------------------------------------------- /coc/client/endpoint.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.endpoints import TCP4ClientEndpoint 2 | 3 | 4 | class CoCClientEndpoint(TCP4ClientEndpoint): 5 | 6 | @property 7 | def host(self): 8 | return self._host 9 | 10 | @property 11 | def port(self): 12 | return self._port 13 | -------------------------------------------------------------------------------- /coc/client/factory.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.protocol import ClientFactory 2 | from coc.client.protocol import CoCClientProtocol 3 | 4 | 5 | class CoCClientFactory(ClientFactory): 6 | 7 | def __init__(self, server): 8 | self.server = server 9 | 10 | def buildProtocol(self, addr): 11 | return CoCClientProtocol(self) 12 | -------------------------------------------------------------------------------- /coc/client/protocol.py: -------------------------------------------------------------------------------- 1 | from coc.protocol import CoCProtocol 2 | from coc.client.crypto import CoCClientCrypto 3 | 4 | 5 | class CoCClientProtocol(CoCClientCrypto, CoCProtocol): 6 | 7 | def __init__(self, factory): 8 | super(CoCClientProtocol, self).__init__(factory) 9 | self.factory.server.client = self 10 | self.server = self.factory.server 11 | self.decoder = self.server.decoder 12 | self.encoder = self.server.encoder 13 | 14 | def connectionMade(self): 15 | super(CoCClientProtocol, self).connectionMade() 16 | print("connected to {}:{} ...".format(self.peer.host, self.peer.port)) 17 | 18 | def packetDecrypted(self, messageid, unknown, payload): 19 | self.decodePacket(messageid, unknown, payload) 20 | self.server.sendPacket(messageid, unknown, payload) 21 | 22 | def connectionLost(self, reason): 23 | print("connection to {}:{} closed ...".format(self.peer.host, self.peer.port)) 24 | self.server.transport.loseConnection() 25 | -------------------------------------------------------------------------------- /coc/crypto.py: -------------------------------------------------------------------------------- 1 | from pyblake2 import blake2b 2 | import nacl.utils 3 | from nacl.public import Box, PrivateKey, PublicKey 4 | 5 | 6 | class CoCCrypto: 7 | _sk = None 8 | _pk = None 9 | _k = None 10 | _encrypt_nonce = None 11 | _decrypt_nonce = None 12 | 13 | session_key = None 14 | 15 | @property 16 | def pk(self): 17 | return bytes(self._pk) 18 | 19 | @property 20 | def k(self): 21 | return bytes(self._k) 22 | 23 | @k.setter 24 | def k(self, k): 25 | self._k = Box.decode(k) 26 | 27 | @property 28 | def encrypt_nonce(self): 29 | return bytes(self._encrypt_nonce) 30 | 31 | @encrypt_nonce.setter 32 | def encrypt_nonce(self, encrypt_nonce): 33 | self._encrypt_nonce = CoCNonce(nonce=encrypt_nonce) 34 | 35 | @property 36 | def decrypt_nonce(self): 37 | return bytes(self._decrypt_nonce) 38 | 39 | @decrypt_nonce.setter 40 | def decrypt_nonce(self, decrypt_nonce): 41 | self._decrypt_nonce = CoCNonce(nonce=decrypt_nonce) 42 | 43 | def keypair(self): 44 | self._sk = PrivateKey.generate() 45 | self._pk = self._sk.public_key 46 | 47 | def beforenm(self, pk): 48 | pk = PublicKey(bytes(pk)) 49 | self._k = Box(self._sk, pk) 50 | 51 | def encrypt(self, message, nonce=None): 52 | if not nonce: 53 | self._encrypt_nonce.increment() 54 | nonce = self.encrypt_nonce 55 | return self._k.encrypt(message, bytes(nonce))[24:] 56 | 57 | def decrypt(self, ciphertext, nonce=None): 58 | if not nonce: 59 | self._decrypt_nonce.increment() 60 | nonce = self.decrypt_nonce 61 | return self._k.decrypt(ciphertext, bytes(nonce)) 62 | 63 | def encryptPacket(self, messageid, unknown, payload): 64 | raise NotImplementedError 65 | 66 | def decryptPacket(self, packet): 67 | raise NotImplementedError 68 | 69 | 70 | class CoCNonce: 71 | 72 | def __init__(self, nonce=None, clientkey=None, serverkey=None): 73 | if not clientkey: 74 | if nonce: 75 | self._nonce = nonce 76 | else: 77 | self._nonce = nacl.utils.random(Box.NONCE_SIZE) 78 | else: 79 | b2 = blake2b(digest_size=24) 80 | if nonce: 81 | b2.update(bytes(nonce)) 82 | b2.update(bytes(clientkey)) 83 | b2.update(bytes(serverkey)) 84 | self._nonce = b2.digest() 85 | 86 | def __bytes__(self): 87 | return self._nonce 88 | 89 | def __len__(self): 90 | return len(self._nonce) 91 | 92 | def increment(self): 93 | self._nonce = (int.from_bytes(self._nonce, byteorder="little") + 2).to_bytes(Box.NONCE_SIZE, byteorder="little") 94 | -------------------------------------------------------------------------------- /coc/protocol.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import protocol 2 | 3 | 4 | class CoCPacketReceiver(protocol.Protocol): 5 | 6 | _buffer = b"" 7 | _packet = b"" 8 | 9 | def dataReceived(self, data): 10 | self._buffer += data 11 | while self._buffer: 12 | if self._packet: 13 | payload_length = int.from_bytes(self._packet[2:5], byteorder="big") 14 | if len(self._buffer) >= payload_length: 15 | self._packet += self._buffer[:payload_length] 16 | self.packetReceived(self._packet) 17 | self._packet = b"" 18 | self._buffer = self._buffer[payload_length:] 19 | else: 20 | break 21 | elif len(self._buffer) >= 7: 22 | self._packet = self._buffer[:7] 23 | self._buffer = self._buffer[7:] 24 | 25 | def packetReceived(self, packet): 26 | raise NotImplementedError 27 | 28 | 29 | class CoCProtocol(CoCPacketReceiver): 30 | 31 | _peer = None 32 | 33 | factory = None 34 | server = None 35 | client = None 36 | 37 | def __init__(self, factory): 38 | self._factory = factory 39 | 40 | @property 41 | def factory(self): 42 | return self._factory 43 | 44 | @property 45 | def peer(self): 46 | return self._peer 47 | 48 | def connectionMade(self): 49 | self._peer = self.transport.getPeer() 50 | 51 | def packetReceived(self, packet): 52 | decrypted = self.decryptPacket(packet) 53 | if decrypted: 54 | self.packetDecrypted(*decrypted) 55 | 56 | def decodePacket(self, messageid, unknown, payload): 57 | try: 58 | decoded = self.decoder.decode(messageid, unknown, payload) 59 | except (KeyError, IndexError, NotImplementedError) as e: 60 | print(messageid, "Error:", e) 61 | print(messageid, (messageid.to_bytes(2, byteorder="big") + len(payload).to_bytes(3, byteorder="big") + unknown.to_bytes(2, byteorder="big") + payload).hex()) 62 | else: 63 | self.decoder.dump(decoded) 64 | 65 | def decryptPacket(self, packet): 66 | raise NotImplementedError 67 | 68 | def packetDecrypted(self, messageid, unknown, payload): 69 | raise NotImplementedError 70 | 71 | def sendPacket(self, messageid, unknown, payload): 72 | encrypted = self.encryptPacket(messageid, unknown, payload) 73 | if encrypted: 74 | (messageid, unknown, payload) = encrypted 75 | packet = (messageid.to_bytes(2, byteorder="big") + len(payload).to_bytes(3, byteorder="big") + unknown.to_bytes(2, byteorder="big") + payload) 76 | self.transport.write(packet) 77 | 78 | def encryptPacket(self, messageid, unknown, payload): 79 | raise NotImplementedError 80 | -------------------------------------------------------------------------------- /coc/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clugh/coc-proxy/fc59fb4cf5fb9b6706612ee3231bd00fac1eabad/coc/server/__init__.py -------------------------------------------------------------------------------- /coc/server/crypto.py: -------------------------------------------------------------------------------- 1 | from nacl.public import PrivateKey, PublicKey 2 | from nacl.exceptions import CryptoError 3 | from coc.crypto import CoCCrypto, CoCNonce 4 | 5 | 6 | class CoCServerCrypto(CoCCrypto): 7 | 8 | def __init__(self, factory): 9 | self._factory = factory 10 | self._sk = PrivateKey(bytes.fromhex("1891d401fadb51d25d3a9174d472a9f691a45b974285d47729c45c6538070d85")) 11 | self._pk = self._sk.public_key 12 | self._clientkey = None 13 | 14 | @property 15 | def serverkey(self): 16 | return bytes(self._pk) 17 | 18 | @property 19 | def clientkey(self): 20 | return bytes(self._clientkey) 21 | 22 | @clientkey.setter 23 | def clientkey(self, pk): 24 | self._clientkey = PublicKey(pk) 25 | 26 | def decryptPacket(self, packet): 27 | messageid = int.from_bytes(packet[:2], byteorder="big") 28 | unknown = int.from_bytes(packet[5:7], byteorder="big") 29 | payload = packet[7:] 30 | if messageid == 10100: 31 | return messageid, unknown, payload 32 | elif messageid == 10101: 33 | self.clientkey = payload[:32] 34 | self.beforenm(self.clientkey) 35 | nonce = CoCNonce(clientkey=self.clientkey, serverkey=self.serverkey) 36 | ciphertext = payload[32:] 37 | try: 38 | message = self.decrypt(ciphertext, nonce) 39 | except CryptoError: 40 | print("Failed to decrypt the message (server, {}).".format(messageid)) 41 | self.transport.loseConnection() 42 | return False 43 | else: 44 | self.decrypt_nonce = self.client.encrypt_nonce = message[24:48] 45 | return messageid, unknown, message[48:] 46 | else: 47 | ciphertext = payload 48 | message = self.decrypt(ciphertext) 49 | return messageid, unknown, message 50 | 51 | def encryptPacket(self, messageid, unknown, payload): 52 | if messageid == 20100 or (messageid == 20103 and not self.session_key): 53 | return messageid, unknown, payload 54 | elif messageid in {20103, 20104}: 55 | nonce = CoCNonce(nonce=self.decrypt_nonce, clientkey=self.clientkey, serverkey=self.serverkey) 56 | message = self.encrypt_nonce + self.client.k + payload 57 | ciphertext = self.encrypt(message, nonce) 58 | self.k = self.client.k 59 | return messageid, unknown, ciphertext 60 | else: 61 | message = payload 62 | return messageid, unknown, self.encrypt(message) 63 | -------------------------------------------------------------------------------- /coc/server/endpoint.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.endpoints import TCP4ServerEndpoint 2 | 3 | 4 | class CoCServerEndpoint(TCP4ServerEndpoint): 5 | 6 | @property 7 | def interface(self): 8 | if not self._interface: 9 | return "0.0.0.0" 10 | else: 11 | return self._interface 12 | 13 | @property 14 | def port(self): 15 | return self._port 16 | -------------------------------------------------------------------------------- /coc/server/factory.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.protocol import Factory 2 | from coc.server.protocol import CoCServerProtocol 3 | from coc.message.encoder import CoCMessageEncoder 4 | from coc.message.decoder import CoCMessageDecoder 5 | 6 | 7 | class CoCServerFactory(Factory): 8 | 9 | def __init__(self, client_endpoint, definitions): 10 | self.client_endpoint = client_endpoint 11 | self.encoder = CoCMessageEncoder(definitions) 12 | self.decoder = CoCMessageDecoder(definitions) 13 | 14 | def buildProtocol(self, endpoint): 15 | return CoCServerProtocol(self) 16 | -------------------------------------------------------------------------------- /coc/server/protocol.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import reactor 2 | from coc.protocol import CoCProtocol 3 | from coc.server.crypto import CoCServerCrypto 4 | from coc.client.factory import CoCClientFactory 5 | 6 | 7 | class CoCServerProtocol(CoCServerCrypto, CoCProtocol): 8 | 9 | client = None 10 | 11 | def __init__(self, factory): 12 | super(CoCServerProtocol, self).__init__(factory) 13 | self.factory.server = self 14 | self.decoder = self.factory.decoder 15 | self.encoder = self.factory.encoder 16 | 17 | def connectionMade(self): 18 | super(CoCServerProtocol, self).connectionMade() 19 | print("connection from {}:{} ...".format(self.peer.host, self.peer.port)) 20 | self.factory.client_endpoint.connect(CoCClientFactory(self)) 21 | 22 | def packetDecrypted(self, messageid, unknown, payload): 23 | if not self.client: 24 | reactor.callLater(0.25, self.packetDecrypted, messageid, unknown, payload) 25 | return 26 | self.decodePacket(messageid, unknown, payload) 27 | self.client.sendPacket(messageid, unknown, payload) 28 | 29 | def connectionLost(self, reason): 30 | print("connection from {}:{} closed ...".format(self.peer.host, self.peer.port)) 31 | if self.client: 32 | self.client.transport.loseConnection() 33 | -------------------------------------------------------------------------------- /proxy.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import reactor 2 | 3 | from coc.message.definitions import CoCMessageDefinitions 4 | from coc.server.endpoint import CoCServerEndpoint 5 | from coc.server.factory import CoCServerFactory 6 | from coc.client.endpoint import CoCClientEndpoint 7 | 8 | if __name__ == "__main__": 9 | 10 | definitions = CoCMessageDefinitions.read() 11 | 12 | client_endpoint = CoCClientEndpoint(reactor, "gamea.clashofclans.com", 9339) 13 | 14 | server_endpoint = CoCServerEndpoint(reactor, 9339) 15 | server_endpoint.listen(CoCServerFactory(client_endpoint, definitions)) 16 | 17 | print("listening on {}:{} ...".format(server_endpoint.interface, server_endpoint.port)) 18 | 19 | reactor.run() 20 | --------------------------------------------------------------------------------