Android Game Hacking Series - Level 2
Introduction to Android Game Hacking and its techniques. Hello everyone! Here is our second post on Android Game Hacking.
Introduction to Android Game Hacking and its techniques.
Hello everyone! Here is our second post on Android Game Hacking. In the previous blogpost we worked on a hack related to memory edition and how to make the hack reliable. If you are interested in that one you can check it here: Android Game Hacking - Level 1
In this blogpost we will explore a new type of hack that will require patching the binary or rather the in-memory version of the binary, in order to make the game behave in a different way. In practice this kind of technique is much more powerful and gets more interesting results. Memory edition has its limits…
In this blogpost I’ll be using the rogue-like game: Hyperrogue
The objective for this hack is to create a god-mode cheat so our character won’t lose ever. A character looses when at least two attackers surrounds it, like in the following case:
Setup
We will work with the following setup:
We will use Genymotion as the emulator, Ghidra to execute the reverse engineering of the application, Frida to help trace the functions that calculate the condition of game over and Game Guardian to patch the application.
Step 1: Starting with an hypothesis
Whenever we have to generate a cheat that requires patching the binary we can opt generally with two alternatives:
In the first place I took the approach of finding the “GAME OVER” text that is shown whenever the user dies:
I used Ghidra to do the binary reverse engineering. In this case I executed a string search for “GAME OVER” as shown in the pictures:
Luckily I found only one match for it, so I went on analyzing the reversed code generated by Ghidra to understand what were the conditions required to show the GAME OVER message:
I found out that an important condition in order to check if the character was dead or not was to have the canmove variable in 0. So I tracked where this variable was being written. This can be done dynamically with a memory editor like cheat engine or statically with Ghidra’s capabilities to track the source code and infer where it is being used.
The dynamic way is better because it monitors the memory, so you could get the exact source code that reads or writes it even when the address is generated in a complex way.
The static way executes some analysis on the assembler and tries to link the instructions to the variables already found. It can be inaccurate or even miss some points.
In this case I wanted to use the static way in order to exercise some reversing skills. Also at the moment of checking the uses of the canmove variable it was written just once:
Step 2: Patching the application
So I had to patch the memory region by switching the strb instruction to anything that could be inoquous, like the nop instruction or a mov one that enforced the value of canmove.
In order to do this in Game Guardian we have to search in memory in the same way we search for a specific value, but instead of looking for an integer or a float we search for a hex value that can be taken from Ghidra:
004fb664 3f 01 00 39 strb wzr,[x9]=>hr::canmove = 01h
When I looked for this instruction I found lots of coincidences (more than 500), so I extended the search for more instructions from the part of the binary I wanted to patch:
89 00 00 34 cbz w9,LAB_004fb668
a9 31 00 d0 adrp x9,0xb31000
29 a1 44 f9 ldr x9,[x9, #0x940]=>->hr::canmove = 00ba54c4
3f 01 00 39 strb wzr,[x9]=>hr::canmove = 01h
Then I changed the instruction to:
004fb664 29 00 80 d2 mov x9,#0x1
But it didn’t work (which is why the subtitle of this section was hypothesis).
Step 3: Finding other path with Frida
After the first failure I decided to follow other path style. I looked in Ghidra for methods related to the string that is printed when the character tries to move when he is surrounded: “You would be killed”.
Then I’ll go through a dynamic validation of which methods are being called by using frida-trace like in the following case, because I found multiple places where the String could be used and the function that prints it was triggered:
frida-trace -U -i "*killMonster*" -i "*showMission*" -i "*count_status*" -i "*checkmove*" -i "*turn*" -i "*remission*" -i "*movepckeydir*" -i "*movevrdir*" -i "*mousemovement*" -i "*handleKeyNormal*" -i "*handleCompass*" -i "*attack*" -i "*Attack*" -i "*monstersnear*" -i "*kill*" HyperRogue
Whenever the action of movement after the user was killed is triggered the following trace could be seen:
Then I proceeded to read the methods and the tree of calls found and analyze if I could understand where the death condition was enforced. After reading around some reversed code I found that if the method monstersnear returns 0, the application won’t execute the remaining attack method which internally calls the wouldkill, which is the method that triggers the “You would be killed” message.
A fast way to test it without going through the binary patching is using Frida. The following script will change the behaviour of the monstersnear function to return always 0:
Interceptor.attach(Module.findExportByName("libhyper.so","_ZN2hr16monstersnear_auxEv"), {
onEnter: function(args) {
//AggregateErrorconsole.log("entra");
},
onLeave: function(retval) {
//console.log(retval);// simply replace the value to be returned with 0
retval.replace(0);
}
});
And after testing the function, it worked! So the next step would be to create a Game Guardian script that would patch the binary whenever the script is loaded. In this case the following hex code:
005075a4 fc 6f 4d a9 ldp x28,x27,[sp, #local_60]
005075a8 00 01 00 12 and w0,w8,#0x1
005075ac ff c3 04 91 add sp,sp,#0x130
005075b0 c0 03 5f d6 ret
would be changed for the following one:
005075a4 fc 6f 4d a9 ldp x28,x27,[sp, #local_60]
005075a8 00 00 80 52 mov w0,#0x0
005075ac ff c3 04 91 add sp,sp,#0x130
005075b0 c0 03 5f d6 ret
Creating a script based on this condition is pretty straightforward, but If you do not want to think, I have here the script for you:
gg.searchNumber("h FC 6F 4D A9 00 01 00 12 FF C3 04 91 C0 03 5F D6", gg.TYPE_BYTE)
gg.getResults(200)
gg.editAll("h FC 6F 4D A9 00 00 80 52 FF C3 04 91 C0 03 5F D6", gg.TYPE_BYTE)
gg.clearResults()
gg.toast("Done")
An alternative to this script would be to patch the application to add the Frida script, and run it when the application starts.
If you want to know how to do it, please read the following link: Permanent Frida Hacks
If you want us to create a video, reach out on our social networks and we will create the content for you :)
If you are a mobile game developer and you want to have an assessment on your mobile game, reach us, and we can help you to make your game hacker-proof!