Reverse engineering and removing Pokémon GO’s certificate pinning
Discussion links: Twitter | Reddit | Hacker News
Update: Due to new security improvements in new versions of Pokémon GO, this method may no longer work.
Hello everyone, this is one of my first attempts at tinkering around on the Android platform. After spending so many years reverse engineering PowerPC executables on the Xbox 360 platform, I quickly got the hang of ARM and am happy to share some important information that will aid the Pokémon GO dev community.
By now, you have probably noticed you can no longer MITM HTTPS requests between your Android device and the Niantic Labs/Pokémon GO servers. This is because of something called certificate pinning.
What is certificate pinning?
Put simply, it is Pokémon GO performing additional validation against the certificate provided by the server. Pokémon GO expects the Niantic Labs certificate, but when you MITM with Fiddler, Pokémon GO sees Fiddler’s certificate. Pokémon GO detects this and aborts the connection before any data is sent to the server.
If you are interested in reading more about this in more detail, this page has a great explanation.
Has Pokémon GO always had certificate pinning?
On July 30th, 2016, version 0.31.0 of Pokémon GO was released. This is the second update for the game. The base game and the first update did not have certificate pinning. I was a little surprised that certificate pinning was not implemented from the beginning. However, once it was added, it was easily noticeable in Fiddler with all the failed CONNECTs.
And an error in Pokémon GO itself, even though the network and account are both fine.
Based on those observations, coupled with the fact that Fiddler worked fine on the previous version of Pokémon GO, there is a very high chance certificate pinning is now implemented in version 0.31.0.
Do I need root access?
You do not need root access! This method works on both rooted and non-rooted devices.
Will I be banned if I do this?
No bans were encountered during testing on version 0.31.0, but this can easily change in a future version. It is recommended you use a throwaway account when you need to MITM, just in case there are any custom/secret APK modification checks.
If you log in using Google…
Due to an Android security feature, you may be unable to log in to Pokémon GO using your Google account with a patched APK.
Reverse engineering the certificate pinning
Note: These steps are only valid for Pokémon GO version 0.31.0.
If you aren’t interested in learning how this was done and just want to patch your APK, scroll down to “Patching the APK”.
Pokémon GO obviously must have the entire leaf, intermediate, or root certificate or at least the public key to validate against somewhere in the APK, likely in a file that contains code. The first thing I tried was searching for the leaf certificate’s public key. To get that, I went to the Niantic Labs website and examined its leaf certificate using Chrome.
Let’s extract the APK and use a hex editor to do a byte sequence search in the files that contain code to find the public key.
One instance found for the public key. This definitely looks like a copy of the Niantic Labs leaf certificate.
This is an so (shared object) file which is full of native code. This is where things get more complicated. I’m going to be using IDA Pro version 6.9 to dig into this file. There are other disassemblers out there that can do the job, but IDA Pro is my tool of choice.
The fun begins.
Let’s search for that same sequence of public key bytes.
There is one instance, as expected. Scrolling up a bit eventually reveals a function that references the entire leaf certificate.
Let’s go into sub_A9BE4. Conveniently, the compiler has left a string at the top that identifies this function.
After a little research on Google, I discovered that NianticTrustManager is basically Niantic’s customized X509TrustManager, and they have chosen to override the default GetAcceptedIssuers method. By overriding it, they, according to Java documentation, have the option to “Return an array of certificate authority certificates which are trusted for authenticating peers.”
Let’s see if there is anything interesting in this function.
I’ve spent enough time reverse engineering to know that a memcmp (compare two blocks of memory) and a “Rejected” string appearing in the same function is definitely something worth investigating. unk_1E2584 is the embedded Niantic Labs leaf certificate, so this function must be comparing it against another certificate. In this case, the other certificate is the Fiddler certificate. Looking at the flow of the assembly, we can NOP (no-operation) that branch below the memcmp and it will eliminate the possibility of getting to that “Rejected” block because of a memcmp failure. A NOP opcode in ARM is 0x00BF, so let’s patch that in and see what the function looks like.
As you can see, our NOP is in place and there is no chance of getting to that “Rejected” block anymore.
One more patch is needed. Before the memcmp, the function is checking the server certificate’s length. It is making sure the server certificate is 0x5FF in length. The Niantic Labs leaf certificate is that long, but Fiddler’s is not. Unfortunately, the flow of the assembly does not allow us to NOP this branch. Right now, it is a BEQ, which, in this context, means “branch if the server certificate’s length is equal to 0x5FF.” Let’s change that to just a B, which is an unconditional branch, meaning it will always branch to a specified location. This will eliminate the possibility of getting to that “Rejected” block because of a length mismatch. To change this BEQ to a B, all we need to do is to update the opcode from 0x14D0 to 0x14E0.
Looks good! There are a few more possibilities of getting to that “Rejected” block, but let’s test this out before we worry about them.
Patching the APK
Note: These steps are only valid for Pokémon GO version 0.31.0.
Open libNianticLabsPlugin.so using a hex editor, or use IDA Pro’s Edit->Patch program menu functions to do the following:
- Go to offset 0xA9C76 and change 14 D0 to 14 E0. If you do not see 14 D0, you might be looking at the wrong file, or are looking at the wrong version of Pokémon GO.
- Go to offset 0xA9CB0 and change E2 D1 to 00 BF. If you do not see E2 D1, you might be looking at the wrong file, or are looking at the wrong version of Pokémon GO.
- Save the changes and close the hex editor.
- Replace the old libNianticLabsPlugin.so file in the APK with the patched one. You can do this using any program that can open zip files – an APK is basically a zip file.
- Sign the APK using your tool of choice or ZipSigner in the Google Play store.
- Uninstall Pokémon GO on your device if it is installed and then install the patched APK, ignoring the unknown sources warnings.
If everything was done correctly, you will be able to see the HTTPS requests in Fiddler, and Pokémon GO will function without displaying any error messages.
Does this work on iPhone?
You need a jailbroken iPhone to modify apps. Thanks to reddit user Mila432, we know that the function is very similar and can be patched the same way.
Also see the comments for another tip.
Important Note: Please do not abuse the Pokémon GO API. Putting additional load on the already-stressed servers could degrade the experience of millions of players around the world and encourage Niantic Labs to implement further API restrictions. Develop responsibly.
Thanks for reading!