Question Pulling Splatoon2 related data from the Nintendo Switch Online app

Discussion in 'Switch - Hacking & Homebrew' started by Dann_, Jul 23, 2017.

  1. Dann_
    OP

    Dann_ Advanced Member

    Newcomer
    61
    33
    May 3, 2016
    Afghanistan
    So I've been trying to programatically aquire data from the Splatoon2 service on the Nintendo Switch Online app for a site like splatoon.ink I've succesfully authenticated as a user with my program but I can't seem to make any api calls after that. I'm just gonna share what I've learned so people can maybe use it or help me out documenting the whole service.

    One thing that may be at fault is my gathered packets, They're from the app "Packet Capture" on the Play store, it uses a VPN and custom SSL Certificate and some things in the app stop working because of it ("Opening the splatoon 2 service results in an empty page :/"), gonna retry this with my phone rooted and an actually decent sniffer in a bit. Will update the thread if something changes because of it.

    I've successfully sniffed 3 HTTPS POST requests the app sends to some service, one logs the user in with a previously aquired token and I've been able to replicate the request in Python3, that requests returns a access_token and an id_token, both which expire after 900 seconds. The second requests is to api.account.nintendo.com/2.0.0/users/me which gets some user related data like mii picture and email address. It requires you to set the Authorization header to "Bearer {access_token}", which I did, however it throws me back an html file with status code 405: Method not allowed (Maybe someone can try this just to see if it isn't me being sleep deprived that is the issue). After this the app is logged in and has the users info. When the Splatoon2 service is clicked another request is send. This request is magical to me, it sends a whole bunch of info to the server including device manufacturer, device name, android version etc etc and some ID's which the app doesn't retrieve at launch (hard coded maybe?), the request is encoded with gzip, all the server returns for me is a json encoded messages saying something along the lines of "1 item receive, 1 item accepted".

    If someone wants the snippet of code I made then do tell, I'll upload it to pastebin or something :P
     
  2. rctgamer3

    rctgamer3 GBAtemp Fan

    Member
    309
    95
    May 5, 2008
    Netherlands
    Since the app is just a fancy website viewer (WebView), i dug a bit into the workings of the app by decrypting the TLS/HTTPS connection. I found at least one JSON that contains schedule data. My token only seemed to expire after a day or two, strange. The hardest part is probably getting a valid token on a computer. Otherwise just grab a token from the app and replay it in your browser.
    Edit 1: Just need to steal the iksm_session token from whatever program you use to proxy the requests, be it Fiddler+HTTPS decrypting or whatever program you use. Thank http://s2terminal.hatenablog.com/entry/2017/07/23/203831 for that.


    [​IMG] Meow.

    Edit 2: stages are at /api/timeline:

    Code:
      "schedule":{
          "importance":0.8,
          "schedules":{
             "regular":[
                {
                   "game_mode":{
                      "key":"regular",
                      "name":"Regular Battle"
                   },
                   "stage_a":{
                      "name":"Musselforge Fitness",
                      "image":"/images/stage/83acec875a5bb19418d7b87d5df4ba1e38ceac66.png",
                      "id":"1"
                   },
                   "end_time":1500840000,
                   "id":4780952683920125481,
                   "stage_b":{
                      "id":"2",
                      "name":"Starfish Mainstage",
                      "image":"/images/stage/187987856bf575c4155d021cb511034931d06d24.png"
                   },
                   "rule":{
                      "name":"Turf War",
                      "key":"turf_war",
                      "multiline_name":"Turf\nWar"
                   },
                   "start_time":1500832800
                }
             ],
             "league":[
                {
                   "stage_a":{
                      "id":"4",
                      "name":"Inkblot Art Academy",
                      "image":"/images/stage/5c030a505ee57c889d3e5268a4b10c1f1f37880a.png"
                   },
                   "game_mode":{
                      "key":"league",
                      "name":"League Battle"
                   },
                   "start_time":1500832800,
                   "rule":{
                      "name":"Splat Zones",
                      "multiline_name":"Splat\nZones",
                      "key":"splat_zones"
                   },
                   "end_time":1500840000,
                   "id":4780952683920125481,
                   "stage_b":{
                      "image":"/images/stage/0907fc7dc325836a94d385919fe01dc13848612a.png",
                      "name":"Port Mackerel",
                      "id":"7"
                   }
                }
             ],
             "gachi":[
                {
                   "game_mode":{
                      "key":"gachi",
                      "name":"Ranked Battle"
                   },
                   "stage_a":{
                      "id":"3",
                      "name":"Sturgeon Shipyard",
                      "image":"/images/stage/bc794e337900afd763f8a88359f83df5679ddf12.png"
                   },
                   "stage_b":{
                      "name":"The Reef",
                      "image":"/images/stage/98baf21c0366ce6e03299e2326fe6d27a7582dce.png",
                      "id":"0"
                   },
                   "end_time":1500840000,
                   "id":4780952683920125481,
                   "start_time":1500832800,
                   "rule":{
                      "name":"Tower Control",
                      "multiline_name":"Tower\nControl",
                      "key":"tower_control"
                   }
                }
             ]
          }
       },
     
    Last edited by rctgamer3, Jul 23, 2017
  3. Dann_
    OP

    Dann_ Advanced Member

    Newcomer
    61
    33
    May 3, 2016
    Afghanistan
    Thanks for finding the endpoint :D, So I figured out why my SSLStrip didn't work, it seems that Android N will still allow you to install user signed certificates, but it will only use them if the app specifically specifies if it's okay with it (which no-one does)... Grabbing my tablet rn since it's on M I believe.. Basically the only packets I've been able to sniff were api auth, crash reports, data collection and google-analytics lol
     
  4. Dann_
    OP

    Dann_ Advanced Member

    Newcomer
    61
    33
    May 3, 2016
    Afghanistan
    Thanks a ton, got the service working, right now it tweets the current maps to @splatoon2info, Gonna set up an API and quick little site since I've gotten all the data already. This is it's first successful tweet :P Once I've got some more free time I'm gonna see if I can get Salmon Run and Splatfests in there too.
     
    Last edited by Dann_, Jul 24, 2017
    woofmute, Marxally and d4mation like this.
  5. Souloibur

    Souloibur Member

    Newcomer
    33
    32
    Nov 8, 2016
    Hello, I got the Json too but the cookie only lasts 24h. ¿How do you bypass this?

    Thank you.
     
  6. Dann_
    OP

    Dann_ Advanced Member

    Newcomer
    61
    33
    May 3, 2016
    Afghanistan
    Currently not bypassing in but I'm quite sure it gets the cookie from a previous request to the server. I'll check it out tonight if I have time but I'd guess you just have to send your session token and client id to the api at some point and you get back the cookie :P
     
  7. Souloibur

    Souloibur Member

    Newcomer
    33
    32
    Nov 8, 2016
    I'm trying to find that request. If you find something and want to tell me, i'll be grateful.
     
  8. crankcube

    crankcube Newbie

    Newcomer
    3
    2
    Jul 24, 2017
    United Kingdom
    Thanks for posting this, it gave me enough of a starting point to figure out the rest.

    I followed the article rctgamer3 posted to sniff an android phone's Nitendo Switch App and discovered that 3 initial inputs are needed


    1. client_id - some hash value, I suspect its the hash of the device or the App.
    2. resource_id - a large numerical number, i suspect this is the identifier for the splatoon2 content with in the app
    3. initial_token_id - a large of data, I think this must be baked into the app
    using the above which you can fish out from your proxy sniffer you can generate valid tokens and ultimately make the splatoon site give you a the iksm_session cookie.


    Code:
    #!/usr/bin/env python3
    import logging
    import requests
    import json
    import sys
    import http.client as http_client
    http_client.HTTPConnection.debuglevel = 1
    
    logging.basicConfig()
    logging.getLogger().setLevel(logging.DEBUG)
    requests_log = logging.getLogger("requests.packages.urllib3")
    requests_log.setLevel(logging.DEBUG)
    requests_log.propagate = True
    
    def main():
    
        client_id = "xxxxxxxxxxxx"
        resource_id = 123456789
        init_session_token = "eyJhbGci.....Wq2Q"
    
        session = requests.Session()
        response = session.post('https://accounts.nintendo.com/connect/1.0.0/api/token',
                                headers={'Accept': 'application/json'},
                                json={ "client_id": client_id,
                                      "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer-session-token",
                                      "session_token": init_session_token})
        api_tokens = response.json()
        #print(json.dumps(response.json(),indent=4))
      
        response = session.post('https://api-lp1.znc.srv.nintendo.net/v1/Account/GetToken',
                                headers={'Accept': 'application/json', 
                                         'Authorization': "Bearer " + api_tokens["access_token"]},
                                json={"parameter": {
                                        "language": 'null',
                                        "naBirthday": 'null',
                                        "naCountry": 'null',
                                        "naIdToken": api_tokens["id_token"] }
                                        })
        tokens = response.json()["result"]
        #print(json.dumps(response.json(),indent=4))
      
        response = session.post('https://api-lp1.znc.srv.nintendo.net/v1/Game/GetWebServiceToken',
                            headers={'Accept': 'application/json',
                                     'Authorization': "Bearer "+tokens["webApiServerCredential"]["accessToken"]},
                            json={"parameter": {"id": resource_id}} )
    
        res_json = response.json()
        if res_json["status"] != 0:
            logging.error(json.dumps(res_json,indent=4))
            raise RuntimeError("initial auth failed")
        access_token = res_json["result"]["accessToken"]
    
        # get the cookie setup
        response = session.get("https://app.splatoon2.nintendo.net/?lang=ja-JP",
                            headers={'Accept': 'application/json',
                                     'X-gamewebtoken': access_token})
    
        # now we can rock'n'roll
        response = session.get('https://app.splatoon2.nintendo.net/api/schedules', headers={'accept': 'application/json'})
        print(json.dumps(response.json(), indent=4))
    
    
     
    Marxally and Dann_ like this.
  9. Dann_
    OP

    Dann_ Advanced Member

    Newcomer
    61
    33
    May 3, 2016
    Afghanistan
    Well done! I'm pretty sure the session token is made when you first log in to the app since it's also used to get information of the user account by the app.
     
    Last edited by Dann_, Jul 24, 2017
  10. crankcube

    crankcube Newbie

    Newcomer
    3
    2
    Jul 24, 2017
    United Kingdom
    When you login to Nintendo via the switch app it asks you which account to use, I think i t must be registering the user with the app and getting a initial session setup then.

    The Nintendo account login part has some protection against MitMProxies and rejects login attempts via the proxy so I've not been able to figure out how to go from a username/password -> initial token. if I can figure that bit out, it should be possible to remove all the hardcoded values.
     
    Last edited by crankcube, Jul 25, 2017
  11. crankcube

    crankcube Newbie

    Newcomer
    3
    2
    Jul 24, 2017
    United Kingdom
    Dann_, would you be able to help me go from a username / password -> some session token ?
     
  12. airball12

    airball12 Newbie

    Newcomer
    3
    2
    Dec 7, 2016
    United States
    s
     
    Last edited by airball12, Jul 26, 2017
  13. rctgamer3

    rctgamer3 GBAtemp Fan

    Member
    309
    95
    May 5, 2008
    Netherlands
    My root cert setup also works during the init login attempt. I'll try to reinstall the app and check if i can find something out.
     
  14. TR0P127

    TR0P127 Newbie

    Newcomer
    1
    0
    Jul 27, 2017
    United States
    Anyone have more information on this topic?
     
  15. XenonNSMB

    XenonNSMB Newbie

    Newcomer
    2
    1
    Jul 27, 2017
    United States
    If you sniff the contents of a request to /api/nickname_and_icon you can get the URL to your Nintendo Switch profile photo. Might be useful for people who want to save their Switch picture to their computer. And unlike everything else, the URL to the icon doesn't require a token to access from a browser. For example, here's the URL to my icon: https://cdn-image-e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com/1/92e977edee90df24


    Here are some other URLs:
    api/festivals/active - presumably lets you check if a Splatfest is going on
    https://api.accounts.nintendo.com/2.0.0/users/me - Lets you get your Mii and account data
    https://api-lp1.znc.srv.nintendo.net/v1/Game/ListWebServices - Used by the app to get a list of webpages to show under "Game-Specific Services". Example JSON for Splatoon 2:
    Code:
                "imageUri": "https://cdn.znc.srv.nintendo.net/gameWebServices/splatoon2/images/usEn/banner.png",
    
                "name": "Splatoon 2",
    
                "uri": "https://app.splatoon2.nintendo.net/",
    
                "whiteList": [
    
                    "app.splatoon2.nintendo.net"
    
                ]
    Apparently each game has its own whitelist array of allowed sites.

    [​IMG]
    Man, the Switch Online app is my favorite web browser.
     
    Last edited by XenonNSMB, Jul 27, 2017
  16. JerryX

    JerryX Member

    Newcomer
    36
    12
    Dec 13, 2008
    United States
    Does anyone know the uri extension to the shop? As in /api/*something* for it.
     
  17. rctgamer3

    rctgamer3 GBAtemp Fan

    Member
    309
    95
    May 5, 2008
    Netherlands
    The Splatoon gear shop, with Annie?
     
  18. JerryX

    JerryX Member

    Newcomer
    36
    12
    Dec 13, 2008
    United States
    Yeah, where can I pull up the JSON for what's being sold?
     
  19. daniel65536

    daniel65536 Newbie

    Newcomer
    1
    0
    Aug 14, 2017
    China
    https://github.com/blackgear/NSOnline_Bot

    I'm current work on this, auto pull the online shop information and order it.

    You can login in with nintendo username and password, and get the that client_id initial_token_id. My code has figured them out totally.
     
  20. eliboa

    eliboa Member

    Newcomer
    49
    56
    Jan 13, 2016
    France
    Hi,
    Since the Nintendo Switch Online app had been updated, I'm having trouble accessing POST https://api-lp1.znc.srv.nintendo.net/v1/Account/Login

    It looks like a new parameter "f" was added to the request body. Here is the HTTPS request sent by the app
    Code:
    POST https://api-lp1.znc.srv.nintendo.net/v1/Account/Login HTTP/1.1
    X-ProductVersion: 1.1.0
    X-Platform: Android
    User-Agent: com.nintendo.znca/1.1.0 (Android/7.0)
    Accept: application/json
    Authorization: Bearer
    Content-Type: application/json; charset=utf-8
    Content-Length: 977
    Host: api-lp1.znc.srv.nintendo.net
    Connection: Keep-Alive
    Accept-Encoding: gzip
    
    {
       "parameter":{
          "naIdToken":"eyJraWQiOiIwMD[...]jDE9DfWUpg",
          "naCountry":null,
          "naBirthday":null,
          "language":null,
          "f":"5c5ab0a441658711115122ab753e5fc3c4291c6de8a0dc91dc6ca7a82a83412a"
       }
    }
    
    I can't figure out how this new token is generated :nayps3:
    Maybe by the app itself since neither "/connect/1.0.0/api/session_token" nor "/connect/1.0.0/api/token" return it.

    Any clue ?

    Btw, note that the "X-ProductVersion" header parameter is now "1.1.0"