Basic memory manipulation

So, lets begin our journey with memory manipulation cheats because I think it is a good start for a person who is not very familiar with cheating and protecting against cheats.

For our experiments I have created simple 2D game using RayLib because this library is very easy to work with and perfect for prototyping things.

As you see, we have a very simple game where we collect pickups (dark blue circles) by colliding with them. Each collected pickup gives player +100 score. Now, let's find this value in CheatEngine (or CE in short) and alternate it.

It took me literally 10 seconds to set our score points to a huge number.

Lets think of how we could protect our score points against this kind of alternation without diving deep into complex anti-cheat implementation, that's our very first anti-cheat feature after all.

Value checksum

Do you remember good old times when your torrent downloaded files could be corrupted due to poor internet connection or faulty hard drive? Along with download it was a common thing to put some sort of hash of the file (md5 or sha1, doesn't matter), this hash was used in order to ensure that integrity of the file wasn't violation during download process.

We could utilize something very similar in our game, but using real hash algorithms could be very performance costly. In my honest opinion, even using crc32 is a bit too much for such task, but you, my dear reader, could implement any sort of checksum calculation you want.

My approach would be simple bit shifting. What I want to do is take the first part of the integer (2 bytes in our case) and swap it with last part of integer. That way we have reversible value that could be unpacked back into original value so we can perform integrity checks:

        __forceinline ProtectedValue& operator=(const TValue& value) {
            EnsureIntegrity();

            m_value = value;
            m_crc = m_value << Shift | m_value >> Shift;

            return *this;
        }

        __forceinline void EnsureIntegrity() const {
            if ((m_value << Shift | m_value >> Shift) != m_crc) {
                Violation violation;
                violation.Type = ViolationType::ProtectedValue;
                violation.Data = static_cast<const void*>(this);

                Report(violation);
            }
        }

I know, simple bit shifting is not secure, but I want to make things as simple as possible in this series.

Now, using this crc we could easily check if value was alternated by using CE (or any other direct memory write technique/software) without updating crc field.

I decided to perform integrity checks not only when I'm reading the value, but when writing, too. It may cost us some performance but will provide additional places where our checks will be inlined.

Note, that I marked methods as __forceinline intentionally, so attacker would have hard time to replace all the usages of this method inside our binary file. Basically, each call would be inlined into your code and those checks are going to be everywhere.

Report method simply shows message box and informs that integrity has been violated by the user.

Lets try to cheat again now, using the very same technique as before:

As soon as I changed the value our integrity check failed and my message box popped up. It works as intended.

But what if I don't want to expose raw value to literally anyone? Well, XOR is our friend in this case, we could easily generate compile-time random number and XOR our original value before setting it:

        __forceinline ProtectedValue& operator=(const TValue& value) {
            EnsureIntegrity();

            m_value = value ^ Key;
            m_crc = m_value << Shift | m_value >> Shift;

            return *this;
        }

No need to change EnsureIntegrity

Nothing, CE is now useless for this dumb types of attack.

Real-world use cases

Beyond theoretical applications, this simple yet effective technique is actually used in the wild by many games today. Developers often use these methods as "bait for idiots." Essentially, some values are intentionally exposed in the game’s memory to lure in less experienced cheaters who attempt to manipulate the game without thorough reverse engineering.

When a cheater modifies these bait values, they trigger a detection system that can lead to an immediate ban. This method is particularly effective because it plays on the overconfidence of the cheater, making them easy targets for anti-cheat systems.

For example, in some multiplayer games, you might find the player’s score or currency values exposed in memory. Cheaters who recklessly modify these values without considering the consequences can be quickly identified and removed from the game.

Conclusion

We've implemented very (!) basic protection against memory manipulation which, for obvious reasons, should not be used in form as I shown above.

In order to achieve better results it is highly recommended to:

  1. Strip all the RTTI and use home-made type id system. Providing RTTI in build is basically giving away your source code (a bit exaggerated, but it will definitely make life of an attacker much, much easier).
  2. Implement compile-time random function which will yield different values for each protected variable you have. This will make reverse engineering a little bit harder.
  3. Force-inline all methods that you will implement inside your protected value class.

In the next post we will start to dive into some coding magic and learn how to protect against function detouring.

💡
All source code is available on github: https://github.com/Revan600/anticheat_blog