Sniffing the Pokémon Go Binary Protocol
The Pokémon Go app communicates over HTTPS using a binary protocol. To intercept it, point Android’s Wi-Fi proxy settings at a local Fiddler instance. The app contacts a generic endpoint first, then switches to a load-balanced one. The content-type header claims form URL-encoded, but the wire bytes are clearly binary. The first byte of every request is 0x08, and fragments of readable text appear throughout. Both are tells for Protocol Buffers, Google’s serialization framework.
Decoding Protobuf Without the Schema
Pokémon Go ships no .proto files, so the schema had to be reconstructed from captured bytes. The --decode_raw flag on protoc decodes any message without a schema and returns field numbers with guessed types. From there, field values were the main guide. Field 10 held what looked like an OpenID token, making it the auth field. Fields showing up as hex integers were actually GPS floats sharing a wire type. Watching which fields changed when moving filled in the rest. Request 106, later named get_map_object, returns Pokémon at exact coordinates for a given S2 level-15 cell.
Certificate Pinning as an Anti-Proxy Defense
After a Pokémon Go update, the scanner stopped and Fiddler captured no traffic at all. A researcher found a new class, NianticTrustManager, extending X509TrustManager and implementing certificate pinning. Standard HTTPS proxying works by installing the proxy’s root CA on the device. Certificate pinning breaks that by hardcoding the server’s expected public key in the app and comparing it against every TLS handshake. The fix was an Xposed module that hooked checkServerTrusted and swapped the incoming certificate chain for the real Niantic certificate before validation ran.
Unknown 6: The Hidden Request Signature
Bypassing cert pinning restored traffic visibility but requests still failed. Active probing (removing optional fields one by one) identified field 6 of the request container as newly required. The team named it Unknown 6. It always measured 288 bytes: a multiple of 256 plus 32. Freezing the system clock showed the first 32 bytes depended only on time. Changing just the S2 cell ID in a request made a valid Unknown 6 fail server validation. That confirmed Unknown 6 was an encrypted signature over the request contents.
Cracking the CBC Encryption and Signature Fields
Static analysis in IDA identified the Unknown 6 generator at offset 87444 in libniantic_manager. It called time, fed the result into srand, then generated 32 random seed bytes. Input was padded to 256-byte blocks and encrypted with CBC mode using that seed as the IV. One group decompiled the entire call chain into a 14,000-line C file that compiled and ran correctly. A second group found the signature needed only six fields: two timestamps, two xxHash-32 values over location and auth, a per-request xxHash-64 list, and one field that accepted no input.
Sixty-Seven Hours to a Working Unofficial API
Sixty-seven hours after the hackathon started, the team sent its first successful unauthorized API request to Pokémon Go servers. The final blocker was field 22 in the signature. It looked like it depended on authentication info, but a researcher eventually found the generator function took no input at all. The field could be any value. The Discord server had grown past 10,000 members by then, with hundreds listening to the research channel voice chat in real time. Pogdev maintained the resulting unofficial API for about a year and a half before going dark.
Notable Quotes
first two questions was actually pretty simple. The server just doesn’t care. Uh Tal Skverer · ▶ 16:08
We don’t know what it means to this day. Tal Skverer · ▶ 43:56
Key Takeaways
- Request 106 returns exact Pokémon GPS coordinates, proving servers often send more data than apps display.
- Checking for X509TrustManager subclasses reliably locates certificate pinning in Android applications.
- Only 6 of Unknown 6’s many signature fields were actually server-validated, despite widespread anti-cheat fears.
- A 14,000-line IDA decompile of the native library compiled cleanly and produced a working encryptor.
- Active probing (removing fields until the server errors) finds required fields faster than static guessing.
About the Speaker
Tal Skverer is the Head of Research at Astrix Security, where he focuses on how cloud providers implement connectivity for non-human identities. He holds an M.Sc. in Theoretical Computer Science from the Weizmann Institute. His background spans reverse engineering, malware analysis, embedded systems, cryptography, and web security. He teaches a biannual workshop on assembly and x86/x64 reverse engineering. Past work includes breaking PokemonGo’s anti-cheating system in 2016 as part of team Unknown 6, hacking vehicle infotainment systems, and converting a broken OnePlus 5T into a home network ad blocker. He has presented at DEFCON, RSAC, BSides, and OWASP chapters.