Simple protection against memory hacking

While playing video games with leaderboards you might notice that sometimes there are players with enormous, impossible scores.

As you probably guess those ones weren’t scored by playing a game the fair way.
There are three (known to me) ways to do such a terrible thing:
  1. By modifying the save files or the shared preferences. Better not to use any kind of shared preferences to store vulnerable values, while the save files should be encrypted and has a checksum for checking if someone was trying to modify them.
  2. By using a proxy to perform a-man-in-the-middle attack (in other words – by modifying the request that was sent to the leaderboards system). Sensitivity to this type of attack depends of the leaderboards’ vendor you use. If you have your own leaderboard system make sure you communicate with it via SSL with public key infrastructure.
  3. By modifying the memory during the game runtime with such programs like GameGuardian.
We will talk about the 3rd way to cheat.
For the sake of simplycity I will use a Cheat Engine to demonstrate how memory hacking works (the process is very similar to the GameGuardian’s one).

Of course, if someone want to hack a game very much, they will find a way anyway. The goal is to make it a little harder than it would be without any kind of security.

Let’s run a simple program, that will increment scores in each second:
uint64_t Scores = 0;
while (true)
{
    std::cout << Scores << std::endl;
    Scores += 10;
    Sleep(1000);
}
Run this program and pause it using Cheat Engine (set the hotkey in Edit->Settings->Hotkeys->”Pause the selected process”.


Now search for the current score value which is 130.


As you can see there are three values in this process that are equal to 130. Which one is the one we are looking for? Unpause tha application and check which one is changing:


Now, when we’ve got the address of the value we’re looking for, we can change it by right-clicking on the address and selecting “Change value of selected address”:


At this moment, when you unpause the application, you will see that the score value was truely changed:


As you can see the memory hacking is based on searching the specific values in the memory and then changing them to receive better scores. To make hacker's life more difficult the value must not be represented in the memory as itself.

One of the easiest and the fastest way to encrypt the value is to xor it by a random key. Because we’re planning to use many types and floating points can’t be xored they have to be casted and stored in the biggest available chunks of data. The uint64_t will be just fine.

Let’s create a new class which will be our safe type.
template <typename T>
class safe_type
{
    uint64_t val;    ///< The value that will be stored in memory.
    uint64_t key;    ///< The key used to encrypt/decrypt the value.
    uint64_t chk;    ///< The check copy for validating the value.
As you can see there are three values. The idea is very straightforward:
  • When creating and modyfying the value the new key is generated.
  • The value is encrypted by this key.
  • Both of them – the encrypted value and the key are saved in the memory.
  • The unencrypted value is used to create a check copy, which is created by xoring it with a static, random number.
The whole constructor should look like this:
static uint64_t mask = 7591541521614551595;

safe_type(T v = 0)
{
    // Generate the key.
    key = (uint64_t)time(0);
    key |= (key << 32);

    // Bit-level cast.
    val = *(uint64_t*)&v;

    // Create a check copy.
    chk = val ^ mask;

    // Encrypting the value.
    val ^= key;
}
The tricky bit-level casting is a casting to another type without conversion. That means all bits from, for example, float will be moved to the int untouched.

The check copy will be used as the final defence line against cheaters, who somehow find the encrypted value.

When the safe_type is called it has to decrypt saved value, check it and return as the normal type.
operator T() const
{
    // Decrypt the value.
    uint64_t ret = val ^ key;

    // Check the check copy.
    assert(ret == (chk ^ mask));

    // Bit-level cast and return value.
    return *(T*)&ret;
}
Most of amateur cheaters won’t find the proper value to change. If the cheater will find it somehow he will have to know how to change all three used values to not cause an assertion (and they can’t blame programmer for an application crash in this situation).

This is, of course, only a suggestion how to do this kind of obfuscation. The trickier the encryption will be, the better.

The only thing that has left is to overload operators, so the whole class will be easy to use.

You can download an example code of how the obfuscation works from GitHub.