def parse(self, id): if id == 0x00: if self.state == 0: data = self.read("varint:version|string:address|ushort:port|varint:state") self.version = data["version"] self.packet.version = self.version if not self.wrapper.server.protocolVersion == self.version and data["state"] == 2: self.disconnect("You're not running the same Minecraft version as the server!") return if not self.wrapper.server.state == 2: self.disconnect("Server has not finished booting. Please try connecting again in a few seconds") return if data["state"] in (1, 2): self.state = data["state"] else: self.disconnect("Invalid state '%d'" % data["state"]) return False elif self.state == 1: sample = [] for i in self.wrapper.server.players: player = self.wrapper.server.players[i] sample.append({"name": player.username, "id": str(player.uuid)}) if len(sample) > 5: break MOTD = {"description": json.loads(self.wrapper.server.processColorCodes(self.wrapper.server.motd.replace("\\", ""))), "players": {"max": self.wrapper.server.maxPlayers, "online": len(self.wrapper.server.players), "sample": sample}, "version": {"name": self.wrapper.server.version, "protocol": self.wrapper.server.protocolVersion} } if os.path.exists("server-icon.png"): f = open("server-icon.png", "r") serverIcon = "data:image/png;base64," + f.read().encode("base64") f.close() MOTD["favicon"] = serverIcon self.send(0x00, "string", (json.dumps(MOTD),)) self.state = 5 return False elif self.state == 2: data = self.read("string:username") self.username = data["username"] if self.config["Proxy"]["online-mode"]: self.state = 4 self.verifyToken = encryption.generate_challenge_token() self.serverID = encryption.generate_server_id() if self.wrapper.server.protocolVersion < 6: # 1.7.x versions self.send(0x01, "string|bytearray_short|bytearray_short", (self.serverID, self.publicKey, self.verifyToken)) else: self.send(0x01, "string|bytearray|bytearray", (self.serverID, self.publicKey, self.verifyToken)) else: self.connect() self.uuid = uuid.uuid3(uuid.NAMESPACE_OID, "OfflinePlayer: %s" % self.username) self.serverUUID = self.UUIDFromName("OfflinePlayer:" + self.username) self.send(0x02, "string|string", (str(self.uuid), self.username)) self.state = 3 self.log.info("%s logged in (IP: %s)" % (self.username, self.addr[0])) return False elif self.state == 3: return False if id == 0x01: if self.state == 3: # chat packet if not self.isLocal == True: return True data = self.read("string:message") if data is None: return False try: if not self.wrapper.callEvent("player.rawMessage", {"player": self.getPlayerObject(), "message": data["message"]}): return False if data["message"][0] == "/": def args(i): try: return data["message"].split(" ")[i] except: return "" def argsAfter(i): try: return data["message"].split(" ")[i:] except: return "" return self.wrapper.callEvent("player.runCommand", {"player": self.getPlayerObject(), "command": args(0)[1:], "args": argsAfter(1)}) except: print traceback.format_exc() elif self.state == 4: # Encryption Response Packet if self.wrapper.server.protocolVersion < 6: data = self.read("bytearray_short:shared_secret|bytearray_short:verify_token") else: data = self.read("bytearray:shared_secret|bytearray:verify_token") sharedSecret = encryption.decrypt_shared_secret(data["shared_secret"], self.privateKey) verifyToken = encryption.decrypt_shared_secret(data["verify_token"], self.privateKey) h = hashlib.sha1() h.update(self.serverID) h.update(sharedSecret) h.update(self.publicKey) serverId = self.packet.hexdigest(h) r = requests.get("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s" % (self.username, serverId)) # print "SessionServer response: %s" % r.text self.packet.sendCipher = encryption.AES128CFB8(sharedSecret) self.packet.recvCipher = encryption.AES128CFB8(sharedSecret) if not verifyToken == self.verifyToken: self.disconnect("Verify tokens are not the same") return False try: data = r.json() self.uuid = data["id"] self.uuid = "%s-%s-%s-%s-%s" % (self.uuid[:8], self.uuid[8:12], self.uuid[12:16], self.uuid[16:20], self.uuid[20:]) self.uuid = uuid.UUID(self.uuid) if not data["name"] == self.username: self.disconnect("Client's username did not match Mojang's record") return False for property in data["properties"]: if property["name"] == "textures": self.skinBlob = property["value"] self.wrapper.proxy.skins[str(self.uuid)] = self.skinBlob self.properties = data["properties"] except: self.disconnect("Session Server Error") return False if self.proxy.lookupUUID(self.uuid): self.username = self.proxy.lookupUUID(self.uuid)["name"] self.serverUUID = self.UUIDFromName("OfflinePlayer:" + self.username) if self.version > 26: self.packet.setCompression(256) # Ban code should go here if not self.wrapper.callEvent("player.preLogin", {"player": self.username, "online_uuid": self.uuid, "offline_uuid": self.serverUUID, "ip": self.addr[0]}): self.disconnect("Login denied.") return False self.send(0x02, "string|string", (str(self.uuid), self.username)) self.state = 3 self.connect() self.log.info("%s logged in (UUID: %s | IP: %s)" % (self.username, self.uuid, self.addr[0])) self.proxy.setUUID(self.uuid, self.username) return False elif self.state == 5: # ping packet during status request keepAlive = self.read("long:keepAlive")["keepAlive"] self.send(0x01, "long", (keepAlive,)) if id == 0x04: data = self.read("double:x|double:y|double:z|bool:on_ground") self.position = (data["x"], data["y"], data["z"]) if id == 0x06: data = self.read("double:x|double:y|double:z|float:yaw|float:pitch|bool:on_ground") #objection = self.wrapper.callEvent("player.move", {"player": self.username, "xyz": (data["x"], data["y"], data["z"]), "on_ground": data["on_ground"]}) self.position = (data["x"], data["y"], data["z"]) if self.server.state is not 3: return False if id == 0x07: # Player Block Dig if not self.isLocal == True: return True if self.version < 6: data = self.read("byte:status|int:x|ubyte:y|int:z|byte:face") position = (data["x"], data["y"], data["z"]) else: data = self.read("byte:status|position:position|byte:face") position = data["position"] if data is None: return False if data["status"] == 2: if not self.wrapper.callEvent("player.dig", {"player": self.getPlayerObject(), "position": position, "action": "end_break", "face": data["face"]}): return False if data["status"] == 0: if not self.gamemode == 1: if not self.wrapper.callEvent("player.dig", {"player": self.getPlayerObject(), "position": position, "action": "begin_break", "face": data["face"]}): return False else: if not self.wrapper.callEvent("player.dig", {"player": self.getPlayerObject(), "position": position, "action": "end_break", "face": data["face"]}): return False if self.server.state is not 3: return False if id == 0x08: # Player Block Placement if not self.isLocal == True: return True if self.version < 6: data = self.read("int:x|ubyte:y|int:z|byte:face|slot:item") position = (data["x"], data["y"], data["z"]) else: data = self.read("position:position|byte:face|slot:item") position = data["position"] position = None if self.version > 6: position = data["position"] if not position == None: face = data["face"] if not self.wrapper.callEvent("player.interact", {"player": self.getPlayerObject(), "position": position}): return False if face == 0: # Compensate for block placement coordinates position = (position[0], position[1] - 1, position[2]) elif face == 1: position = (position[0], position[1] + 1, position[2]) elif face == 2: position = (position[0], position[1], position[2] - 1) elif face == 3: position = (position[0], position[1], position[2] + 1) elif face == 4: position = (position[0] - 1, position[1], position[2]) elif face == 5: position = (position[0] + 1, position[1], position[2]) if not self.wrapper.callEvent("player.place", {"player": self.getPlayerObject(), "position": position, "item": data["item"]}): return False if self.server.state is not 3: return False if id == 0x09: # Held Item Change slot = self.read("short:short")["short"] if self.slot > -1 and self.slot < 9: self.slot = slot else: return False return True
def _handle_client(self, connection): with closing(connection[0]) as sock: clt_spec, srv_spec = protocol[0] print t.bold("\nConnected to %s:%s" % connection[1]) print t.bold_cyan("\nExpecting Server Ping (0xfe) " + "or Handshake (0x02) packet") packet = parse_packet(sock, clt_spec) if packet['msgtype'] == 0xfe: send_packet(sock, srv_spec, {'msgtype': 0xff, 'reason': 'mc3p debugger'}) return elif packet['msgtype'] != 0x02: raise UnexpectedPacketException(packet['msgtype']) if packet['proto_version'] < 38: print t.bold_red("Error:"), print "Unsupported protocol version" return username = packet['username'] clt_spec, srv_spec = protocol[packet['proto_version']] print t.bold("\nGenerating RSA key pair") key = encryption.generate_key_pair() challenge = encryption.generate_challenge_token() server_id = encryption.generate_server_id() packet = {'msgtype': 0xfd, 'server_id': server_id, 'public_key': encryption.encode_public_key(key), 'challenge_token': challenge} send_packet(sock, srv_spec, packet) packet = parse_packet(sock, clt_spec, 0xfc) try: decrypted_token = encryption.decrypt_shared_secret( packet['challenge_token'], key ) except: decrypted_token = None if decrypted_token is None: try: decrypted_token = key.decrypt(packet['challenge_token']) except: pass if decrypted_token == challenge: print t.bold_red("\nError:"), print ("The challenge token was not padded " + "correctly. See ftp://ftp.rsasecurity.com/pub/" + "pkcs/pkcs-1/pkcs-1v2-1.pdf section 7.2.1 if " + "your library does not support PKCS#1 padding.") else: print t.bold_red("\nError:"), print "The challenge token is not encrypted correctly.\n" print PacketFormatter.bytes(decrypted_token, "Decrypted bytes: ", t.bold) return elif decrypted_token != challenge: print t.bold_red("\nError:"), print "Received challenge token does not", print "match the expected value.\n" print PacketFormatter.bytes(decrypted_token, "Received bytes: ", t.bold) print print PacketFormatter.bytes(challenge, "Expected bytes: ", t.bold) return secret = encryption.decrypt_shared_secret(packet['shared_secret'], key) if secret is None: print t.bold_red("\nError:"), print ("The shared secret was not padded" + "correctly. See ftp://ftp.rsasecurity.com/pub/" + "pkcs/pkcs-1/pkcs-1v2-1.pdf section 7.2.1 if " + "your library does not support PKCS#1 padding.") return print PacketFormatter.bytes(secret, "Shared secret: ", t.bold) if len(secret) != 16: print t.bold_red("\nError:"), print "The shared secret must be 16 bytes long", print "(received length is %s)" % len(secret) return print t.bold_cyan("\nAuthentication") print PacketFormatter.bytes(server_id, "Server ID: ", t.bold) print PacketFormatter.bytes(secret, "Shared secret: ", t.bold) print PacketFormatter.bytes(encryption.encode_public_key(key), "Public key: ", t.bold) print t.bold("Login hash: "), print Authenticator.login_hash(server_id, secret, key) if Authenticator.check_player(username, server_id, secret, key): print t.bold_green("Success:"), "You are authenticated" else: print t.bold_yellow("Warning:"), "You are not authenticated" send_packet(sock, srv_spec, {'msgtype': 0xfc, 'challenge_token': '', 'shared_secret': ''}) print t.bold("\nStarting AES encryption") clt_cipher = encryption.AES128CFB8(secret) srv_cipher = encryption.AES128CFB8(secret) backup_cipher = encryption.AES128CFB8(secret) parse_packet(sock, clt_spec, 0xcd, clt_cipher, backup_cipher) send_packet(sock, srv_spec, {'msgtype': 0x01, 'eid': 1337, 'level_type': 'flat', 'server_mode': 0, 'dimension': 0, 'difficulty': 2, 'unused': 0, 'max_players': 20}, srv_cipher) if self.send_chunks: while True: print packet = parse_packet(sock, clt_spec, cipher=clt_cipher) if packet['msgtype'] == 0x0d: break x, y, z = 5, 9, 5 send_packet(sock, srv_spec, {'msgtype': 0x06, 'x': x, 'y': y, 'z': z}, srv_cipher) send_packet(sock, srv_spec, {'msgtype': 0xca, 'abilities': 0b0100, 'walking_speed': 25, 'flying_speed': 12}, srv_cipher) send_packet(sock, srv_spec, {'msgtype': 0x04, 'time': 0}, srv_cipher) send_packet(sock, srv_spec, multi_chunk_packet(), srv_cipher) send_packet(sock, srv_spec, {'msgtype': 0x0d, 'x': x, 'y': y, 'stance': y + 1.5, 'z': z, 'yaw': 0, 'pitch': 0, 'on_ground': False}, srv_cipher) buffer = StringSocket() send_packet(buffer, srv_spec, {'msgtype': 0x03, 'chat_msg': 'First message'}, srv_cipher) send_packet(buffer, srv_spec, {'msgtype': 0x03, 'chat_msg': 'Second message'}, srv_cipher) sock.sendall(buffer.data) if self.stay_connected: while True: packet = parse_packet(sock, clt_spec, cipher=clt_cipher, title=True) if packet['msgtype'] == 0xff: break elif packet['msgtype'] == 0x00: send_packet(buffer, srv_spec, {'msgtype': 0x00, 'id': 0}, srv_cipher) break else: send_packet(sock, srv_spec, {'msgtype': 0xff, 'reason': "Successfully logged in"}, srv_cipher)
def handle_read(self): """Read all available bytes, and process as many packets as possible. """ t = time() if self.last_report + 5 < t and self.stream.tot_bytes > 0: self.last_report = t logger.debug( "%s: total/wasted bytes is %d/%d (%f wasted)" % (self.side, self.stream.tot_bytes, self.stream.wasted_bytes, 100 * float(self.stream.wasted_bytes) / self.stream.tot_bytes)) self.stream.append(self.recv(4092)) if self.out_of_sync: data = self.stream.read(len(self.stream)) self.stream.packet_finished() if self.other_side: self.other_side.send(data) return try: packet = parse_packet(self.stream, self.msg_spec, self.side) while packet != None: rebuild = False if packet['msgtype'] == 0x02 and self.side == 'client': # Determine which protocol message definitions to use. proto_version = packet['proto_version'] logger.info('Client requests protocol version %d' % proto_version) if not proto_version in messages.protocol: logger.error("Unsupported protocol version %d" % proto_version) self.handle_close() return self.username = packet['username'] self.msg_spec, self.other_side.msg_spec = messages.protocol[ proto_version] self.cipher = encryption.encryption_for_version( proto_version) self.other_side.cipher = self.cipher elif packet['msgtype'] == 0xfd: self.rsa_key = encryption.decode_public_key( packet['public_key']) self.encoded_rsa_key = packet['public_key'] packet['public_key'] = encryption.encode_public_key( self.other_side.rsa_key) if 'challenge_token' in packet: self.challenge_token = packet['challenge_token'] self.other_side.challenge_token = self.challenge_token self.other_side.server_id = packet['server_id'] if check_auth: packet['server_id'] = encryption.generate_server_id() else: packet['server_id'] = "-" self.server_id = packet['server_id'] rebuild = True elif packet['msgtype'] == 0xfc and self.side == 'client': self.shared_secret = encryption.decrypt_shared_secret( packet['shared_secret'], self.rsa_key) if (len(self.shared_secret) > 16 and self.cipher == encryption.RC4): logger.error("Unsupported protocol version") self.handle_close() return packet['shared_secret'] = encryption.encrypt_shared_secret( self.other_side.shared_secret, self.other_side.rsa_key) if 'challenge_token' in packet: challenge_token = encryption.decrypt_shared_secret( packet['challenge_token'], self.rsa_key) if challenge_token != self.challenge_token: self.kick("Invalid client reply") return packet[ 'challenge_token'] = encryption.encrypt_shared_secret( self.other_side.challenge_token, self.other_side.rsa_key) if auth: logger.info("Authenticating on server") auth.join_server(self.server_id, self.other_side.shared_secret, self.other_side.rsa_key) if check_auth: logger.info("Checking authenticity") if not Authenticator.check_player( self.username, self.other_side.server_id, self.shared_secret, self.rsa_key): self.kick("Unable to verify username") return rebuild = True elif packet['msgtype'] == 0xfc and self.side == 'server': logger.debug("Starting encryption") self.start_cipher() forward = True if self.plugin_mgr: forwarding = self.plugin_mgr.filter(packet, self.side) if forwarding and packet.modified: rebuild = True if rebuild: packet['raw_bytes'] = self.msg_spec[ packet['msgtype']].emit(packet) if forwarding and self.other_side is not None: self.other_side.send(packet['raw_bytes']) if packet['msgtype'] == 0xfc and self.side == 'server': self.other_side.start_cipher() # Since we know we're at a message boundary, we can inject # any messages in the queue. msgbytes = self.plugin_mgr.next_injected_msg_from(self.side) while self.other_side and msgbytes is not None: self.other_side.send(msgbytes) msgbytes = self.plugin_mgr.next_injected_msg_from( self.side) # Attempt to parse the next packet. packet = parse_packet(self.stream, self.msg_spec, self.side) except PartialPacketException: pass # Not all data for the current packet is available. except Exception: logger.error( "MinecraftProxy for %s caught exception, out of sync" % self.side) logger.error(traceback.format_exc()) logger.debug("Current stream buffer: %s" % repr(self.stream.buf)) self.out_of_sync = True self.stream.reset()
def handle_read(self): """Read all available bytes, and process as many packets as possible. """ t = time() if self.last_report + 5 < t and self.stream.tot_bytes > 0: self.last_report = t logger.debug("%s: total/wasted bytes is %d/%d (%f wasted)" % ( self.side, self.stream.tot_bytes, self.stream.wasted_bytes, 100 * float(self.stream.wasted_bytes) / self.stream.tot_bytes)) self.stream.append(self.recv(4092)) if self.out_of_sync: data = self.stream.read(len(self.stream)) self.stream.packet_finished() if self.other_side: self.other_side.send(data) return try: packet = parse_packet(self.stream, self.msg_spec, self.side) while packet != None: rebuild = False if packet['msgtype'] == 0x02 and self.side == 'client': # Determine which protocol message definitions to use. proto_version = packet['proto_version'] logger.info('Client requests protocol version %d' % proto_version) if not proto_version in messages.protocol: logger.error("Unsupported protocol version %d" % proto_version) self.handle_close() return self.username = packet['username'] self.msg_spec, self.other_side.msg_spec = messages.protocol[proto_version] self.cipher = encryption.encryption_for_version(proto_version) self.other_side.cipher = self.cipher elif packet['msgtype'] == 0xfd: self.rsa_key = encryption.decode_public_key( packet['public_key'] ) self.encoded_rsa_key = packet['public_key'] packet['public_key'] = encryption.encode_public_key( self.other_side.rsa_key ) if 'challenge_token' in packet: self.challenge_token = packet['challenge_token'] self.other_side.challenge_token = self.challenge_token self.other_side.server_id = packet['server_id'] if check_auth: packet['server_id'] = encryption.generate_server_id() else: packet['server_id'] = "-" self.server_id = packet['server_id'] rebuild = True elif packet['msgtype'] == 0xfc and self.side == 'client': self.shared_secret = encryption.decrypt_shared_secret( packet['shared_secret'], self.rsa_key ) if (len(self.shared_secret) > 16 and self.cipher == encryption.RC4): logger.error("Unsupported protocol version") self.handle_close() return packet['shared_secret'] = encryption.encrypt_shared_secret( self.other_side.shared_secret, self.other_side.rsa_key ) if 'challenge_token' in packet: challenge_token = encryption.decrypt_shared_secret( packet['challenge_token'], self.rsa_key ) if challenge_token != self.challenge_token: self.kick("Invalid client reply") return packet['challenge_token'] = encryption.encrypt_shared_secret( self.other_side.challenge_token, self.other_side.rsa_key ) if auth: logger.info("Authenticating on server") auth.join_server(self.server_id, self.other_side.shared_secret, self.other_side.rsa_key) if check_auth: logger.info("Checking authenticity") if not Authenticator.check_player( self.username, self.other_side.server_id, self.shared_secret, self.rsa_key): self.kick("Unable to verify username") return rebuild = True elif packet['msgtype'] == 0xfc and self.side == 'server': logger.debug("Starting encryption") self.start_cipher() forward = True if self.plugin_mgr: forwarding = self.plugin_mgr.filter(packet, self.side) if forwarding and packet.modified: rebuild = True if rebuild: packet['raw_bytes'] = self.msg_spec[packet['msgtype']].emit(packet) if forwarding and self.other_side is not None: self.other_side.send(packet['raw_bytes']) if packet['msgtype'] == 0xfc and self.side == 'server': self.other_side.start_cipher() # Since we know we're at a message boundary, we can inject # any messages in the queue. msgbytes = self.plugin_mgr.next_injected_msg_from(self.side) while self.other_side and msgbytes is not None: self.other_side.send(msgbytes) msgbytes = self.plugin_mgr.next_injected_msg_from(self.side) # Attempt to parse the next packet. packet = parse_packet(self.stream,self.msg_spec, self.side) except PartialPacketException: pass # Not all data for the current packet is available. except Exception: logger.error("MinecraftProxy for %s caught exception, out of sync" % self.side) logger.error(traceback.format_exc()) logger.debug("Current stream buffer: %s" % repr(self.stream.buf)) self.out_of_sync = True self.stream.reset()