Welcome to the first (real) post of my new blog!
Last October, I posted the following tweet:
The stage shown, Blackbelly Skatepark, was not officially announced by Nintendo at this point. I did not have any support from major Switch hackers, nor was public homebrew capable of any sort of game modding.
How did I do it?
The planned method
My initial thought was that the only possible entrypoint would be a feature in the game itself that we have some control over. Let’s look at the options:
- modifying online packets
- modifying local wireless packets
- modifying LAN mode packets
I didn’t (and still don’t) have any amiibo, which rules out the first option. Local wireless communications are undocumented, and I wasn’t willing to put in the effort to reverse wireless stuff. Finally, I was also thinking that modifying online packets would be too difficult. The encryption key would be negotiated with the matchmaking server, and the server itself had its communications encrypted in a fashion unknown to me at the time. Plus, I did not want to get my Switch banned from online play. This leaves one final entrypoint – LAN mode.
When thinking about how I could attack LAN mode, I thought that perhaps I could modify packets in-flight as they travelled across the network. My first step to verify that my method might work was to actually play a game in LAN mode, but I didn’t have any other consoles (or friends with Switches) that I could test with. After many false starts, I proceeded to set up a SoftEther VPN server and had some remote friends install the VPN software. Finally, in late August, we finally managed to play a Salmon Run shift in LAN mode over the Internet.
Breaking the encryption
We played a bunch of Private Battles and Salmon Run shifts while I was sniffing packets with Wireshark. The game would begin its initial communications in plaintext and transition shortly after to encrypted communications. I quickly concluded that the encryption algorithm must be AES-128-ECB. In a similar fashion to the image below, I saw that there seemed to be repeating 16-byte sequences, but sometimes the sequences would abruptly change to ‘random bytes’. Later static analysis of the binary revealed that I was correct in my conclusions – the game calls AES encryption functions but doesn’t do any “transformations” to the input, output, or the key.
Now that I knew the encryption algorithm, I could get to work trying to find the encryption key. Because I was inexperienced with IDA at the time, I wasn’t able to find where the encryption key is set, so I took a different approach. I knew that the repeating 16 byte sequences must be some sort of padding bytes, and they would likely be zeros. Another (naive) assumption I made was that the encryption key must be in one of the plaintext packets, otherwise the other consoles wouldn’t know how to talk to each other once they transition to encrypted communications. I made a program to brute force all 16-byte sequences in every plaintext packet against a single block of padding. If the block was decrypted to 16 bytes of zeros, then I would have found the encryption key for that session.
I ran the program, and it immediately spat out a key:
The IP address of the host console, the port that the session is running on, and ten bytes of zeros.
Good job, Nintendo! (By the way, there was also an HMAC-MD5 ‘signature’ appended to the packet. It used the same key.)
Modifying the map
Now that I could decrypt the packets, I went to work trying to write a Wireshark dissector for it. I have to give many, many thanks to Yuki Mizuno for uploading his Packetoon presentation. Mizuno-san reverse engineered part of the Splatoon 1 protocol, and documented what he managed to find in that presentation. Thankfully for me, the Splatoon 2 protocol at the time was exactly the same, apart from the actual payload data. (The development team used the same P2P networking library called
pia, and it seems that Nintendo did not make any breaking changes to the library between Splatoon 1 and the version used in Splatoon 2.) The information from his presentation allowed me to write my own Wireshark dissector.
While I was working on the dissector, I was simultaneously working on a way to decrypt and modify packets in-flight. I came up with a program I referred to as the “Interceptor”, which would relay packets from my local network to the VPN network. While it was relaying, it would also decrypt the packet and modify the payload(s) if necessary. The program was coded in C# and used Pcap.Net to listen and send packets on the local and VPN network interfaces. A friend and I wrote classes to deserialize and reserialize
pia packets into C# classes, allowing me to access the raw payloads easily. I wrote a loader inspired by RayKoopa’s Wii U BFRES library which allowed me to write “scripts” that modify packets, which saved me the trouble of having to recompile the program every time I wanted to patch something new or make a change.
Using the dissector, I did a search in Wireshark for map IDs in all packet payloads. I found the map ID for Spawning Grounds in Salmon Run, and also found that the game uses the same packet in multiplayer. I then wrote a script for the Interceptor which modifies that map ID to one of my choosing.
On October 18th, I finally had something to show for all my work, nearly two months after my initial idea.
I also got a glimpse into
cVrc (the unreleased gamemode Rocket), but making it actually work is outside of the capabilities of LAN mode modifications. It simply loaded the Tower Control layout. Simon, my testing partner, was playing on Rainmaker – only one console could be modded at a time because of the client/host relationship. (I modified the packet that the host sends to the client with details like map ID and gamemode. This packet only goes one way, because the host is in charge.) Even if this had worked, there are no Rocket objects on any released maps, so it would have failed in the end anyway.
I also found yet another unreleased map, but it didn’t have a model at the time, so the game crashed. In the English localization, it was called “Dunkleo Aquatic Center”. We would find out in later versions that this map was renamed to Shellendorf Institute.
After Splatoon 2 version 2.0.0, I started playing a cat-and-mouse game with Nintendo. Each major update, they progressively made their key generation algorithm more advanced, incrementally making it harder to decrypt packets. The biggest change was in version 3.0.0 when they moved away from the insecure AES-128-ECB algorithm to AES-128-GCM. Perhaps I will write about how I reversed the algorithms in 2.0.0 and 3.0.0 in the future?
I would continue reversing the
pia protocol and update my tools until atmosphere’s
loader module started working on 5.0.0. As much as these tools are close to my heart, they are no longer used because superior methods are available for datamining and modding purposes. Expect to see them released in the future when I’m not lazy.
Anyway, that’s how I was able to begin datamining future updates without homebrew, exefs, or romfs replacement. tl;dr – I reverse engineered the encryption for Splatoon 2’s LAN mode and modified game packets as they were transmitted over the network.
(Shoutouts to Wii Sports, and very special thanks to my testers + contributors at the time: Yahya14, NefariousSquid, amibu, and Simon. Please give me feedback on my Twitter about how I can improve my writing!)