>> Pokémon GO - REVISITING THE "HACKING" SCENE (PART 2)
Seems fitting with the haloween promotion there is a shady creature sitting
in the shadows.
In our
previous entry
we covered the history behind the reverse engineering effort on the
Pokémon GO application that has now turned into a cat and mouse
game between the developers Niantic and the hacking community. While
the current method may be questionable; it is actually in fact quite
ingenious - utilizing disassembly tools like
Hex-Rays IDA
and a CPU emulator known as
Unicorn.
While I wont post working code; I will explain the concept and show
the hash generation.
The typical process of reverse engineering requires deconstructing the
package files, isolating the executable code and using debugging tools
to trace the execution flow of the application. In the initial versions
of Pokémon GO where no counter measures in place to prevent
the decompilation of the application - in fact, it was so easy,
developers could modify the code and introduce neat hacks quite easily.
Not anymore however - the code is now heavily obfuscated.
As updates rolled out, additional counter measures were implemented.
One of the first was to deal with "cheaters", was the
Unknown6
signature to the elusive hash function that has yet to be reverse engineered.
While the hash function location was found quite easily - the frustration of
dealing with the anti-tamper mechanisms forced exploring alternative
solutions; CPU emulation.
While the documentation is sparse; the
tutorial for Unicorn explains usage well.
The idea floating around was to utilize the pokemongo executable
from the iOS application directly to calculate the hash required while
communicating with the servers. After isolating the "root" check
(basically, ignore the detection) and patching some of the dynamically
loaded function references such as stack checking guards - the code was
ready to run through the emulator.
In order to do this; the calling parameters of the hash function had to be
determined. In typical ARM calling sequences, this was that r0
contains the pointer to the buffer and r1 contains the length
of the buffer to be hashed detailing the calling sequence as:
uint64_t Hash(unsigned char* buffer, int size)
While it would be possible to call the function directly in the
emulation engine, to make things easier - a small stub was created
to know when to stop emulation. This stub essentially executes a
blx r2 which means call the function at the address stored
in r2. On completion; the hash is stored within the r0
and r1 registers and an uint64_t hash can be constructed.
An overview of the assembler looks like:
0x00001000: blx r2 0x1be8290: push {r4, r5, r6, r7, lr}
0x1be8292: add r7, sp, #0xc
---> call 0x1be8290 0x1be8294: push.w {r8, r10, r11}
0x1be8298: sub sp, #0x118
0x1be829a: mov r4, sp
0x1be829c: bfc r4, #0, #3
0x1be82a0: mov sp, r4
....
0x1be95aa: moveq sp, r4
0x1be95ac: popeq.w {r8, r10, r11}
0x1be95b0: popeq {r4, r5, r6, r7, pc}
The exact calling sequence looks like:
uint64_t Hash(unsigned char* buffer, int size)
{
uint64_t ret;
uint32_t r0 = 0xE0001000; // buffer (our emu heap)
uint32_t r1 = size; // buffer length
uint32_t r2 = HashFunctionAddr + 1; // iOS hash function address
// copy our buffer to the emulation heap
uc_mem_write(uc, r0, buffer, size);
// put the appropriate values in the right registers
uc_reg_write(uc, UC_ARM_REG_R0, &r0);
uc_reg_write(uc, UC_ARM_REG_R1, &r1);
uc_reg_write(uc, UC_ARM_REG_R2, &r2);
// execute code
uc_err err = uc_emu_start(uc, 0x1000, 0x1002, 0, 0);
// re-construct the return value from r0 and r1
uc_reg_read(uc, UC_ARM_REG_R0, &r0);
uc_reg_read(uc, UC_ARM_REG_R1, &r1);
ret = (unsigned int)r1;
ret = (ret << 32) | (unsigned int)r0;
return ret;
}
Looks pretty basic right? Let's see how it performs.
$ ./pogohash
:: Hash(buffer, sizeof(buffer) [iOS code]
61 24 7f bf 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 len=28
hash: 0xddef1097a35c4ed8
done
The return value was validated and then integrated into the other known
efforts for reverse engineering the API and a successful request was
made. The most difficult effort was then to integrate the CPU emulation
into the existing third party service and deal with the issue of performance.
So; how does the Unicorn CPU emulator actually perform?
$ time ./pogohash
real 0m3.573s
user 0m3.320s
sys 0m0.280s
No; that wasn't for a single hash. In fact, the Unicorn CPU emulator
has a
JIT (Just In Time)
compilation engine that actually gives it near native performance - it
is only the initial execution that is slower. The above timing was
performed on a Linux machine with a Intel® Xeon® CPU running at
3.20GHz with 8GB of RAM - for 10,000 hashes - thats 2,798 hashes per
second!
While the reverse engineering team is continuing to deconstruct the
algorithm to avoid utilizing the application binaries itself - it just
goes to show that regardless how much you try to protect your application
from hackers, you will always lose the battle. In fact; having a CPU
emulator do all the work for you also gives it a little bit of future
proofing as well.
The source code (updated to 43.3) for the cpu emulation and socket
server for hashing is available:
UPDATE: 2016-11-05
A version of the hash function has been produced after hours of work
tracing the raw assembler of the hash function and translating it to C.
It has been released to the public
on github - I have taken the liberty of
integrating it into pogohash-server and a new, separate download
is available below.