├── .gitignore ├── .gitmodules ├── ClientState.cs ├── ServerState.cs ├── State.cs ├── Proxy.cs ├── Protocol.cs ├── Client.cs ├── README.md ├── Server.cs ├── PacketReceiver.cs ├── ServerCrypto.cs └── ClientCrypto.cs /.gitignore: -------------------------------------------------------------------------------- 1 | Properties 2 | bin 3 | obj 4 | *.config 5 | *.csproj -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "messages"] 2 | path = messages 3 | url = git@github.com:clugh/coc-messages-csharp.git 4 | -------------------------------------------------------------------------------- /ClientState.cs: -------------------------------------------------------------------------------- 1 | using Sodium; 2 | 3 | namespace coc_proxy_csharp 4 | { 5 | public class ClientState : State 6 | { 7 | public ServerState serverState; 8 | 9 | public KeyPair clientKey; 10 | public byte[] serverKey, nonce; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ServerState.cs: -------------------------------------------------------------------------------- 1 | using Sodium; 2 | 3 | namespace coc_proxy_csharp 4 | { 5 | public class ServerState : State 6 | { 7 | public ClientState clientState; 8 | 9 | public KeyPair serverKey; 10 | public byte[] clientKey, nonce, sessionKey, sharedKey; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /State.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | using coc_messages_csharp; 3 | 4 | namespace coc_proxy_csharp 5 | { 6 | public class State 7 | { 8 | public Socket socket = null; 9 | public const int BufferSize = 1024; 10 | public byte[] buffer = new byte[BufferSize]; 11 | public byte[] packet = new byte[0]; 12 | public Decoder decoder; 13 | 14 | public State() 15 | { 16 | this.decoder = new Decoder(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Proxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace coc_proxy_csharp 4 | { 5 | class Proxy 6 | { 7 | public static string hostname = "gamea.clashofclans.com"; 8 | public static int port = 9339; 9 | 10 | private static void Main(string[] args) 11 | { 12 | try 13 | { 14 | Server server = new Server(Proxy.port); 15 | server.StartServer(); 16 | } 17 | catch (Exception e) 18 | { 19 | Console.WriteLine(e.ToString()); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Protocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | 4 | namespace coc_proxy_csharp 5 | { 6 | public class Protocol 7 | { 8 | protected static void ReceiveCallback(IAsyncResult ar) 9 | { 10 | try 11 | { 12 | State state = (State)ar.AsyncState; 13 | Socket socket = state.socket; 14 | int bytesReceived = socket.EndReceive(ar); 15 | PacketReceiver.receive(bytesReceived, socket, state); 16 | socket.BeginReceive(state.buffer, 0, State.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); 17 | } 18 | catch (Exception e) 19 | { 20 | Console.WriteLine(e.ToString()); 21 | } 22 | } 23 | 24 | protected static void SendCallback(IAsyncResult ar) 25 | { 26 | try 27 | { 28 | State state = (State)ar.AsyncState; 29 | Socket socket = state.socket; 30 | int bytesSent = socket.EndSend(ar); 31 | } 32 | catch (Exception e) 33 | { 34 | Console.WriteLine(e.ToString()); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Client.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | 5 | namespace coc_proxy_csharp 6 | { 7 | public class Client : ClientCrypto 8 | { 9 | public ClientState state = new ClientState(); 10 | 11 | public Client(ServerState serverstate) 12 | { 13 | this.state.serverState = serverstate; 14 | this.state.clientKey = this.clientKey; 15 | this.state.serverKey = ClientCrypto.serverKey; 16 | } 17 | 18 | public void StartClient() 19 | { 20 | try 21 | { 22 | IPHostEntry ipHostInfo = Dns.GetHostEntry(Proxy.hostname); 23 | IPAddress ipAddress = ipHostInfo.AddressList[0]; 24 | IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, Proxy.port); 25 | 26 | Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 27 | this.state.socket = socket; 28 | socket.Connect(remoteEndPoint); 29 | socket.BeginReceive(this.state.buffer, 0, State.BufferSize, 0, new AsyncCallback(Protocol.ReceiveCallback), this.state); 30 | 31 | Console.WriteLine("Connected to {0} ...", socket.RemoteEndPoint.ToString()); 32 | } 33 | catch (Exception e) 34 | { 35 | Console.WriteLine(e.ToString()); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #coc-proxy-csharp 2 | 3 | Build, then run with: 4 | 5 | coc-proxy-csharp.exe 6 | 7 | **Warning**: This does not automatically close the other side when the connection to the client or server closes. In fact, it doesn't even detect that either side has closed and will still try to send data on the closed socket. 8 | 9 | ##Installation 10 | 11 | Install [libsodium-net](https://github.com/adamcaudill/libsodium-net) with NuGet. 12 | 13 | Patch `libg.so` with: 14 | 15 | adb pull /data/data/com.supercell.clashofclans/lib/libg.so 16 | dd if=libg.so of=key-backup.bin skip=$( key-new.bin 19 | xxd -p -c 32 key-new.bin 20 | dd if=key-new.bin of=libg.so seek=$( 0) 17 | { 18 | if (state.packet.Length >= 7) 19 | { 20 | payloadLength = BitConverter.ToInt32(new byte[1].Concat(state.packet.Skip(2).Take(3)).Reverse().ToArray(), 0); 21 | bytesNeeded = payloadLength - (state.packet.Length - 7); 22 | if (bytesAvailable >= bytesNeeded) 23 | { 24 | state.packet = state.packet.Concat(state.buffer.Skip(bytesRead).Take(bytesNeeded)).ToArray(); 25 | bytesRead += bytesNeeded; 26 | bytesAvailable -= bytesNeeded; 27 | if (state.GetType() == typeof(ClientState)) 28 | { 29 | ClientCrypto.DecryptPacket(socket, (ClientState)state, state.packet); 30 | } 31 | else if (state.GetType() == typeof(ServerState)) 32 | { 33 | ServerCrypto.DecryptPacket(socket, (ServerState)state, state.packet); 34 | } 35 | state.packet = new byte[0]; 36 | } 37 | else 38 | { 39 | state.packet = state.packet.Concat(state.buffer.Skip(bytesRead).Take(bytesAvailable)).ToArray(); 40 | bytesRead = bytesReceived; 41 | bytesAvailable = 0; 42 | } 43 | } 44 | else if (bytesAvailable >= 7) 45 | { 46 | state.packet = state.packet.Concat(state.buffer.Skip(bytesRead).Take(7)).ToArray(); 47 | bytesRead += 7; 48 | bytesAvailable -= 7; 49 | } 50 | else 51 | { 52 | state.packet = state.packet.Concat(state.buffer.Skip(bytesRead).Take(bytesAvailable)).ToArray(); 53 | bytesRead = bytesReceived; 54 | bytesAvailable = 0; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ServerCrypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | using System.Linq; 4 | using Sodium; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace coc_proxy_csharp 8 | { 9 | public class ServerCrypto : Protocol 10 | { 11 | protected static KeyPair serverKey = PublicKeyBox.GenerateKeyPair(Utilities.HexToBinary("1891d401fadb51d25d3a9174d472a9f691a45b974285d47729c45c6538070d85")); 12 | 13 | public static void DecryptPacket(Socket socket, ServerState state, byte[] packet) 14 | { 15 | int messageId = BitConverter.ToInt32(new byte[2].Concat(packet.Take(2)).Reverse().ToArray(), 0); 16 | int payloadLength = BitConverter.ToInt32(new byte[1].Concat(packet.Skip(2).Take(3)).Reverse().ToArray(), 0); 17 | int unknown = BitConverter.ToInt32(new byte[2].Concat(packet.Skip(2).Skip(3).Take(2)).Reverse().ToArray(), 0); 18 | byte[] cipherText = packet.Skip(2).Skip(3).Skip(2).ToArray(); 19 | byte[] plainText; 20 | 21 | if (messageId == 10100) 22 | { 23 | plainText = cipherText; 24 | } 25 | else if (messageId == 10101) 26 | { 27 | state.clientKey = cipherText.Take(32).ToArray(); 28 | byte[] nonce = GenericHash.Hash(state.clientKey.Concat(state.serverKey.PublicKey).ToArray(), null, 24); 29 | cipherText = cipherText.Skip(32).ToArray(); 30 | plainText = PublicKeyBox.Open(cipherText, nonce, state.serverKey.PrivateKey, state.clientKey); 31 | state.sessionKey = plainText.Take(24).ToArray(); 32 | state.clientState.nonce = plainText.Skip(24).Take(24).ToArray(); 33 | plainText = plainText.Skip(24).Skip(24).ToArray(); 34 | } 35 | else 36 | { 37 | state.clientState.nonce = Utilities.Increment(Utilities.Increment(state.clientState.nonce)); 38 | plainText = SecretBox.Open(new byte[16].Concat(cipherText).ToArray(), state.clientState.nonce, state.sharedKey); 39 | } 40 | try 41 | { 42 | JObject decoded = state.decoder.decode(messageId, unknown, plainText); 43 | Console.WriteLine("{0}: {1}", decoded["name"], decoded["fields"]); 44 | } 45 | catch (Exception e) 46 | { 47 | Console.WriteLine(e.Message); 48 | Console.WriteLine("{0} {1}", messageId, Utilities.BinaryToHex(BitConverter.GetBytes(messageId).Reverse().Skip(2).Concat(BitConverter.GetBytes(plainText.Length).Reverse().Skip(1)).Concat(BitConverter.GetBytes(unknown).Reverse().Skip(2)).Concat(plainText).ToArray())); 49 | } 50 | ClientCrypto.EncryptPacket(state.clientState.socket, state.clientState, messageId, unknown, plainText); 51 | } 52 | 53 | public static void EncryptPacket(Socket socket, ServerState state, int messageId, int unknown, byte[] plainText) 54 | { 55 | byte[] cipherText; 56 | if (messageId == 20100 || (messageId == 20103 && state.sharedKey == null)) 57 | { 58 | cipherText = plainText; 59 | } 60 | else if (messageId == 20103 || messageId == 20104) 61 | { 62 | byte[] nonce = GenericHash.Hash(state.clientState.nonce.Concat(state.clientKey).Concat(state.serverKey.PublicKey).ToArray(), null, 24); 63 | plainText = state.nonce.Concat(state.sharedKey).Concat(plainText).ToArray(); 64 | cipherText = PublicKeyBox.Create(plainText, nonce, state.serverKey.PrivateKey, state.clientKey); 65 | } 66 | else 67 | { 68 | // nonce was already incremented in ClientCrypto.DecryptPacket 69 | cipherText = SecretBox.Create(plainText, state.nonce, state.sharedKey).Skip(16).ToArray(); 70 | } 71 | byte[] packet = BitConverter.GetBytes(messageId).Reverse().Skip(2).Concat(BitConverter.GetBytes(cipherText.Length).Reverse().Skip(1)).Concat(BitConverter.GetBytes(unknown).Reverse().Skip(2)).Concat(cipherText).ToArray(); 72 | socket.BeginSend(packet, 0, packet.Length, 0, new AsyncCallback(SendCallback), state); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ClientCrypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.Sockets; 4 | using Sodium; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace coc_proxy_csharp 8 | { 9 | public class ClientCrypto : Protocol 10 | { 11 | protected KeyPair clientKey = PublicKeyBox.GenerateKeyPair(); 12 | protected static byte[] serverKey = Utilities.HexToBinary("01c98c143a840d92ee656996dad5af41de5d1b8ebb289081368b5cfda9bd4a30"); 13 | 14 | public static void DecryptPacket(Socket socket, ClientState state, byte[] packet) 15 | { 16 | int messageId = BitConverter.ToInt32(new byte[2].Concat(packet.Take(2)).Reverse().ToArray(), 0); 17 | int payloadLength = BitConverter.ToInt32(new byte[1].Concat(packet.Skip(2).Take(3)).Reverse().ToArray(), 0); 18 | int unknown = BitConverter.ToInt32(new byte[2].Concat(packet.Skip(2).Skip(3).Take(2)).Reverse().ToArray(), 0); 19 | byte[] cipherText = packet.Skip(2).Skip(3).Skip(2).ToArray(); 20 | byte[] plainText; 21 | 22 | if (messageId == 20100 || (messageId == 20103 && state.serverState.sharedKey == null)) 23 | { 24 | plainText = cipherText; 25 | } 26 | else if (messageId == 20103 || messageId == 20104) 27 | { 28 | byte[] nonce = GenericHash.Hash(state.nonce.Concat(state.clientKey.PublicKey).Concat(state.serverKey).ToArray(), null, 24); 29 | plainText = PublicKeyBox.Open(cipherText, nonce, state.clientKey.PrivateKey, state.serverKey); 30 | state.serverState.nonce = plainText.Take(24).ToArray(); 31 | state.serverState.sharedKey = plainText.Skip(24).Take(32).ToArray(); 32 | plainText = plainText.Skip(24).Skip(32).ToArray(); 33 | } 34 | else 35 | { 36 | state.serverState.nonce = Utilities.Increment(Utilities.Increment(state.serverState.nonce)); 37 | plainText = SecretBox.Open(new byte[16].Concat(cipherText).ToArray(), state.serverState.nonce, state.serverState.sharedKey); 38 | } 39 | try 40 | { 41 | JObject decoded = state.decoder.decode(messageId, unknown, plainText); 42 | Console.WriteLine("{0}: {1}", decoded["name"], decoded["fields"]); 43 | } 44 | catch (Exception e) 45 | { 46 | Console.WriteLine(e.Message); 47 | Console.WriteLine("{0} {1}", messageId, Utilities.BinaryToHex(BitConverter.GetBytes(messageId).Reverse().Skip(2).Concat(BitConverter.GetBytes(plainText.Length).Reverse().Skip(1)).Concat(BitConverter.GetBytes(unknown).Reverse().Skip(2)).Concat(plainText).ToArray())); 48 | } 49 | ServerCrypto.EncryptPacket(state.serverState.socket, state.serverState, messageId, unknown, plainText); 50 | } 51 | 52 | public static void EncryptPacket(Socket socket, ClientState state, int messageId, int unknown, byte[] plainText) 53 | { 54 | byte[] cipherText; 55 | if (messageId == 10100) 56 | { 57 | cipherText = plainText; 58 | } 59 | else if (messageId == 10101) 60 | { 61 | byte[] nonce = GenericHash.Hash(state.clientKey.PublicKey.Concat(state.serverKey).ToArray(), null, 24); 62 | plainText = state.serverState.sessionKey.Concat(state.nonce).Concat(plainText).ToArray(); 63 | cipherText = PublicKeyBox.Create(plainText, nonce, state.clientKey.PrivateKey, state.serverKey); 64 | cipherText = state.clientKey.PublicKey.Concat(cipherText).ToArray(); 65 | } 66 | else 67 | { 68 | // nonce was already incremented in ServerCrypto.DecryptPacket 69 | cipherText = SecretBox.Create(plainText, state.nonce, state.serverState.sharedKey).Skip(16).ToArray(); 70 | } 71 | byte[] packet = BitConverter.GetBytes(messageId).Reverse().Skip(2).Concat(BitConverter.GetBytes(cipherText.Length).Reverse().Skip(1)).Concat(BitConverter.GetBytes(unknown).Reverse().Skip(2)).Concat(cipherText).ToArray(); 72 | socket.BeginSend(packet, 0, packet.Length, 0, new AsyncCallback(SendCallback), state); 73 | } 74 | } 75 | } 76 | --------------------------------------------------------------------------------