From 3df984eb0d4af37dd42af015c3fdcfc188a74763 Mon Sep 17 00:00:00 2001 From: polaris Date: Mon, 31 Mar 2014 07:07:10 -0400 Subject: [PATCH] Fixed encryption, implemented more QR server stuff - Fixed the RC4/Base64 encryption method (seems to be more commonly known as "gsmsalg"). - Implemented more of the query and reporting server. --- gamespy/gs_utility.py | 5 ++- master_server.py | 95 ++++++++++++++++++++++++++++++++++++++----- server_browser.py | 34 ++++++++++++++++ 3 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 server_browser.py diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index 4b7fcf9..29cb4cb 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -42,11 +42,12 @@ def rc4_encrypt(_key, _data): return data # Tetris DS overlay 10 @ 0216E9B8 -# Used by the master server to send some data between the client and server +# Used by the master server to send some data between the client and server. +# This seems to be what Luigi Auriemma calls "Gsmsalg". def prepare_rc4_base64(_key, _data): data = rc4_encrypt(_key, _data) data.append(0) - return base64_encode(buffer(data)) + return base64.b64encode(buffer(data)) # Parse my custom authtoken generated by the emulated nas.nintendowifi.net/ac def parse_authtoken(authtoken): diff --git a/master_server.py b/master_server.py index 6ade677..3264cf5 100644 --- a/master_server.py +++ b/master_server.py @@ -1,4 +1,8 @@ # Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net +# Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview + +# TODO: Refactor into a class + import socket import gamespy.gs_utility as gs_utils import other.utils as utils @@ -15,6 +19,11 @@ s.bind(address) utils.print_log("Server is now listening on %s:%s..." % (address[0], address[1])) +# Temporarily make these global until everything is made into a client class. +server_challenge = "" +secretkey = "JJlSi8" # Parse gslist.cfg later +sent_challenge = False + while 1: recv_data, addr = s.recvfrom(2048) @@ -22,7 +31,7 @@ while 1: # Tetris DS overlay 10 @ 02144184 - Handle responses back to server # # After some more packet inspection, it seems the format goes something like this: - # - All server messages seem to always start with \xfe\xfe. + # - All server messages seem to always start with \xfe\xfd. # - The first byte from the client (or third byte from the server) is a command. # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a # session-like ID because the number changes between connections. Copying the client's ID might be enough. @@ -37,7 +46,7 @@ while 1: # # CLIENT: # 0x01 - Response (Tetris DS overlay 10 @ 216DCA4) - # Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01. Server doesn't respond? + # Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01. # # 0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30) # Data sent: @@ -96,21 +105,87 @@ while 1: # # - \xfd\xfc commands get passed directly between the other player(s)? # + # + # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr + # Use as reference. - if recv_data[0] == '\x01': - utils.print_log("Response received from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) - elif recv_data[0] == '\x03': - utils.print_log("Received client state from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) - elif recv_data[0] == '\x07': - utils.print_log("Received unknown data from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) - elif recv_data[0] == '\x08': + if recv_data[0] == '\x00': # Query + utils.print_log("NOT IMPLEMENTED! Received query from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + + elif recv_data[0] == '\x01': # Challenge + utils.print_log("Received challenge from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + + # Prepare the challenge sent from the server to be compared + challenge = gs_utils.prepare_rc4_base64(secretkey, server_challenge) + + # Compare challenge + client_challenge = recv_data[5:-1] + if client_challenge == challenge: + # Challenge succeeded + sent_challenge = True + + # Handle successful challenge stuff here + packet = bytearray([0xfe, 0xfd, 0x0a]) # Send client registered command + packet.extend(recv_data[1:5]) # Get the ID + s.sendto(packet, addr) + utils.print_log("Sent client registered to %s:%s..." % (addr[0], addr[1])) + + elif recv_data[0] == '\x02': # Echo + utils.print_log("NOT IMPLEMENTED! Received echo from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + + elif recv_data[0] == '\x03': # Heartbeat + data = recv_data[5:] + utils.print_log("Received heartbeat from %s:%s... %s" % (addr[0], addr[1], data)) + + # Parse information from heartbeat here + d = data.rstrip('\0').split('\0') + + # It may be safe to ignore "unknown" keys because the proper key names get filled in later... + k = {} + for i in range(len(d) / 2): + k[d[i]] = d[i+1] + + # Store k per client + + if sent_challenge == False: + addr_hex = ''.join(["%02X" % int(x) for x in addr[0].split('.')]) + port_hex = "%04X" % int(addr[1]) + server_challenge = utils.generate_random_str(8) + addr_hex + port_hex + + packet = bytearray([0xfe, 0xfd, 0x01]) # Send challenge command + packet.extend(recv_data[1:5]) # Get the ID + packet.extend(server_challenge) + packet.extend('\x00') + + s.sendto(packet, addr) + utils.print_log("Sent challenge to %s:%s..." % (addr[0], addr[1])) + + elif recv_data[0] == '\x04': # Add Error + utils.print_log("NOT IMPLEMENTED! Received add error from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + + elif recv_data[0] == '\x05': # Echo Response + utils.print_log("NOT IMPLEMENTED! Received echo response from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + + elif recv_data[0] == '\x06': # Client Message + utils.print_log("NOT IMPLEMENTED! Received echo from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + + elif recv_data[0] == '\x07': # Client Message Ack + utils.print_log("NOT IMPLEMENTED! Received client message ack from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + + elif recv_data[0] == '\x08': # Keep Alive utils.print_log("Received keep alive from %s:%s..." % (addr[0], addr[1])) - elif recv_data[0] == '\x09': + + elif recv_data[0] == '\x09': # Available # Availability check only sent to *.available.gs.nintendowifi.net utils.print_log("Received availability request for '%s' from %s:%s... %s" % ( get_game_id(recv_data), addr[0], addr[1], [elem.encode("hex") for elem in recv_data])) s.sendto(bytearray([0xfe, 0xfd, recv_data[0], recv_data[1], recv_data[2], recv_data[3], recv_data[4]]), addr) + + elif recv_data[0] == '\x0a': # Client Registered + # Only sent to client, never received? + utils.print_log("NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (addr[0], addr[1], recv_data[5:])) + else: utils.print_log( "Unknown request from %s:%s: %s" % (addr[0], addr[1], [elem.encode("hex") for elem in recv_data])) diff --git a/server_browser.py b/server_browser.py new file mode 100644 index 0000000..e463860 --- /dev/null +++ b/server_browser.py @@ -0,0 +1,34 @@ +# I found an open source implemention of this exact server I'm trying to emulate here: https://github.com/sfcspanky/Openspy-Core/blob/master/serverbrowsing/ +# Use as reference later. + +# Tetris DS won't let you search for a match unless this server exists, so just create an empty server for now. + +import socket +import gamespy.gs_utility as gs_utils +import other.utils as utils +import time + +def get_game_id(data): + game_id = data[5: -1] + return game_id + +#address = ('127.0.0.1', 28910) # accessible to only the local computer +address = ('0.0.0.0', 28910) # accessible to outside connections (use this if you don't know what you're doing) +backlog = 10 +size = 2048 + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.bind(address) +s.listen(backlog) + +utils.print_log("Server is now listening on %s:%s..." % (address[0], address[1])) + +while 1: + client, address = s.accept() + + utils.print_log("Received connection from %s:%s" % (address[0], address[1])) + + while 1: + accept_connection = True + data = client.recv(size).rstrip() + utils.print_log("RESPONSE: %s" % data)