Unbundling Pokémon Go
We have been wanting to write a blog post about reverse engineering for quite some time, but could never find the perfect app to take a look at. And then, out of nowhere, Pokémon Go took over the world in a week. We made some interesting findings ;-).
For an app that was only available in 3 countries at release (US, Australia and New Zealand), it still managed to top out big names like Twitter for active users or Facebook for time spent each day. It has become the most successful US mobile game ever, beating Candy Crush Saga. It has also proved beneficial, not just for the developers (who are making more than 1 million $ a day), but also for local businesses which have noticed an increased traffic and started planning on how to profit from this release, and for Nintendo, which saw its valuation rise 90% since release.
Since the game has been out for such a short time, it has also been the source of many rumors and urban legends, which are as many incentives for us to take a look at its inner workings. This blog post will focus on how to extract information from an Android app, via reverse-engineering of its code and its network interactions.
TL;DR:
- The code is not obfuscated, which makes attempts at reverse engineering much easier.
- We are able to rebuild a functional project
- Dependencies could be better managed
- No hint to future VR or Cardboard versions
- It may be possible to downgrade the minimum requirements
- We can get access to quite a few things: code for location/network/sensors and communication with Pokémon Go Plus
- The requests can be easily intercepted because of the lack of certificate pinning
- The requests seem to be done via protobuffers-RPC.
Reverse Engineering the app
The first thing you need to do in order to reverse engineer any app is obviously to get a hold of said app (which would be an apk file for Android). It seems not too difficult to find an apk of Pokémon Go (considering the amount of installations in countries where it had not been released yet), so we won’t detail here how to acquire it.
Please note that installing an apk from an untrusted source is a huge security risk. The Play Store does many analyses on applications it provides in order to minimize this risk, and bypassing it makes you a lot more vulnerable. But for reverse engineering, stumbling on a malicious apk would actually prove to be very interesting. Just make sure to understand everything inside it before installing it on a device.
In our case, we worked from an apk of version 0.29.0, dated from July 7.
We should also take a minute to discuss what we will not be able to see when reverse engineering. We are restricted only to what is inside the app, so there are parts of the project the developers see that are inaccessible to us. Here is a non exhaustive list:
- everything build related: we only have the result of the build, not the process
- everything about testing or continuous integration
- other variants (mainly debug versions): when developing, you tend to have special features that are not visible in production, and since we are working from a release version, theses features should not be included in the app (and are probably not)
- the source of the back-end. This should be obvious, but this is especially important here where a lot of people would be very interested to learn about the algorithms that decide, for example, what pokémons spawn where. But this is a decision made on servers. We can only learn how to communicate with these servers, not how they behave internally.
Inside the apk
Now, let’s take a look inside the apk. Actually, an apk is just a zip archive with the following parts inside:
Here is what each file (in green) or folder (in red) does:
- the Manifest is just the Android Manifest of the application. It acts like an identity card and provides the name, icon, version, permissions, device restrictions and different components of the app. It also acts as the entry point and is read by the system on installation or update.
- classes.dex is where the Java source is compiled. You can actually have more than one (this is called Multidex), but it doesn’t really matter for our purpose.
- lib is a folder containing the native libraries
- res and assets are direct matches for the resources and assets of the project
- resources.arsc is an Android specific file, which makes the link between code and resources (it allows the code to manipulate resources). This is the compiled version of the project’s R.java file
- META-INF is a folder containing metadata and is of no interest to us
So this is what you expect to see when unzipping the apk. The first file we will study is classes.dex.
Extracting code
The dex
extension stands for Dalvik Executable (Dalvik being the name of the old virtual machine Android used, the new one being ART, Android Runtime, but the file extension remains the same). This is a file format specific to Android, and is not very accessible. There are two main strategies for dealing with dex files: the first one consists in converting the contents to a more readable bytecode called smali (language created for this exact purpose) and the second one is to convert it to a more traditional Java file.
We will go the second route and use dex2jar to convert the dex into a jar, as the name indicates (a jar is just a zip file containing Java bytecode in .class files). Now that we have a jar, we have a lot more tools available to us. The next tool that interests us is a decompiler that will convert the .class bytecode files into Java source code. There are a lot of decompilers available, with their own strengths and weaknesses, which should be minor in our case. We used Jadx, but feel free to use another. You can even find decompilers online.
We should now have most of the code easily readable for any Java developer (which are a lot more frequent than Smali developers). “Most of the code” only, because it turns out decompilers have their limitations and are not always able to decompile every piece of code. There is actually a fascinating conversation on Procyon (another Java decompiler) about why bytecode converted from dex with dex2jar can sometimes prove hard to decompile.
Also important to note: this code is NOT the code the original developers wrote. A good analogy would be using Google Translate to convert a text from English to French, and then back in English. You would obtain a new text (in an English which would assuredly not be perfect) that would contain the same number of sentences, and the same themes, but it would not be the original text. The reason is that when translating in French, some choices will be made about how best to translate this word or this turn of phrase, and you would then make another set of choices when translating back in English. And since you only have the French text, it would take a miracle to make the exact same choices everywhere. For code, this works in a similar manner: we got code that acts the same way but is not a perfect match to the original source. For example, among things we will certainly lose are names of methods, variables and fields and developer comments, which are not present in bytecode and so cannot be reverted.
The first thing we can notice is that there does not seem to be any obfuscation. All packages have readable names, which allows us to see what libraries are used in the app. Here is a list of the libraries we could identify:
- Android support libraries : support-v4, appcompat and support-annotations
- Various parts of the Play Services
- Jackson (JSON parser) : core, annotations and databind
- Gson (JSON parser)
- Otto (event bus)
- Dagger (dependency injection)
- RxJava / RxAndroid (reactive programming)
- Apache Commons IO (utilities for I/O)
- AdMob, now declared as firebase-ads (ads, analytics)
- Upsight (analytics)
- Crittercism, now known as Apteligent (monitoring and crash reporting)
- Unity classes.jar (interaction between the Android framework and Unity)
- Lunar Mobile Console (Unity logger for Android)
- Voxelbusters’s Cross Platform Native Plugins (mainly used for sharing from Unity)
- Google VR SDK
Now if you are an Android developer, a few things should feel weird. Two JSON parsers ? Both reactive programming and an event bus ? This is generally a sign of transitive dependencies : you declare a couple of dependencies, and they themselves have their own dependencies, and so on. And sometimes, you end up with way more dependencies than you should. You can read more about transitive dependencies and how to analyse them in a previous blog post from us.
After cleaning everything, we end up with the following list of direct dependencies used by the project:
- Gson
- Crittercism
- Upsight
- Admob/firebase-ads
- Google VR SDK, Unity and associated
which is suddenly a much more reasonable list.
Actually, most of the imports we were seeing are coming from Upsight, which has a tremendous amount of dependencies (the list, with their number of methods: RxAndroid (4k), Dagger (~200), Commons IO (1k), Jackson (10k), Otto (~50), various Play Services (12k)), on top of their own code (3k methods):
+--- com.upsight.android:all:4.1.3
| +--- io.reactivex:rxandroid:1.0.1
| | \--- io.reactivex:rxjava:1.0.13
| +--- com.upsight.android:analytics:4.1.3
| | +--- io.reactivex:rxandroid:1.0.1 (*)
| | +--- com.google.dagger:dagger:2.0.2
| | | \--- javax.inject:javax.inject:1
| | +--- com.upsight.android:core:4.1.3
| | | +--- io.reactivex:rxandroid:1.0.1 (*)
| | | +--- com.google.dagger:dagger:2.0.2 (*)
| | | +--- commons-io:commons-io:2.4
| | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3
| | | | +--- com.fasterxml.jackson.core:jackson-annotations:2.6.0
| | | | \--- com.fasterxml.jackson.core:jackson-core:2.6.3
| | | \--- com.squareup:otto:1.3.8
| | +--- commons-io:commons-io:2.4
| | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*)
| | \--- com.squareup:otto:1.3.8
| +--- com.google.dagger:dagger:2.0.2 (*)
| +--- com.upsight.android:google-advertising-id:4.1.3
| | +--- io.reactivex:rxandroid:1.0.1 (*)
| | +--- com.upsight.android:analytics:4.1.3 (*)
| | +--- com.google.dagger:dagger:2.0.2 (*)
| | +--- com.android.support:support-v4:23.2.1 (*)
| | +--- com.google.android.gms:play-services-ads:8.4.0 -> 9.2.0 (*)
| | +--- com.upsight.android:core:4.1.3 (*)
| | +--- com.upsight.android:marketing:4.1.3
| | | +--- io.reactivex:rxandroid:1.0.1 (*)
| | | +--- com.upsight.android:analytics:4.1.3 (*)
| | | +--- com.google.dagger:dagger:2.0.2 (*)
| | | +--- com.upsight.android:core:4.1.3 (*)
| | | +--- commons-io:commons-io:2.4
| | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*)
| | | \--- com.squareup:otto:1.3.8
| | +--- commons-io:commons-io:2.4
| | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*)
| | \--- com.squareup:otto:1.3.8
| +--- com.upsight.android:google-push-services:4.1.3
| | +--- io.reactivex:rxandroid:1.0.1 (*)
| | +--- com.upsight.android:analytics:4.1.3 (*)
| | +--- com.google.dagger:dagger:2.0.2 (*)
| | +--- com.android.support:support-v4:23.2.1 (*)
| | +--- com.google.android.gms:play-services-gcm:8.4.0 -> 9.2.0 (*)
| | +--- com.upsight.android:core:4.1.3 (*)
| | +--- com.upsight.android:marketing:4.1.3 (*)
| | +--- commons-io:commons-io:2.4
| | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*)
| | \--- com.squareup:otto:1.3.8
| +--- com.upsight.android:managed-variables:4.1.3
| | +--- io.reactivex:rxandroid:1.0.1 (*)
| | +--- com.upsight.android:analytics:4.1.3 (*)
| | +--- com.google.dagger:dagger:2.0.2 (*)
| | +--- com.upsight.android:core:4.1.3 (*)
| | +--- commons-io:commons-io:2.4
| | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*)
| | \--- com.squareup:otto:1.3.8
| +--- com.upsight.android:marketing:4.1.3 (*)
| +--- com.upsight.android:core:4.1.3 (*)
| +--- commons-io:commons-io:2.4
| +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*)
| \--- com.squareup:otto:1.3.8
This means you have a huge dependency bringing you thousands and thousands of methods with it, and that you use just for analytics.
But now that the list of direct dependencies is considerably shorter (and after having excluded analytics, monitoring, crash reporting and eventual ads), the main dependency that remains is Unity, which is the engine on which Pokémon Go is running. This is why when you launch the app, you have a splashscreen with the Niantic logo (despite splashscreens being evil on Android) in order to give some time for the Unity engine to start. Then you have another loading (with a progress bar) which allows the engine to load all the assets required. Then you spend your whole time inside Unity (which is why you have no Android native interface).
This brings us to another dependency upon which a lot of ink has been spilled: the VR SDK. There have been a few articles during the Pokemon Go beta when people noticed a mention of Cardboard/VR in the code (using the same techniques we have), or this week when the app was updated and mentions of Cardboard appeared in the licenses. I’m afraid that my analysis of the code reveals no future plans of VR or compatibility with Cardboard. To the best of our knowledge, the code that was imported in the app is only used to make the link between the Android framework and Unity. Pokémon Go, like Cardboard apps, needs to make these two parts communicate and so the developers have included a bunch of open source code that facilitates this. But, according to me, there is absolutely nothing in the code that hints to any VR version of Pokémon Go.
At this point, we have spent some time on the code, and yet we still don’t have a functional project because we ignored the resources and assets. So, to finish up with this part and carry on, we cleaned the code artefacts left by the decompiler (methods unable to decompile, java vs AIDL issues, invalid java, extraneous files: multiple instances of BuildConfig.java, Manifest.java and XXXR.java…). Let’s now continue and move on to the other files from the apk, namely, said resources and assets.
Resources and assets
Fortunately, getting to the resources and assets is going to be much easier than the source code. In fact, assets are untouched during the build phase and packaged as-is in the app. But almost every asset in Pokémon Go is intended for Unity, so we are not going to interact with these for the time being.
The resources are much more interesting in an Android project. They contain the icons, layouts and wording of your application.
But unlike assets, resources are affected by the build phase (specifically, by aapt, the Android Asset Packaging Tool, which despite its name, works on resources and not assets). Notably, xml layouts (and the Manifest for that matter) are converted to a binary xml format, which makes it impossible for a person to read or edit. Also, 9-patches are transformed and lose the rows and columns that indicate their scaling properties.
The good news is that we have another tool (aptly named apktool) that allows you to revert these transformations. The decode option (apktool d) takes an apk and gives you an almost working Android project (with Manifest and resources in a much more readable format). The reason we did not use it earlier is that apktool transforms the classes.dex into smali files, when we wanted java classes.
Now we can include the decompiled resources, decompiled Manifest, and the assets into our project, and, since we cleaned up our java code earlier, we have now extracted everything we need for a fully functional Android project.
Building and running
In order to convert the sources in a new apk, you still need to create an Android project and provide build instructions. As you may remember from earlier, those are things we cannot reverse engineer since they are not included in the apk. In our case, we went for the simplest (and official solution): Gradle.
Since we identified all the dependencies we needed earlier, the process is pretty quick and straightforward.
One of the most interesting things this process teaches us is about the minimum requirements. Currently, the application on the store requires you to have at least Android KitKat (Android 4.4, API level 19). But the hardest requirement inside the project is the Google VR SDK, which requires at least API level 16 (JellyBeans, or Android 4.1). When looking at the code, I have no idea why the application on the Play Store requires you to have 3 versions more than it should. This restriction excludes 20% of the Android users (according to Google’s latest numbers). Either this is a conscious decision (for example, they have an update in the works that would require API level 19 and don’t want people that had the application not being able to update), or this could be a mistake (putting 19 earlier for reasons no longer valid and forgetting to downgrade the requirements).
But the most important thing is that we now have a project able to run on a real device. If you want to install an application reverse-engineered that way, my first recommendation is to change the application id (in your build.gradle and Manifest components/permissions) so that the two applications (yours and the official one) don’t interfere. Whatever the application, you will probably still want to keep the official version, which will keep being updated.
Unfortunately, you will not be able to go very far with this version: you will be stuck at the login step. The first login option (which I’m sure is the most used by a huge margin) is Google Sign-In. But when you use this, there is verification made to assert whether the app making the sign-in request is what it pretends to be. Namely, it checks the certificate with which the application has been signed. Since obviously we do not have the same certificate as the official developers, this step fails (you get a GoogleAuthException: INVALID_AUDIENCE). Being able to circumvent this security would be a huge breach (not just for this app, for every Google Sign-In in every application), so this is a bit out of our league. We could avoid completely the problem by registering a new application in the Google Developer Console (with our own package and certificate) which would allow us to login, but would give us a token that we would be unable to use to interact with the back-end (as the latter would aptly reject it and rebuff us).
But there is a second login option, which I doubt bothers to check the certificate of the application: via a Pokémon Trainer Club account. Unfortunately, due to the success of Pokémon Go, they are overwhelmed by the number of account creation requests, and have disabled the process for the moment. When it’s back up, we will be able to check whether our application can authenticate this way.
Analyzing the code
This will be a short overview of what we can learn from the code we now have access to. The main goal of this article is “reverse-engineering in general”, but this part would be heavily specific to each app, so there would little transferable knowledge from an app to the next. I will leave the detailed Pokémon Go analysis to motivated fans.
As we saw earlier, most of the app runs inside Unity. The upside for the developers is that Unity is platform-independant. So everything they write for Unity will run on both iOS and Android. This means that the code that remains are the parts specific to Android that they could not integrate to Unity. Namely, you can see code for:
- Sign-in / Registration (inside the package com.nianticlabs.nia.account)
- In-App purchases (inside com.nianticlabs.nia.iap)
- Interaction with Location, Network and Sensors (inside com.nianticlabs.nia.location/ network/sensors)
- Communication via Bluetooth with the Pokémon Go Plus (inside com.nianticproject.holoholo.sfida)
At first sight, the most interesting part would be the location/network/sensors code (if you are interested in cheating the game by faking your position or speed, or spoofing requests: always capturing pokémons on first try, getting exact locations and types, durations of lures, …).
But I think that the code handling the interaction with the Pokémon Go Plus should prove extremely beneficial for players. The promise of the device is to notify you of proximity to PokéStops (and retrieve items from it) or capturable Pokémons while having your phone in your pocket or bag. This means that there already is code that you can trigger to have the game running while the phone is locked. Being able to trigger this background behavior without a Pokémon Go Plus, and being notified via a phone notification would simplify the gameplay for the vast majority of players. If you combine this with analysis of the network requests, you could be able to be notified only for the pokémons that interest you: you could have the app running all the time in the background and only notifying you the one day you cross paths with the one pokémon that was missing in your collection.
Just take a look at some of the methods exposed in one interface contained in the package describing the interactions with the Pokémon Go Plus:
boolean notifyCancelDowser();
boolean notifyError();
boolean notifyFoundDowser();
boolean notifyNoPokeball();
boolean notifyPokeballShakeAndBroken(String str);
boolean notifyPokemonCaught();
boolean notifyProximityDowser(String str);
boolean notifyReachedPokestop(String str);
boolean notifyReadyForThrowPokeball(String str);
boolean notifyRewardItems(String str);
boolean notifySpawnedLegendaryPokemon(String str);
boolean notifySpawnedPokemon(String str);
boolean notifySpawnedUncaughtPokemon(String str);
boolean notifyStartDowser();
This is extremely valuable information! And you probably have enough information to build your own device, should you want to:
Listening to network requests
There is another category of reverse engineering you can use that does not require you to read the code or even put your hands on a file with the application: watching how the app interacts with the rest of the world. The principle of this method is not Android-specific and can be used for analysing any software.
Any interesting app is going to interact with the world in a variety of ways. The most basic apps will interact with the screen (via display and touch interactions), which you could trick via accessibility options (which will be the topic of one of our future blog posts). But many apps will interact with other parts of the system: the file system, sensors, network, …
Here we are mostly interested in network requests. Like we mentioned earlier, the most important parts of the game live on servers. The application needs to interact with servers in order to function. If we are able to intercept these requests and learn from them, we may be able to learn how to interact with those servers without going through the application (which is a very limited way to interact with the data living on those servers).
Side-note: actually, most of the time, Pokémon Go is doing something great with network requests, called Optimistic Models (actually, a weaker form, but still…). This means that when you do an action, the app does not wait for the server to confirm the action and moves along. If there is no error, the app feels much more responsive and fast. If there is indeed an error (which should happen with a very low probability when the servers are not overloaded), it warns you that what you did failed. You can see this when Transferring pokémons: there is no spinner, you are warned only if it failed. This is great for mobile apps where network requests can take quite some time. Currently, it’s not working so great because servers are over-saturated, but this should smooth out in the coming weeks.
So, how do we intercept network requests? The easiest way is to put yourself somewhere between the device and the server by using a proxy. But a basic proxy is limited: if the communication is done through HTTPS, the requests and responses contents will be encrypted. You will only be able to see some metadata about the exchange, but not the most important parts.
The next step is a man-in-the-middle attack. The way it works is that your proxy passes itself as the server for the app, and as the app for the server. Since you are the endpoint in both communications, you are the one with which the app and the server will exchange cryptographic keys. Basically, when you receive a request from the app, you decrypt it using your app-side key, encrypt it with your server-side key, wait for a response from the server, decrypt it with your server-side key, encrypt it with your app-side key and send it back to the app. You get access to full contents of the request and response, and both the app and the server have no idea that a third party was involved.
Obviously, if the story ended here, all encryption on the internet (and HTTPS) would be pretty useless since anyone that had access to your WiFi router could put such a proxy in place and see everything you do on the web. The issue with our previous scenario is that we exchange keys with unknown people. One of the solutions is to identify trusted persons before needing to exchanges keys. This works using certificate authorities, which act as trusted third-party. Your phone or browser is provided with a few before you do anything and this allows you to interact only with persons trusted by these authorities (this is how HTTPS, or rather TLS, behaves). If you want to interact with an untrusted person, you will receive an error/warning message.
But, since we own the phone, if we still want to intercept network requests, we actually can put our own certificate on our device, which will make the phone trust our proxy. So, in the end, even if the traffic goes through HTTPS (Pokémon Go does), there are still ways to intercept and read network interactions.
There are a variety of tools for this purpose, like mitmproxy which works through a command line interface. A more user-friendly (but paying) alternative is Charles. After configuring Charles and our phone, we can now see what goes on. This is an example of the requests that happen when you launch the app:
So we can learn a lot from this sequence already. It confirms a lot of what we saw previously while looking inside the apk. Let’s explain the first few requests:
- https://android.clients.google.com/c2dm/register3 : registering for push notifications
- https://stats.unity3d.com/HWStatsUpdate.cgi : probably just an analytics event related to Unity
- https://bootstrap.upsight-api.com/config/v1/a9cc12f87adc420baf964f187672ecb4/ : first analytics event for Upsight
- https://appload.ingest.crittercism.com/v0/appload : first analytics event for Crittercism
- https://pgorelease.nianticlabs.com/plfe/rpc : now we are getting to the good stuff. We will come back to those requests a bit later
- https://play.googleapis.com/log : interacting with the Play Services back-end
- http://lh4.ggpht.com/LakctgAXpXwe-3PMCWws8rCoVn1_TmyfAiWjWXm6VtsRjRl5v53n1JrWBumWmldzsBFxIUdRLXgsMewLjuyN : this is a request made to Picasa. This actually is the picture of the PokéStop just right outside of our office
- https://e.crashlytics.com : attempt to connect to Crashlytics (actually, this requests fails)
- https://www.google.com/loc/m/api : geolocation
We can see that the app interacts mostly with https://pgorelease.nianticlabs.com/plfe/ (which hilariously only displays the following message: “Dude, this is the Player Frontend.” and has for title “Holoholo Player Frontend”: we had already seen “holoholo” in the code for the package in which the app interacted with the Pokémon Go Plus: com.nianticproject.holoholo.sfida).
The number after in the request (in the screenshot: 226) is determined by the first request (which has no such number). I suspect this is for load balancing: a first request asks to be assigned a server, and then all of your subsequent requests (for this session) are made to the same server.
Finally, the last part of the URL in the request is “rpc”, which likely means that the app communicates with the server through Remote Procedure Call. This is consistent with the fact that all requests are made to the same URL (which would not be the case if they were communicating via REST).
Now, looking at the content, this does not look like JSON or XML. But it is not compressed or encrypted either: we can clearly see UUIDs and raw text (“pm0015” and such). Knowing Google’s fondness for protocol buffers (or variants such as flat buffers), this is probably the serialization format they used. And actually, Charles can help us with that by showing us request/response contents seen as protocol buffers (if you prefer the command line, mitm also handles protocol buffers, or you can use protoc --decode_raw
in a terminal after installing the protocol buffers library). And surely enough, it works, as we go from:
5ÉßÛS#pgorelease.nianticlabs.com/plfe/226:[
@nrÝZ¡Ï¯½'ëXÖÐ_}Î~ñ÷0'@
Ít-C÷
<j8yÊvâ9~Ä/§¾ñ¶,s^åïúÞ*$Äß.¸ñD©nz»fM¢¢
to:
1: 53
2: 6032429073588813826
3: "pgorelease.nianticlabs.com/plfe/226"
7 {
1: "nr\026\335Z\206\241\317\257\275\224\'\353X\326\320_}\220
\316~\227\361\3670\'@\205\315t\221\233-C\367\211\r<j8y\024
\224\312v\342\2269~\304\202/\036\247\276\361\266,\033s\027\006\f^"
2: 1468599616357
3: "$\002\304\337.\034\270\361\214D\251nz\273fM"
}
100 {
}
100 {
}
This is the content of the response of the first request made to pgorelease.nianticlabs.com/plfe/rpc. And as we can see, the response provides us with a new endpoint: pgorelease.nianticlabs.com/plfe/226
, which will be used for any subsequent request. And we also have an idea of the structure, even if we cannot understand it yet.
If you are wondering what are all those “\xxx”, this is called “octal escaping”. This is a way of representing bytes that do not map to simple characters. If we use an online decoder, we can see that:
nr\026\335Z\206\241\317\257\275\224\'\353X\326\320_}\220
\316~\227\361\3670\'@\205\315t\221\233-C\367\211\r<j8y\024
\224\312v\342\2269~\304\202/\036\247\276\361\266,\033s\027\006\f^
becomes:
nr5Z617754\'3X60_}06~7170\'@55t13-C71\r<j8y42v269~42/7616,s\f^
(which, granted, is not much more readable).
But we can already infer a few things from this. It looks like a list of objects, indexed by their UUIDs, and followed by their attributes (among which is “pm0015”). I am willing to bet that the content of this response is a list of surrounding pokémons (UUIDs identifying spawns), which type is given by the text (pm0015 would be pokémon n°15: Beedrill, pm0120 would be pokémon 120: Staryu, and so on). The rest of the data probably contains coordinates, combat power and other stats.
Actually, this hypothesis appears to be confirmed by other requests, for example: https://storage.googleapis.com/cloud_assets_pgorelease/bundles/android/pm0126. This seems to be a request to get the assets behind a pokémon (from Google’s cloud storage). So this request would give us the Unity asset for Magmar, so it can be rendered for capture.
If we keep digging, we can find other information. For example, this extract seems to be part of a response containing information about a player:
100 {
1: 1
2 {
1: 1467925951134
2: "REDACTED: player name"
7: "\000\001\003\004\a"
8 {
8: 1
}
9: 250
10: 350
11 {
}
12 {
}
13 {
}
14 {
1: "POKECOIN"
}
14 {
1: "STARDUST"
2: 500
}
}
}
One value is kind of identifiable for seasoned developers: the start of 1467925951134
and the length point to a Unix timestamp, precise to the millisecond. Sure enough, the date associated is 07/07/2016 21:12
(which is way too close to the current day to be a coincidence). My guess would be the registration date of the user. You can find these kind of timestamps everywhere in requests and responses. There is one inconsistency though: they are sometimes precise to the millisecond and sometimes to the nanosecond (even though this is only theoretical precision since the nanosecond numbers are always 0). For example, 1467338276561000
points to July the 1st.
By digging even deeper, we can see a lot of times a pair of consecutive values with the following example values: 0x40486ddc40000000, 0x4002d99520000000
. These values vary from one object to the next but are always close to other values, and always go by pair. This makes us think these could be coordinates, but the format is a bit more tricky. These numbers are not huge integers encoded in hexadecimal, but IEEE 754 doubles. The previous pair can be converted in decimal to:
Those are the coordinates to our office! With a bit of scripting, we can extract all the coordinates contained in a response, and then display them on a map. This was the data contained in just one response, and it had several types of points. We are guessing that the points represent: the position of the user (yellow marker), points of interests / PokéStops (red markers) and possible spawn points (green markers).
So with that, we are now able to read the network exchanges, we have the serialization format (protocol buffers), and we have identified a few ids, timestamps and GPS coordinates.
Again, we will stop our analysis here and leave the rest to motivated fans.
Conclusion: how to prevent reverse engineering
Now maybe after reading this, you are wondering if there is nothing the developers could have done to prevent this kind of analysis. Well there are quite a few options.
The most obvious one would have been to obfuscate the Java code, using Proguard. This would have replaced all the packages and names of fields and methods by random names, which would have made any analysis much more tricky. If you wanted to reverse-engineer such an app, the best strategy is to use the framework classes, which cannot be obfuscated, and move from there (for example, find which classes extend Activity, Fragment, View and so on). Also, Proguard is not limited to obfuscation but can also help in removing all unused resources and methods, which would reduce the app’s size (for Pokémon Go, this would not make a huge difference, since most of the size is used by Unity assets, with which Proguard cannot interact). Proguard is quite easy to use, and I’m certain future versions of Pokémon Go will use it.
Another way would be to minimize the amount of Java code (which can always be decompiled since it must be interpreted by the JVM). For example, compiling the critical parts of your application in native libraries makes any analysis much harder. But the inconvenient aspect is that it impacts your development, and in case there are too much communication between Java and native parts, this will also affect your performance.
Finally, we were able to intercept network requests so easily because the application does not use certificate pinning (ie, not using trusted third parties, but packaging your app with a certificate which will be the only way to communicate with the server). It is a common procedure, and is well documented, whether you use the basic Android classes, or OkHttp. Like obfuscation, this will not deter very motivated attackers (since they can reverse-engineer your certificate pinning), but this can slow them down quite a bit.
In the end, despite its length, this is a fairly basic analysis, using only off-the-shelf, accessible tools. We have not revealed any game-breaking secrets, published cheats or hacks, or given to readers any unfair advantage over their competition. But, if you are an app developer, you have to realize that those would be possible for dedicated attackers if you do not take steps to actively limit such attacks.
Here is a short recap of what we found:
- The code is not obfuscated, which makes attempts at reverse engineering much easier.
- We are able to rebuild a functional project
- Dependencies could be better managed
- No hint to future VR or Cardboard versions
- It may be possible to downgrade the minimum requirements
- We can get access to quite a few things: code for location/network/sensors and communication with Pokémon Go Plus
- The requests can be easily intercepted because of the lack of certificate pinning
- The requests seem to be done via protobuffers-RPC.
You can find our reverse-engineered project on Github.
If you have some feedback or questions, you can contact us on Twitter
We're hiring!
We're looking for bright people. Want to be part of the mobile revolution?
Open positions