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’ve probably noticed you can no longer MITM HTTPS requests between your Android device and the Niantic Labs servers. This is because of something called certificate pinning.
What is certificate pinning?
Put simply, it is the client performing extra validation against the certificate provided by the server. In this case, 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 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 the game itself, even though the network and account are both fine.
That’s a clear indication of a client checking the server certificate.
Before I try this, 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.
If you log in to the game using Google…
Due to an Android security feature, you may be unable to log in to Pokémon GO using your Google account.
Reverse engineering certificate pinning
Note: These steps are only valid for Pokémon GO version 0.31.0.
If an app implements certificate pinning, then it obviously must have the entire leaf, intermediate, or root certificate or at least the public key to validate against. The first thing I tried was searching for the leaf’s public key. To get that, I went to the Niantic Labs website and examined its leaf using Chrome.
The public key is clearly visible, so let’s extract the APK and use a hex editor to do a byte-search in the files that contain code.
classes.dex? Nope.
lib\armeabi-v7a\libmain.so? Nope.
lib\armeabi-v7a\libNianticLabsPlugin.so? DING!
One instance found for the public key. This definitely looks like a copy of the Niantic Labs leaf certificate.
This is an so file (shared object) which is full of native code. This is where things get more complicated. I’m going to be using IDA Pro v6.9 to dig into this file. There’s 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’s 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’s anything interesting in this function.
I’ve spent enough time reverse engineering to know that a memcmp and a “Rejected” string appearing in the same function is definitely something worth investigating. unk_1E2584 is the Niantic Labs leaf certificate, so this function must be comparing it against another certificate. In this case, the other certificate is the Fiddler certificate. We’ll want to patch this. Looking at the flow of the assembly, we can NOP (no-operation) that BNE below the memcmp and it will eliminate the possibility of getting to that “Rejected” block. 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’s no chance of getting to that “Rejected” block.
There’s one more patch needed. Before the memcmp, the function is checking the Fiddler certificate’s length. It’s making sure the Fiddler certificate is 0x5FF in length, which is the size of the Niantic Labs certificate. Niantic Labs probably does this to save a tiny amount of performance. Unfortunately, the flow of the assembly won’t allow us to place another NOP at the branch. Right now, it’s a BEQ, which means “branch if this 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 this location. Let’s make sure there’s no chance of getting to that “Rejected” block because of a length mismatch. To change this BEQ to a B, all that is needed is to update the opcode to 0x14E0 from 0x14D0.
Looks good! There’s a few more possibilities of getting to the “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.
IDA Pro doesn’t modify files directly, so we need to apply the patches manually to libNianticLabsPlugin.so using a hex editor.
- 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.
- 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 PC tools or ZipSigner in the Google Play store.
- Install the APK on your phone, ignoring the unknown sources warnings.
- Verify it worked by attempting to MITM the HTTPS requests using your software of choice.
If everything was done correctly, you will be able to see the HTTPS API RPC requests, and the game will function without displaying any error messages.
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. Develop responsibly.
Thanks for reading!
-Eaton