Example #1
0
def parseFirstMessage(orig_msg):
    """Parse the first (client's) message from the game protocol.

    Args:
        orig_msg (NetworkMessage): the network message to be parsed.

    Returns list
    """
    orig_msg.skipBytes(16)
    msg_buf = RSA.RSA_decrypt(orig_msg.getRest()[:128])
    msg = NetworkMessage(msg_buf)
    # Extract the XTEA keys from the RSA-decrypted message.
    xtea_key = [msg.getU32() for _ in range(4)]
    assert (msg.getByte() == 0)  # gamemaster flag
    account_number = msg.getString()  # account
    character_name = msg.getString()  # character name
    password = msg.getString()  # password
    challenge_pos = msg.getPos()
    timestamp = msg.getU32()
    random_number = msg.getByte()
    return create_handshake_reply(xtea_key=xtea_key,
                                  account_number=account_number,
                                  password=password,
                                  character_name=character_name,
                                  timestamp=timestamp,
                                  random_number=random_number,
                                  challenge_pos=challenge_pos,
                                  first_16=orig_msg.getRaw()[:16],
                                  decrypted_raw=bytearray(msg_buf))
Example #2
0
def parseFirstMessage(msg):
    """Parse the first (client's) message from the login protocol.

    Args:
        msg (NetworkMessage): the network message to be parsed.
        skip_bytes (int): the offset at which is the RSA-encrypted message.

    Returns list
    """
    msg.skipBytes(28)
    msg_buf = RSA.RSA_decrypt(msg.getRest()[:128])
    msg = NetworkMessage(msg_buf)
    # Extract the XTEA keys from the RSA-decrypted message.
    return [msg.getU32() for _ in range(4)]
Example #3
0
def parseFirstMessage(orig_msg):
    """Parse the first (client's) message from the game protocol.

    Args:
        orig_msg (NetworkMessage): the network message to be parsed.

    Returns list
    """
    orig_msg.skipBytes(16)
    msg_buf = RSA.RSA_decrypt(orig_msg.getRest()[:128])
    msg = NetworkMessage(msg_buf)
    # Extract the XTEA keys from the RSA-decrypted message.
    xtea_key = [msg.getU32() for _ in range(4)]
    assert(msg.getByte() == 0)        # gamemaster flag
    account_number = msg.getString()  # account
    character_name = msg.getString()  # character name
    password = msg.getString()        # password
    challenge_pos = msg.getPos()
    timestamp = msg.getU32()
    random_number = msg.getByte()
    return create_handshake_reply(xtea_key=xtea_key,
                                  account_number=account_number,
                                  password=password,
                                  character_name=character_name,
                                  timestamp=timestamp,
                                  random_number=random_number,
                                  challenge_pos=challenge_pos,
                                  first_16=orig_msg.getRaw()[:16],
                                  decrypted_raw=bytearray(msg_buf))
Example #4
0
def parseFirstMessage(msg):
    """Parse the first (client's) message from the login protocol.

    Args:
        msg (NetworkMessage): the network message to be parsed.
        skip_bytes (int): the offset at which is the RSA-encrypted message.

    Returns list
    """
    msg.skipBytes(28)
    msg_buf = RSA.RSA_decrypt(msg.getRest()[:128])
    msg = NetworkMessage(msg_buf)
    # Extract the XTEA keys from the RSA-decrypted message.
    return [msg.getU32() for _ in range(4)]
Example #5
0
    def handleLogin(self, conn, msg):
        """Handles the login communication, passing it to the destination host,
        modifying the server IPs and returning the modified character list to
        the user.

        Args:
            conn (socket): the already established connection.
            msg (NetworkMessage): the first message received.

        Returns None
        """
        xtea_key = LoginProtocol.parseFirstMessage(msg)

        # Connect to the destination host, send the request and read the reply.
        dest_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        log("Connecting to the destination host...")
        dest_s.connect((self.destination_login_host,
                        self.destination_login_port))
        if not self.real_tibia:
            dest_s.send(msg.getRaw())
        else:
            reencrypted = RSA.RSA_encrypt(RSA.RSA_decrypt(msg.getRaw()[28:]))
            unencrypted = msg.getRaw()[6:28]
            new_buf = ""
            new_buf += msg.getRaw()[:2]
            new_buf += struct.pack("<I",
                                   adlerChecksum(unencrypted+reencrypted))
            new_buf += unencrypted
            new_buf += reencrypted
            dest_s.send(new_buf)
        data = dest_s.recv(1024)
        if data == '':
            log("Server disconnected.")
            conn.close()
            return
        msg = NetworkMessage(data)
        reply = LoginProtocol.parseReply(msg, xtea_key)
        if reply is None:
            # The reply doesn't seem to contain character list - just forward
            # it.
            log("WARNING: Passing through the login request.")
            conn.send(data)
            conn.close()
            return

        for character in reply['characters']:
            self.characters[character['name']] = character

        # Replace the IP and port with the address to the proxy.
        client_reply = copy.deepcopy(reply)
        for world in client_reply['worlds']:
            world['hostname'] = self.announce_host
            world['port'] = self.announce_port
        client_reply_msg = LoginProtocol.prepareReply(client_reply)
        # Send the message and close the connection.
        conn.send(client_reply_msg.getEncrypted(xtea_key))
        conn.close()
Example #6
0
 def accept_login_conn():
     conn, addr = self.l_s.accept()
     log("Received a login connection from %s:%s" % addr)
     data = conn.recv(1024)
     msg = NetworkMessage(data)
     if not self.debug:
         t = threading.Thread(target=self.handleLogin, args=[conn, msg])
         t.start()
     else:
         self.handleLogin(conn, msg)
Example #7
0
def prepareReply(handshake_reply, real_tibia):
    """Create a handshake reply based on the dictionary from the argument that
    has a modified challenge response.

    handshake_reply (dict): the origina handshake reply dictionary
    real_tibia (bool): whether to reencrypt the message for real Tibia

    Returns bytearray
    """

    to_encrypt_raw = handshake_reply['decrypted_raw']
    to_encrypt_msg = NetworkMessage(to_encrypt_raw)
    to_encrypt_msg.skipBytes(handshake_reply['challenge_pos'])
    to_encrypt_msg.replaceU32(handshake_reply['timestamp'])
    to_encrypt_msg.replaceByte(handshake_reply['random_number'])
    to_encrypt = to_encrypt_msg.getRaw()
    first16_wo_headers = handshake_reply['first_16'][6:]
    if real_tibia:
        encrypted = RSA.RSA_encrypt(to_encrypt)
    else:
        encrypted = RSA.RSA_encrypt(to_encrypt, n=RSA.otserv_n)
    rest = first16_wo_headers + encrypted
    checksum = struct.pack("<I", adlerChecksum(rest))
    return (handshake_reply['first_16'][:2] + checksum + rest)
Example #8
0
def prepareReply(handshake_reply, real_tibia):
    """Create a handshake reply based on the dictionary from the argument that
    has a modified challenge response.

    handshake_reply (dict): the origina handshake reply dictionary
    real_tibia (bool): whether to reencrypt the message for real Tibia

    Returns bytearray
    """

    to_encrypt_raw = handshake_reply['decrypted_raw']
    to_encrypt_msg = NetworkMessage(to_encrypt_raw)
    to_encrypt_msg.skipBytes(handshake_reply['challenge_pos'])
    to_encrypt_msg.replaceU32(handshake_reply['timestamp'])
    to_encrypt_msg.replaceByte(handshake_reply['random_number'])
    to_encrypt = to_encrypt_msg.getRaw()
    first16_wo_headers = handshake_reply['first_16'][6:]
    if real_tibia:
        encrypted = RSA.RSA_encrypt(to_encrypt)
    else:
        encrypted = RSA.RSA_encrypt(to_encrypt, n=RSA.otserv_n)
    rest = first16_wo_headers + encrypted
    checksum = struct.pack("<I", adlerChecksum(rest))
    return (handshake_reply['first_16'][:2] + checksum + rest)
Example #9
0
 def client_send_said(self, player, pos, msg):
     sendmsg = NetworkMessage()
     sendmsg.addByte(0xAA)
     sendmsg.addU32(3)  # statement ID
     sendmsg.addString("1")
     sendmsg.addU16(1)  # level
     sendmsg.addByte(1)  # type: SPEAK_SAY
     assert(len(pos) == 3)
     sendmsg.addU16(pos[0])
     sendmsg.addU16(pos[1])
     sendmsg.addByte(pos[2])
     sendmsg.writable = True  # FIXME
     sendmsg.addString(msg)
     self.conn.send(sendmsg.getEncrypted(self.xtea_key))
Example #10
0
    def handleGame(self, conn):
        """Connect to the game server, relay the packets between the the
        player, the proxy and the game server, reading them and running
        callbacks based on incoming/outgoing data.

        TODO: add anything beyond the eval() proof of concept. This function
        is one giant kludge at the moment.

        Args:
            conn (socket): the already established connection.
            data (str): the raw data sent by the player.

        Returns None
        """

        # send a bogus challenge = 109, timestamp = 1385139009
        conn.send(b'\x0c\x00@\x02!\x07\x06\x00\x1fA\x8b\x8fRm')

        data = conn.recv(2)
        size = struct.unpack("<H", data)[0]
        data += conn.recv(size)
        # Read the XTEA key from the player, pass on the original packet.
        msg = NetworkMessage(data)
        firstmsg_contents = GameProtocol.parseFirstMessage(msg)

        # Connect to the game server.
        dest_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        character = self.characters[firstmsg_contents['character_name']]
        game_host = character['world']['hostname']
        game_port = character['world']['port']
        log("Connecting to the game server (%s:%s)." % (game_host, game_port))
        dest_s.connect((game_host, game_port))
        size_raw = dest_s.recv(2)
        size = struct.unpack("<H", size_raw)[0]
        checksum = dest_s.recv(4)
        data = dest_s.recv(size - 4)
        msg = NetworkMessage(data)

        challenge_data = GameProtocol.parseChallengeMessage(msg)

        xtea_key = firstmsg_contents['xtea_key']
        firstmsg_contents['timestamp'] = challenge_data['timestamp']
        firstmsg_contents['random_number'] = challenge_data['random_number']
        dest_s.send(GameProtocol.prepareReply(firstmsg_contents,
                                              self.real_tibia))

        conn_obj = Connection(conn, xtea_key)
        received_player = False
        while True:
            # Wait until either the player or the server sent some data.
            has_data, _, _ = select.select([conn, dest_s], [], [])
            if conn in has_data:
                data = bytearray()
                size_raw = conn.recv(2)
                data += size_raw
                if size_raw == bytearray():
                    log("The client disconnected")
                    break
                size = struct.unpack("<H", size_raw)[0]
                data += conn.recv(size+4)
                msg = NetworkMessage(data)
                msg_size = msg.getU16()
                msg.getU32()  # skip the checksum validation
                if msg_size != len(data) - 2:
                    log("Strange packet from client: %s" % repr(data))
                    log("len(data)=%s, msg_size=%s" % (len(data), msg_size))
                    dest_s.send(data)
                    continue
                msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
                msg = NetworkMessage(msg_buf)
                msg.getU16()
                packet_type = msg.getByte()
                if packet_type in GameProtocol.client_packet_types:
                    if self.debug:
                        log("C [%s] %s" % (hex(packet_type),
                            GameProtocol.client_packet_types[packet_type]))
                else:
                    log("Got a packet of type %s from client" % packet_type)
                should_forward = True
                if packet_type == 150:
                    # We got a player "say" request. Read what the player
                    # wanted to say, treat it like a Python expression and send
                    # the result back to the user.
                    msg.skipBytes(1)
                    player_said = msg.getString()
                    for plugin in self.plugins:
                        if 'on_client_say' in dir(plugin):
                            plugin_returned = plugin.on_client_say(conn_obj,
                                                                   player_said)

                            if plugin_returned:
                                should_forward = False

                if should_forward:
                    # Otherwise, just pass the packet to the server.
                    dest_s.send(data)
            if dest_s in has_data:
                # Server sent us some data.
                data = bytearray()
                size_raw = dest_s.recv(2)
                data += size_raw
                if data == bytearray():
                    conn.close()
                    log("The server disconnected")
                    break
                size = struct.unpack("<H", size_raw)[0]
                data += dest_s.recv(size)
                msg = NetworkMessage(data)
                msg_size = msg.getU16()
                msg.getU32()  # skip the checksum validation
                if msg_size != len(data) - 2:
                    log("Strange packet from server: %s" % repr(data))
                    log("len(data)=%s, msg_size=%s" % (len(data), msg_size))
                    conn.send(data)
                    continue
                msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
                msg = NetworkMessage(msg_buf)
                msg.getU16()
                while msg.finished():
                    def getMapDescription():
                        def getTile():
                            got_effect = False
                            for stackPos in range(256):
                                if msg.peekU16() >= 0xff00:
                                    return msg.getU16() & 0xff
                                if not got_effect:
                                    msg.getU16()
                                    got_effect = True
                                    continue
                                assert(stackPos <= 10)
                                thingId = msg.getU16()
                                log(thingId)
                                assert(thingId != 0)
                                if thingId == 96:
                                    log("strange... got a staticText")
                                elif thingId in [97, 98, 99]:
                                    # it's a creature
                                    pass
                                else:
                                    # it's a thing - might require reading 3
                                    # extra bytes
                                    pass

                        def getFloorDescription(x, y, z, w, h, offset, skip):
                            for nx in range(w):
                                for ny in range(h):
                                    getTile()

                        x, y, z = msg.getCoordinates()
                        w = 18
                        h = 14
                        skip = [-1]
                        if z > 7:
                            startz = z - 2
                            endz = min(16 - 1, z + 2)
                            zstep = 1
                        else:
                            startz = 7
                            endz = 0
                            zstep = -1
                        nz = startz
                        while nz != endz + zstep:
                            getFloorDescription(x, y, nz, w, h, z - nz, skip)
                            nz += zstep
                    packet_type = msg.getByte()
                    if packet_type in GameProtocol.server_packet_types:
                        if self.debug:
                            log("S [%s] %s" % (hex(packet_type),
                                GameProtocol.server_packet_types[packet_type])
                                )
                    else:
                        log("Got a packet of type %s from server" %
                            packet_type)
                    # login successful
                    if packet_type == 0x17:
                        msg.getU32()   # player ID
                        msg.getU16()   # beat duration
                        msg.getU32()   # speed A
                        msg.getU32()   # speed B
                        msg.getU32()   # speed C
                        msg.getByte()  # 1 if can report bugs, 0 otherwise

                        # no idea what's this:
                        assert_equal(msg.getByte(), 0xB6)
                        assert_equal(msg.getByte(), 0x7F)
                        assert_equal(msg.getByte(), 0x00)
                        assert_equal(msg.getByte(), 0x0A)
                        assert_equal(msg.getByte(), 0x0F)
                        assert_equal(msg.getByte(), 0x64)
                        getMapDescription()
                        log("TODO")
                        received_player = True
                    # FYI box
                    if packet_type == 0x15:
                        fyi = msg.getString()
                        log("Got a FYI: %s" % fyi)

                log("Sending!")

                conn.send(data)
Example #11
0
def parseReply(msg, xtea_key):
    """Parse the reply from the login server.

    Args:
        msg (NetworkMessage): the network message to be parsed.

    Returns dict or None
    """
    size = msg.getU16()

    # TODO: someday perhaps I'll have enough time to even check the checksums!
    msg.skipBytes(4)

    msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
    msg = NetworkMessage(msg_buf)
    #assert(len(msg.getWithHeader()) == size)
    decrypted_size = msg.getU16()
    #assert(decrypted_size == size - 5)

    packet_type = msg.getByte()
    if packet_type != 0x14:
        # The reply doesn't seem to contain character list.
        return None
    motd = msg.getString()

    assert(msg.getByte() == 0x64)

    num_worlds = msg.getByte()
    worlds = []
    for _ in range(num_worlds):
        world_id = msg.getByte()
        world_name = msg.getString()
        world_hostname = msg.getString()
        world_port = msg.getU16()
        log("Received server address %s:%s" % (world_hostname, world_port))
        msg.skipBytes(1)  # no idea what's that.
        worlds += [create_login_world_entry(name=world_name,
                                            hostname=world_hostname,
                                            port=world_port)]

    num_chars = msg.getByte()
    characters = []
    for _ in range(num_chars):
        world_num = msg.getByte()
        char_world = worlds[world_num]
        char_name = msg.getString()
        characters += [create_login_character_entry(name=char_name,
                                                    world=char_world)]

    return create_login_reply_info(characters, motd, worlds)
Example #12
0
def prepareReply(login_reply):
    """Prepare the reply based on a LoginReply instance.

    Args:
        login_reply (dict): the login_reply structure used to build
            the response.

    Returns NetworkMessage
    """

    ret = NetworkMessage()
    ret.addByte(0x14)
    ret.addString(login_reply['motd'])
    ret.addByte(0x64)

    ret.addByte(len(login_reply['worlds']))
    world_id = 0
    for world in login_reply['worlds']:
        ret.addByte(world_id)
        ret.addString(world['name'])
        ret.addString(world['hostname'])
        ret.addU16(world['port'])
        ret.addByte(0)
        world_id += 1

    ret.addByte(len(login_reply['characters']))
    for char in login_reply['characters']:
        ret.addByte(login_reply['worlds'].index(char['world']))
        ret.addString(char['name'])
    ret.addByte(0x00)
    ret.addByte(0x00)
    return ret
Example #13
0
def parseReply(msg, xtea_key):
    """Parse the reply from the login server.

    Args:
        msg (NetworkMessage): the network message to be parsed.

    Returns dict or None
    """
    size = msg.getU16()

    # TODO: someday perhaps I'll have enough time to even check the checksums!
    msg.skipBytes(4)

    msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
    msg = NetworkMessage(msg_buf)
    #assert(len(msg.getWithHeader()) == size)
    decrypted_size = msg.getU16()
    #assert(decrypted_size == size - 5)

    packet_type = msg.getByte()
    if packet_type != 0x14:
        # The reply doesn't seem to contain character list.
        return None
    motd = msg.getString()

    assert (msg.getByte() == 0x64)

    num_worlds = msg.getByte()
    worlds = []
    for _ in range(num_worlds):
        world_id = msg.getByte()
        world_name = msg.getString()
        world_hostname = msg.getString()
        world_port = msg.getU16()
        log("Received server address %s:%s" % (world_hostname, world_port))
        msg.skipBytes(1)  # no idea what's that.
        worlds += [
            create_login_world_entry(name=world_name,
                                     hostname=world_hostname,
                                     port=world_port)
        ]

    num_chars = msg.getByte()
    characters = []
    for _ in range(num_chars):
        world_num = msg.getByte()
        char_world = worlds[world_num]
        char_name = msg.getString()
        characters += [
            create_login_character_entry(name=char_name, world=char_world)
        ]

    return create_login_reply_info(characters, motd, worlds)
Example #14
0
def prepareReply(login_reply):
    """Prepare the reply based on a LoginReply instance.

    Args:
        login_reply (dict): the login_reply structure used to build
            the response.

    Returns NetworkMessage
    """

    ret = NetworkMessage()
    ret.addByte(0x14)
    ret.addString(login_reply['motd'])
    ret.addByte(0x64)

    ret.addByte(len(login_reply['worlds']))
    world_id = 0
    for world in login_reply['worlds']:
        ret.addByte(world_id)
        ret.addString(world['name'])
        ret.addString(world['hostname'])
        ret.addU16(world['port'])
        ret.addByte(0)
        world_id += 1

    ret.addByte(len(login_reply['characters']))
    for char in login_reply['characters']:
        ret.addByte(login_reply['worlds'].index(char['world']))
        ret.addString(char['name'])
    ret.addByte(0x00)
    ret.addByte(0x00)
    return ret