Demystifying Typical Mobile Game Hack
Mobile game hacks are widely used to unlock premium features or obtain an unfair advantage, but these techniques can be used to target any type of app. In fact, game modders are often some of the most agile and effective attackers in the mobile space. That means understanding the techniques used for cheating can help you harden your app against potential attacks as well.
In this blog post, we will shed more light on mobile game hacks that players can find online in the form of modded APK or IPA files. These distributed cheats are quite powerful because players don’t need a jailbroken or rooted device to install them. As usual, we picked a well-known game for the analysis, but our research has shown that many games are vulnerable in similar ways. We've anonymized our example here to respect the interests of the game publisher, who does not want to draw undue attention to this form of cheating. For the purpose of this article, we will call it Hacked and Vulnerable iOS Game (HViG).
The original game and its sequel are notoriously modded by cheat developers and distributed for free on online cheat forums. Both editions have been cracked to remove limits put on resources in the game and avoid paying for extra items. The mobile game cheat is distributed in the form of IPA/APK files and available for non-jailbroken and non-rooted devices. So far, it has been reported that there were 600k downloads across multiple platforms and from different cheat distributors.
The cheat usually comes with a full description of all the hacked features; you can install it on your device by sideloading the app. Below you can find the cheat descriptions for the most recent versions of both editions.
Cheats description of Hacked and Vulnerable iOS Game
The cheat description clearly shows what features were modified, but the best way to find out how the game has been modified is to compare it with its original version. At the time of writing this blog, both cheats are targeting the newest versions of the original games from Apple’s AppStore, despite the fact that these original versions were released just a week prior to writing this! This highlights the speed and agility of the mobile gaming cheat community.
Comparing the original and the modded versions
As a first step, we installed the game from the AppStore and dumped it from a jailbroken iPhone with bagbak. Once we collected the modded and the original versions of the game, we compared the apps’ main binaries, which contained the cheats in this case.
Remember, when the app is compiled we can no longer see nicely formatted Swift code but a set of assembly instructions. Don’t worry if you don’t know a thing about assembly, we will guide you through the process.
Let’s start from the first edition of the game. We used a combination of otool and diff to compare the original and modded binaries. The output of the commands shows all the changes in the binary; we can see that 7 assembly instructions were replaced. We can analyze a few of the assembly instruction changes and match them with the corresponding features from the cheat description.
Modding the game to get unlimited money
The first patch has been found at the0x100145838
address. The original instruction at this address issubs w10, w10, w11
and it simply subtracts two values from each other. In other words, you can imagine this expression asw10 = w10 - w11
. The wX are called registers, consider them assembly ‘variables’.
If your game subtracts the number of coins when a player buys an item, the compiler would probably use this assembly instruction to calculate the difference. The application is not obfuscated, so you can easily decompile the whole function at address 0x100145838
into pseudo C source code using, for example, IDA, Ghidra, Hopper. Below, you can see the output of Hopper’s decompiler with our comments for a better understanding. The function is rather easy to follow:
- Check if we can afford a certain item of price given in
arg1
- Subtract the current amount of money with
arg1
- Returns
True
on success
- arg0 + 0x58cc is
gameobject->money_count
- sub_10015c138 is
getBonusMultiplier()
This can easily be deduced from the game since we’re dealing with unobfuscated code.
// arg0 - address to a game object in the memory containing current amount of money.
// arg1 - amount of money to decrease (itme price)
int sub_1001457d0(int arg0, int arg1) {
// Check if the player has enough money points to buy an item
// Memory at (arg0 + 0x58cc) contains the curent amount of money
if (arg1 <= *(arg0 + 0x58cc) + sub_10015c138(arg0)) {
// Subtract current amount of money
*(arg0 + 0x58cc) = *(arg0 + 0x58cc) - arg1;
// Set the return value as True
var_retval = 0x1;
}
else {
...
}
return var_retval;
}
An output of Hopper decompiler with our comments included.
In the modded version of the game, the sub instruction is replaced with nop
. The nop instruction does nothing, literally nothing. It basically tells the processor to just skip to the next instruction. Therefore, the nop instruction is commonly used by mobile game cheat developers to discard a part of functionality such as the subtraction of coins. If you decompile the modded version, the pseudocode of the function will be identical apart from line 8 because it skips the subtraction instruction as shown below. On a pseudocode level, this basically comments out line 8 of the function.
*(arg0 + 0x58cc) = *(arg0 + 0x58cc)
Runtime Confirmation
Let’s confirm our theory by actually executing and monitoring the game, a technique called dynamic analysis. We ran the original app and attached an LLDB debugger to intercept original values stored in registersw10
andw11
at address0x100145838
. We then inserted a breakpoint at this address to stop the execution of the game, and print the values of w10
and w11
. Then we continued playing the game until the LLDB debugger yielded an output.
Below you can find the LLDB commands that we used. In case you don’t know how to attach the debugger to the game, read our previous blog about memory tampering in games where it’s covered in detail.
You may notice that we put a breakpoint at a different address than 0x100145838 . Keep in mind that ASLR adds a random offset to the memory address space in the runtime. So, you need to calculate it before setting up a breakpoint . You can use the image list command in LLDB to look up the base address of the binary and then add it to your instruction address. |
The screenshots below show the moment where a player buys an item for 100 $ units. Due to the censorship, we substituted all gameplay screenshots with equivalent sketches showing the same logic as found in the original game.
The LLDB breakpoint triggers when we buy a new car and the current money counter decreases by 100. After substituting the output from LLDB to the original, unmodded assembly instruction, you can see that the new value of w10
is 100 because w10 = 200 - 100
. This is also the new value of the money counter in the game. This subtraction doesn't take place in the modded version of the game because it is nopped away.
Voila! We just found the patch for the first mod from the cheat description - unlimited money.
A game mod for even more money
Let’s move to the second patch at address0x10015bf9c
. The original instruction at this address wasadd w9, w12, w9
but the cheater patched it intoadd w9, w10, w9
. The add instruction simply adds 2nd and 3rd registers and stores the result in the 1st one. You can think of it asw9 = w10 + w9
. We can see that the attacker simply replacedw12
with thew10
register.
Let’s see what values are stored in these registers in the runtime using dynamic analysis. The output of LLDB below showed values of x9
, x10
, and x12
registers after collecting the money 3 times in the row (red, green, purple). We started the original, unmodified game with the money counter equal to 50 and with each collection we scored 25$.
GIF below shows the increasing amount of money at each collection of a money sack
It’s clear that x9
holds the number of dollars that are added to the money counter with each collection.x12
stores the amount of money before the addition.x10
seems to contain a constant value of 22732
at each collection. A quick look at the binary shows that it’s a constant offset used for latter address calculations.
That means replacingadd w9, w12, w9
withadd w9, w10, w9
keeps the money counter at 22732+25 $.
The two patches that we analyzed so far relate to the same cheat feature - unlimited money. The first one protects against subtracting the current amount of money and the second keeps the amount at a high level. One might have been enough, but it seems the author of the cheat was thorough.
The remaining patched instructions in HViG work in a similar way to enable the rest of the hacked features from the mod description.
Dealing with obfuscated cheats.
While inspecting cheats from the second edition of HViG, which promise unlimited money and even more extras, we noticed something interesting. After applying the same techniques as described before it seemed there were over 1k changed instructions
Luckily most of these turned out to be deliberate distractions. The author of the mod likely replaced random assembly instructions inside the binary with their interchangeable equivalents so they look like real modifications. These interchangeable equivalent instructions don’t change the logic of the game but stills show up as diffs.
Deobfuscation of the instruction substitution
A quick analysis of these 1k patched instructions showed that the obfuscation pattern is rather simple. The attacker identified 3 types of operations that could be easily substituted. The table below shows all interchangeable instructions the attacker used.
Original instruction |
Modified instruction |
Description |
mov x0, #0x0 |
sub x0, x0, x0 |
x0 = 0 ==> x0 = x0 - x0 |
eor x0, x0, x0 |
Using a xor operation on the same value always results in 0. This is the same as assigning 0 to a register. |
|
nop |
mov x0, x0 |
nop instruction skips to the next instruction, so it doesn’t change the logic of the code. Moving a value from one register to the same one doesn’t change the logic of the code either. |
ret |
br x30 |
ret instruction jumps to the address from which the current function was invoked. The return address is stored at register x30, so we can jump there directly with a br instruction. |
Because of the simplicity, it’s straightforward to deobfuscate such code with, for example, grep
, filtering out all substituted assembly instructions. You can catch the substitution patterns using regular expressions. Below, you can find a complete command filtering out all obfuscated assembly instructions.
Finally, we can see only 3 relevant patched instructions that really affect the game and all of them look similar. The general purpose of theldr
instruction is to load the value from the memory to the register for later calculations. It’s often used in getter methods in high-level languages when a developer wants to get a value of the object’s property.
This mod patches theldr
instruction inside getter functions for amount of money so that it returns a different values. The value is taken from thex29
register, which holds the address of the stack frame pointer. This address is always described by a high value, which is surely big enough to consider as unlimited. Below you can see a screenshot of the modded version of the game with the near infinite number of money.
Screenshot taken in the modded version of Hacked and Vulnerable iOS Game
Take home lesson
Distributing modded games is a common way of cheating in games. Cheat developers distribute modded APK and IPA files on the internet so anyone can download them and freely install the mods on their device. However, there is a couple of ways to protect your game against such mods:
- Validate every update of a sensitive value with the server, so that a local mod on the application won’t affect the game because the disparity will be caught on the server side. Unfortunately, real-time games can’t validate all variables on the server side because it would slow down the game significantly and ruin the real-time game experience. At the same time, validating a large number of variables introduces complexity to the code base and adds overhead to the computing efficiency, so remember to fine-tune your code and be ready for trades-off.
- Modders usually run your game and inspect variables at specific addresses with a debugger or Frida to find out which instructions to mod. Therefore, we recommend implementing runtime application self- protection against debugging, dynamic analysis, and a compromised environment. Analyzing games on jailbroken and rooted devices makes it much easier for an attacker to learn your app because they are not constrained to the system limitations anymore.
- Verify that the code of the app hasn’t changed since the compilation. You can iterate over the machine instructions of your app in the runtime and make sure they are intact. A common way is to create a checksum of the whole code and verify the whole code at once.
- Players who want to install a modded game need to re-sign the app first to let it run on their devices, so you can check if the app signature and the application ID are still identical to the original ones.
- Obfuscate your binary to make it harder for an attacker to find the right spot in your binary to patch. Leaving your getters and setters in their original representation gives an attacker an easy way to find all your variables in the game. Even modders obfuscate their patches, why wouldn’t you obfuscate your code?
Benefits of app obfuscation by example of Hacked and Vulnerable iOS Game
In the previous section, we showed how easy it is to understand the pseudocode of an unobfuscated function and how it can be exploited by the attacker to mod the game afterward. Obfuscating is a great countermeasure against reading our code with such ease.
There are a few types of obfuscation that we could use to protect our app from mobile game hacks:
- Arithmetic obfuscation - adds complexity to the calculation inside the application. It replaces simple operations such as subtraction and multiplication with complex mathematical operations and makes them dependable on multiple parts of the game.
- Control flow obfuscation - scatters the logic of the code around many places in the binary, so the attacker can’t find a single comprehensive chain of instructions that would describe the behavior of the app.
Through various means (strings, runtime inspection,...) cheaters identify the sensitive locations in games after which they’ll resort to static analysis to be able to understand and correctly patch the logic. Important tools here are control flow and data flow analysis, with ‘control flow graphs’ and ‘call graphs’ being key visualizations. These graphs show all if statements, switches, function invocations and loops in the form of an arrow to a separate block of assembly instructions, so you can easily follow the logic of the game. For example, below you can find the CFG graph of the previously mentioned function to buy an item.
We obfuscated the same reconstructed function with arithmetic and control-flow obfuscation to compare both graphs. Below, you can see how much the obfuscation complexified the function.
Reading the pseudocode of such a function doesn’t even make sense because the decompiled code would miss a lot of key details or would be inaccurate. Many decompilers and disassemblers can’t even generate a pseudocode for such obfuscated code. What’s more, in the case of our analyzed function, the control flow graph doesn’t show a single if statement branch anymore, so there is no easy way to follow the logic of the code.
Resetting-the-clock
Applying obfuscation during every new release will make your app binary completely different each time, so an attacker would need to analyze your game from scratch when a new version appears in the AppStore or Google Play store. Therefore, even if the cheater finds the right place to patch the assembly instruction, the same mod won’t be reusable in the new version of the game.