From c4772ad84aaa910cb92bcf31ac0da8e31686e234 Mon Sep 17 00:00:00 2001 From: Samy Kamkar Date: Sun, 18 Nov 2018 11:53:47 -0800 Subject: [PATCH 1/7] Support Frida connection over network --- Main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Main.py b/Main.py index 86af93c..9765f9e 100644 --- a/Main.py +++ b/Main.py @@ -26,14 +26,17 @@ def onClose(udp_protocol): udp_protocol.packetProcessor.stop() -def start_frida_script(): +def start_frida_script(network): # Would be better to use frida.get_usb_device().spawn to spawn the app # But it seems that it is broken on some version so we use adb to spawn the game os.system("adb shell monkey -p com.supercell.clashroyale -c android.intent.category.LAUNCHER 1") time.sleep(0.5) try: - device = frida.get_usb_device() + if network: + device = frida.get_remote_device() + else: + device = frida.get_usb_device() except Exception as exception: print('[*] Can\'t connect to your device ({}) !'.format(exception.__class__.__name__)) @@ -71,6 +74,7 @@ def start_frida_script(): if __name__ == '__main__': parser = argparse.ArgumentParser(description='Python proxy used to decrypt all clash royale game traffic') parser.add_argument('-f', '--frida', help='inject the frida script at the proxy runtime', action='store_true') + parser.add_argument('-n', '--network', help='connect to frida via network rather than USB', action='store_true') parser.add_argument('-v', '--verbose', help='print packet hexdump in console', action='store_true') parser.add_argument('-r', '--replay', help='save packets in replay folder', action='store_true') parser.add_argument('-u', '--udp', help='start the udp proxy', action='store_true') @@ -85,7 +89,7 @@ def start_frida_script(): exit() if args.frida: - start_frida_script() + start_frida_script(args.network) crypto = Crypto(config['ServerKey']) replay = Replay(config['ReplayDirectory']) From c90f066904f637a5c62a8f2a2acc691a54ca1e76 Mon Sep 17 00:00:00 2001 From: Samy Kamkar Date: Sun, 18 Nov 2018 11:58:35 -0800 Subject: [PATCH 2/7] Allow optional path to adb --- Main.py | 7 ++++--- README.md | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Main.py b/Main.py index 9765f9e..fee3212 100644 --- a/Main.py +++ b/Main.py @@ -26,10 +26,10 @@ def onClose(udp_protocol): udp_protocol.packetProcessor.stop() -def start_frida_script(network): +def start_frida_script(network, adbpath): # Would be better to use frida.get_usb_device().spawn to spawn the app # But it seems that it is broken on some version so we use adb to spawn the game - os.system("adb shell monkey -p com.supercell.clashroyale -c android.intent.category.LAUNCHER 1") + os.system(adbpath + " shell monkey -p com.supercell.clashroyale -c android.intent.category.LAUNCHER 1") time.sleep(0.5) try: @@ -78,6 +78,7 @@ def start_frida_script(network): parser.add_argument('-v', '--verbose', help='print packet hexdump in console', action='store_true') parser.add_argument('-r', '--replay', help='save packets in replay folder', action='store_true') parser.add_argument('-u', '--udp', help='start the udp proxy', action='store_true') + parser.add_argument('-a', '--adbpath', help='path to adb', default='adb') args = parser.parse_args() @@ -89,7 +90,7 @@ def start_frida_script(network): exit() if args.frida: - start_frida_script(args.network) + start_frida_script(args.network, args.adbpath) crypto = Crypto(config['ServerKey']) replay = Replay(config['ReplayDirectory']) diff --git a/README.md b/README.md index 4fcb3e3..b863192 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,12 @@ To start the proxy you will just have to run the following command: However the proxy accept some optionals arguments that are: -* `-f`: if specified, the game will be automatically spawned and the frida script will be injected at proxy runtime -* `-v`: if specified, the proxy will be run in verbose mode that basically output packets hexdump in terminal -* `-r`: if specified, all packets will be saved in the repository you've set in config.json (ReplayDirectory key) -* `-u`: if specified UDP proxy will be launched too +* `-f`: the game will be automatically spawned and the frida script will be injected at proxy runtime +* `-n`: connect to frida via network rather than USB +* `-v`: the proxy will be run in verbose mode that basically output packets hexdump in terminal +* `-r`: all packets will be saved in the repository you've set in config.json (ReplayDirectory key) +* `-u`: UDP proxy will be launched too +* `-a`: optional path to adb, useful if using specific adb/Android emulator ### UDP Proxy From 2cab495d416c43ddfd33252606bea1c4eabedb76 Mon Sep 17 00:00:00 2001 From: Samy Kamkar Date: Sun, 18 Nov 2018 12:15:59 -0800 Subject: [PATCH 3/7] Support UDP packet interpretation over TCP --- TCP/Server/protocol.py | 3 ++- UDP/packetEnum.py | 2 +- UDP/packetProcessor.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/TCP/Server/protocol.py b/TCP/Server/protocol.py index ab11be4..c841f6b 100644 --- a/TCP/Server/protocol.py +++ b/TCP/Server/protocol.py @@ -6,6 +6,7 @@ from TCP.Client.factory import ClientFactory from TCP.PacketReceiver import packetReceiver from TCP.Packet.packetEnum import packet_enum +from UDP.packetEnum import udp_packet_enum from twisted.internet.protocol import Protocol @@ -33,7 +34,7 @@ def processPacket(self, packet_id, data): reactor.callLater(0.25, self.processPacket, packet_id, data) return - packet_name = packet_enum.get(packet_id, packet_id) + packet_name = packet_enum.get(packet_id, udp_packet_enum.get(packet_id, packet_id)) print('[*] {} received from client'.format(packet_name)) diff --git a/UDP/packetEnum.py b/UDP/packetEnum.py index c0d9af7..1a11b46 100644 --- a/UDP/packetEnum.py +++ b/UDP/packetEnum.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -packet_enum = { +udp_packet_enum = { # Client Packet 15620: 'KeepAlive', 17187: 'SectorCommand', diff --git a/UDP/packetProcessor.py b/UDP/packetProcessor.py index 644deab..fddc354 100644 --- a/UDP/packetProcessor.py +++ b/UDP/packetProcessor.py @@ -3,7 +3,7 @@ from queue import Queue from threading import Thread from TCP.Packet.reader import Reader -from UDP.packetEnum import packet_enum +from UDP.packetEnum import udp_packet_enum class packetProcessor(Thread): @@ -51,7 +51,7 @@ def run(self): for packet in reversed(packet_list): if packet['sequence_id'] == host_dict['next_sequence_id']: - packet_name = packet_enum.get(packet['id'], packet['id']) + packet_name = udp_packet_enum.get(packet['id'], packet['id']) print('[*] Received UDP chunk {} from {}, chunk length: {}'.format( packet_name, host, From 8a5dde52b92053db14e8f5442fab56c619422a86 Mon Sep 17 00:00:00 2001 From: Samy Kamkar Date: Sun, 18 Nov 2018 12:17:47 -0800 Subject: [PATCH 4/7] Add EndClientTurnMessage --- UDP/packetEnum.py | 1 + 1 file changed, 1 insertion(+) diff --git a/UDP/packetEnum.py b/UDP/packetEnum.py index 1a11b46..42c35b4 100644 --- a/UDP/packetEnum.py +++ b/UDP/packetEnum.py @@ -5,6 +5,7 @@ # Client Packet 15620: 'KeepAlive', 17187: 'SectorCommand', + 17104: 'EndClientTurnMessage', # Server Packet 26301: 'SectorHearbeat' From 423fa62f1e38d5f9071527d975b21c6b05a94fb8 Mon Sep 17 00:00:00 2001 From: Samy Kamkar Date: Sun, 18 Nov 2018 22:27:01 -0800 Subject: [PATCH 5/7] Support UDP packets + tunnel before anything else --- TCP/Server/protocol.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TCP/Server/protocol.py b/TCP/Server/protocol.py index c841f6b..16b7624 100644 --- a/TCP/Server/protocol.py +++ b/TCP/Server/protocol.py @@ -34,11 +34,15 @@ def processPacket(self, packet_id, data): reactor.callLater(0.25, self.processPacket, packet_id, data) return + decrypted = self.crypto.decrypt_client_packet(packet_id, data[7:]) + encrypted = self.crypto.encrypt_client_packet(packet_id, decrypted) + payload = packet_id.to_bytes(2, 'big') + len(encrypted).to_bytes(3, 'big') + data[5:7] + encrypted + packet_name = packet_enum.get(packet_id, udp_packet_enum.get(packet_id, packet_id)) print('[*] {} received from client'.format(packet_name)) - decrypted = self.crypto.decrypt_client_packet(packet_id, data[7:]) + self.client.transport.write(payload) if self.factory.args.verbose and decrypted: print(hexdump.hexdump(decrypted)) @@ -46,7 +50,3 @@ def processPacket(self, packet_id, data): if self.factory.args.replay: self.factory.replay.save_tcp_packet(packet_name, data[:7] + decrypted) - encrypted = self.crypto.encrypt_client_packet(packet_id, decrypted) - payload = packet_id.to_bytes(2, 'big') + len(encrypted).to_bytes(3, 'big') + data[5:7] + encrypted - - self.client.transport.write(payload) From f45fb132736ee3c8d6235fe312b1e0ce6a27f2bb Mon Sep 17 00:00:00 2001 From: Samy Kamkar Date: Sun, 18 Nov 2018 22:27:25 -0800 Subject: [PATCH 6/7] Tunnel packets before anything else for speed --- TCP/Client/protocol.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/TCP/Client/protocol.py b/TCP/Client/protocol.py index 371527d..e57160e 100644 --- a/TCP/Client/protocol.py +++ b/TCP/Client/protocol.py @@ -4,6 +4,7 @@ from TCP.PacketReceiver import packetReceiver from TCP.Packet.packetEnum import packet_enum +from UDP.packetEnum import udp_packet_enum from twisted.internet.protocol import Protocol @@ -24,10 +25,6 @@ def connectionLost(self, reason): self.server.transport.loseConnection() def processPacket(self, packet_id, data): - packet_name = packet_enum.get(packet_id, packet_id) - - print('[*] {} received from server'.format(packet_name)) - decrypted = self.crypto.decrypt_server_packet(packet_id, data[7:]) if packet_id == 27579 and self.server.factory.args.udp: @@ -35,13 +32,18 @@ def processPacket(self, packet_id, data): decrypted = self.server.factory.udp_protocol.build_udp_info_packet(client_host, decrypted) + encrypted = self.crypto.encrypt_server_packet(packet_id, decrypted) + payload = packet_id.to_bytes(2, 'big') + len(encrypted).to_bytes(3, 'big') + data[5:7] + encrypted + + self.server.transport.write(payload) + + packet_name = packet_enum.get(packet_id, udp_packet_enum.get(packet_id, packet_id)) + + print('[*] {} received from server'.format(packet_name)) + if self.server.factory.args.verbose and decrypted: print(hexdump.hexdump(decrypted)) if self.server.factory.args.replay: self.server.factory.replay.save_tcp_packet(packet_name, data[:7] + decrypted) - encrypted = self.crypto.encrypt_server_packet(packet_id, decrypted) - payload = packet_id.to_bytes(2, 'big') + len(encrypted).to_bytes(3, 'big') + data[5:7] + encrypted - - self.server.transport.write(payload) From 49b26b918721c0d26f6ec1b67da3ad5b8cad31c5 Mon Sep 17 00:00:00 2001 From: Samy Kamkar Date: Wed, 21 Nov 2018 22:28:57 -0800 Subject: [PATCH 7/7] Forward packets to external UDP server --- TCP/Client/protocol.py | 7 +++++++ TCP/Server/protocol.py | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/TCP/Client/protocol.py b/TCP/Client/protocol.py index e57160e..fc05064 100644 --- a/TCP/Client/protocol.py +++ b/TCP/Client/protocol.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- import hexdump +import socket from TCP.PacketReceiver import packetReceiver from TCP.Packet.packetEnum import packet_enum from UDP.packetEnum import udp_packet_enum from twisted.internet.protocol import Protocol +UDP_IP = "127.0.0.1" +UDP_PORT = 9340 class ClientProtocol(packetReceiver, Protocol): @@ -15,6 +18,7 @@ def __init__(self, factory): self.factory.server.client = self self.server = self.factory.server self.crypto = self.server.crypto + self.udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) def connectionMade(self): self.peer = self.transport.getPeer() @@ -36,6 +40,9 @@ def processPacket(self, packet_id, data): payload = packet_id.to_bytes(2, 'big') + len(encrypted).to_bytes(3, 'big') + data[5:7] + encrypted self.server.transport.write(payload) + bstr = (packet_id).to_bytes(2, byteorder='big') + if len(decrypted) < 40: + self.udpsock.sendto(bstr+decrypted, (UDP_IP, UDP_PORT)) packet_name = packet_enum.get(packet_id, udp_packet_enum.get(packet_id, packet_id)) diff --git a/TCP/Server/protocol.py b/TCP/Server/protocol.py index 16b7624..e253c19 100644 --- a/TCP/Server/protocol.py +++ b/TCP/Server/protocol.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import hexdump +import socket from twisted.internet import reactor from TCP.Client.factory import ClientFactory @@ -9,7 +10,8 @@ from UDP.packetEnum import udp_packet_enum from twisted.internet.protocol import Protocol - +UDP_IP = "127.0.0.1" +UDP_PORT = 9340 class ServerProtocol(packetReceiver, Protocol): def __init__(self, factory): @@ -17,6 +19,7 @@ def __init__(self, factory): self.factory.server = self self.crypto = self.factory.crypto self.client = None + self.udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) def connectionMade(self): self.peer = self.transport.getPeer() @@ -43,6 +46,9 @@ def processPacket(self, packet_id, data): print('[*] {} received from client'.format(packet_name)) self.client.transport.write(payload) + bstr = (packet_id).to_bytes(2, byteorder='big') + if len(decrypted) < 40: + self.udpsock.sendto(bstr+decrypted, (UDP_IP, UDP_PORT)) if self.factory.args.verbose and decrypted: print(hexdump.hexdump(decrypted))