>> Pokémon GO - REVISITING THE "HACKING" SCENE (PART 9)
"security through obscurity" - keeping things private/secret to go against the
norm.
Sometimes; things are not always as they seem - take the above entry to the
IOCCC
in 1989. What on the surface looks like the mathematical symbol π, even
containing the well known 3.1415 number sequence actually calculated
the value of the e constant (2.718). In the realm of reverse
engineering one of the most common approaches is to find known patterns that
are put in place by the compiler, such as how
parameters
and return values are passed at assembler level.
In Pokémon GO; they made a simple mistake - standard calling methods
making detection easy.
uint8_t buf[256] = { 0x61, 0x24, 0x7f, 0xbf, 0x00 };
uint32_t len = 28;
uint64_t hash;
For the same of demonstration, lets consider we have the above global variables
and we want to calculate the LocationHash at lat=0, long=0
and altitude=0. This has been a standard test vector to verify the
hash function; as the GPS can be spoofed/disabled to get a known response
from the game servers to validate the hash function. The existing calling
sequence was:
uint64_t Hash(uint8_t *buf, uint32_t *len);
hash = Hash(buf, len); // perform the hash
This is a standard calling sequence - when compiled to ARM assembly; the
buffer pointer is passed in register r0, the length is passed in
register r1 and the result is returned as a combination of
registers r0 and r1. In fact; taking a quick look at the
sources to the unicorn program doing the hashing - you can see this directly.
So; in order to find the hash function, just need to find a similar signature
and it will be considered a candidate.
So; what could Niantic do to mess with this pattern?
The following is a very simple modification, venturing away from standard
practice; that could throw the reverse engineering team off guard when it
comes to finding the hash function. A couple of small type declarations
and the function signature needs to change:
typedef struct
{
uint8_t *buf;
uint32_t len;
uint64_t hash;
} HashPayload;
void Hash(HashPayload *payload);
The idea now would be to use a single parameter that references a chunk
of memory that will contain the other parameters; this single parameter is
then passed in using the register r0 and since there is no return
value declared - the hash result can be obfuscated in the calling sequence.
To hash a buffer, the following code would be required:
HashPayload payload;
payload.buf = buf;
payload.len = len;
Hash(&payload); // perform the hash
hash = payload.hash;
Depending on how the HashPayload structure is defined - this
could be hell for the reverse engineering efforts. They would need to
figure out how to reconstruct the result from whatever is being stored.
A small tweak would be to mix up the structure and force some deconstruction
after.
typedef struct
{
uint32_t hash_lo;
uint8_t *buf;
uint32_t len;
uint64_t hash_hi;
} HashPayload;
...
hash = ((uint64_t)payload.hash_hi << 32) | payload.hash_lo;
Even without employing code obfuscation to force a wild goose chase;
such a simple modification would be far more effective - as it requires
internal knowledge to know how to piece together the resulting information.
Not as simple as following standard calling procedures.
In fact; it seems that something like this may have been implemented
with the latest 0.47 release, finding the elusive hash function
has been a lot more effort - and even when some candidates have been found,
they haven't been using the registers the same way. A live update from
discord:
It isn't however the end of the cat and mouse game; just another chapter
in what will continue to be an ongoing saga between the game developer
and those who want to capitalize on the API with bot engines or maps to
either cheat or improve gameplay experience.
What about anti-emulation measures, what can be done here to make it harder?
It has been speculated that on Android; the
SafetyNet
engine has been integrated to prevent the use of emulation; more specifically,
since Google can provide device integrity checks independent of the application
this could be tied into the hashing process - an unknown only available when
running on a non-tampered device. iOS doesn't offer such a service - at this
point in time.
I am surprised that Niantic hasn't tried to integrate a dynamic element into
the process - something that can only be generated when the application is
running on the device itself. It could be as simple as hashing a couple of
known resources in sequence between each API release. Injecting it using
obscurity would make it almost impossible to track. Tie it into
"forced-updates" periodically - it will be simply too much effort to reverse
engineer for the rewards earned.