Artikel uit SDN Magazine 147
Like many of us in the tech industry, I have enjoyed playing games from a young age. Sometimes I would want to do things in games that you were not really supposed to do. I would use open source tools and trainers to do this, but was clueless as to how any of those tools could possibly work. One day I found a tool written in C# with some bugs. As a C# developer, reading through this tool felt less daunting, and it led me on a path to learn game hacking and reverse engineering. In this article I will show you how easy it can be to do a very basic attack on a game, and how simple and short the code can be.
Disclaimer: I mod offline games only. Cheating in online games is lame. Use the tricks I teach you in this article for offline games only. Don’t use these tricks to get around paying for a license in programs.
The best way to learn is by doing. A remake of a popular video game “Resident Evil 4” has recently been released. In honor of this, I opted to target the original Resident Evil 4 from 2005 for this article. Let’s modify that game so that we have infinite ammo. Let’s talk strategy.
The game will allocate some memory to store a variable for ammo (the number of bullets that remain in our gun). This number could be in a class instance, or a singleton, we don’t know, but it will definitely be somewhere in memory. We can use a memory scanner to find this memory location. Once found, we can use a debugger to find out what parts of the game’s code is reading or writing to and from this memory location. We can then use a disassembler to turn the game’s machine code into readable assembly, from there we can decipher which parts of the code are decrementing the value each time we fire the gun. We can then replace that code with our own code that does nothing, effectively giving us infinite bullets! Sounds easy enough right? Let’s dig in.
Memory scanning
When a native program is started on Windows, the executable file is copied into memory. This file contains the machine code (ones and zeros) that make up the game’s code. Everything the program does will also need memory in one way or another. In our case, there will be a player object somewhere in memory. There will also be a level, some enemy objects, etc. All of these things will be allocated on a memory location called the “heap”. The heap is dynamic, and everything will go in a different spot every time. Things you find on the may no longer be there after a certain interaction with the game, like changing levels, or restarting the game (which will clear all of the heap of course). We will be scanning through all of this memory, mainly through the heap. If a game says it is using 12gb of ram in task manager, we will be scanning through the largest portion of this 12gb of memory.
Multiple tools exist to assist with memory scanning, I will be using Cheat Engine for this. Cheat Engine is a Swiss knife when it comes to reverse engineering games.
The first step is to reason about, or do an educated guess, as to what data type will be used to store the memory value that you want to find. In our case, it would not make sense for the ammo to be stored in a floating point data type, or in a string. Most likely it would be stored in a 32 bit integer.
The second step is the filtering strategy. In our case, we know the value of the ammo. Sometimes, you’re looking for an internal game engine value, like the ID of the current map or the current soundtrack. In those cases, you don’t know the value. Besides knowing the value, it is important to be able to influence the value, or to at least to know when it has changed. The reason for this is that we will go through all memory associated with the program, and do a little elimination game.
We might start off with an “unknown initial value”, in which case we start with all memory, or we have a known initial value, in which case we already start filtering out memory locations with values that do not meet the criteria of our initial value. After the initial scan, we will do another scan, to do another round of elimination. This time, we don’t scan all memory again, instead we use the locations that we found in our previous scan. Each scan makes the list of possible memory locations smaller.
We can influence the ammo value by shooting in-game, right before we do a next scan, to change the value. We then either scan with an exact value, where we filter out any memory locations whose value is not equal to the ammo, or we scan for values that have decremented since last scan or we could scan for values that have simply “changed” in any way, increased or decreased.
Once we only have a handful of memory locations left, we can write to them one by one and check the game if it changes our ammo. You probably have at least 10 addresses most of the time that could potentially be the thing you are looking for. But that means the other 9 are for something else, and writing to those addresses could potentially crash the game.
You can also imagine that the UI code will have a copy of the ammo variable, and use that copy to update the UI, so that players can see it on their screen. This value will be overwritten by the real variable every frame. If we would overwrite this value, we might see the game’s UI update to the new value, but as soon as we shoot the gun, the value gets brought down to the original, minus one. This is why we write to each variable, one at a time, and then fiddle with the game to see if it had the correct effect.
Being skilled at manual memory management in C/C++ makes it easier to understand how memory works, but will also make your educated guesses better. It will also help you understand what interactions with the game would be helpful while scanning.
(picking a data type and a value for the first memory scan in cheat engine)
(scan results)
As you can see, I tried the elimination game with exact values. I looked at the game’s UI to determine what value to scan for. Unfortunately, this approach ultimately led me to eliminate all memory locations.
On the 2nd attempt, I started scanning for an “unknown initial value”, and scanned for a “decremented value” each consecutive scan after that, shooting the gun in-game in between each scan. This ultimately boiled the list down to 3 memory locations. At first sight, the values of those locations didn’t make sense at all for them to hold the ammo.
(3 resulting memory locations)
These values seem far too high – I do not have over 5000 bullets. After watching these values carefully while interacting with the game, it started to become apparent to me that the last location (0x091B75A0) encodes the ammo in only the first 3 bytes. Besides that, each shot decrements the value by 8. Overwriting those first 3 bytes with 128 caused the ammo in-game to jump to 16. Bingo! We have found our magic memory location.
Defending against memory scanning
While memory scanning, we found a strange encoding of ammo, in increments of 8. From a “security” or “anti-cheat” perspective, this encoding was very trivial to defeat, but it is not always so easy. Some games will have a secondary memory value to check if the first value has been tampered with. This secondary value can be a simple copy of the original, but sometimes a complicated formula is used to make this second value differ from the first. That makes it hard to find in scans while you can still compare the two values with the same formula, to make sure neither has been tampered with.
A reasonable defense would be to have seemingly random floating point values, or even strings, in a linked list. Rather than decreasing the value for each gunshot, you move to the next element in the linked list; sometimes shooting would cause the current number to go up, sometimes to go down. You can then use a dictionary to map this seemingly random float/string to the actual amount of ammo, to update the UI or to check if the gun is empty. This can of course also be defeated, but it would take a significant amount of time to find and understand the code responsible for this.
Finding and disassembling the code
The next step is to find out what code is decrementing the number. We can ask Cheat Engine to show us what code is writing to our memory address.
(attaching a debugger)
This will attach a debugger to the game, with a memory breakpoint on our memory location. After setting this up, we have to shoot again in-game, to trigger some part of the game’s code to decrement the value, and trigger our memory breakpoint.
(triggering a memory breakpoint)
We get another memory location out of this, and some assembly. This memory location is from inside the region of the game’s executable. It’s important to realize that this address is static: it will not change in between levels or restarting the game, unlike the ammo variable location. From here, clicking on “show disassembler” will have Cheat Engine disassemble the machine code into assembly for us:
(disassembly in cheat engine)
These are the assembly instructions that the game is executing. Might look scary at first, but if you take your time, it isn’t that hard to understand. The line where our breakpoint triggers is on a mov instruction. Mov moves a value from one place to the other. With that in mind, it becomes apparent that [edi+0x08] (which just boils down to a number, another memory location) contains the location where ammo is stored and the dx CPU register contains the newly decremented ammo value. Mov is simply copying the new value into the location where the old value is stored. In this case, that is really all we need to understand when looking at this code, we don’t care about the other instructions. For now we can write down the address bio4.exe+3091E3.
Fun fact: there are reverse engineering communities that completely reverse engineer the assembly of games back into human readable code. A notable example of one such community centers around zelda games: https://zelda64.dev/.
The hackity part
Now that we have our disassembled code, and we know what instruction we want to overwrite, it is time to get started with some C#.
There are a few ways that we can access another process, like DLL injection. From C#, we will take the simple path for now, and let the win32 API handle this part for us. For the uninitiated, the win32 API is an API that Windows itself provides. Any native windows application uses this API. That includes the C# standard library, though most of its use is hidden behind common objects, or indirectly handled by the runtime.
The functions we’re after reside in kernel32.dll. We want a function called “WriteProcessMemory”. This function accepts the handle of a process as a parameter, along with an address to write to, and an array of bytes to write to that address. As the name suggests, this function will write memory to the specified process.
We will need to do some P/invoking to ask Windows to execute this function for us, and we’re going to abstract most of the required parameters away in an extension method:
Now that we can write memory, we need to get a reference to the game’s running process (bio4.exe just happens to be the name of the executable). We need to replace the single 4 byte wide mov instruction with multiple nop (no operation, it does nothing) instructions, because nop is only 1 byte wide. The hexadecimal value of nop is 0x90. We can define an array of these, and write to 0x3091E3. We can’t write directly to 0x3091E3 – this address is relative to the location where the executable is loaded in memory. We can use the MainModule’s base address to calculate the absolute address to write to.
After running this program, I can shoot my gun infinitely, without my ammo ever decreasing!
This trick is not limited to changing game code, we can also overwrite memory values themselves. The game has an in-game shop where you can buy weapons and healing items for the “ptas” currency. We can scan for the value of ptas, and apply a similar trick. I ended up finding this address at offset 0x85F708. By just adding a couple more lines of code, we can give ourselves a lot of in-game money:
A conversion from a high integer value (high because we want lots of money) to bytes is needed here, BitConverter is perfect to deal with that problem – it will give us the binary representation of that number, which we can write back to the game’s memory. The effect of running this is immediately apparent in the game:
We can now spend to our hearts content!
Conclusion
With some educated guessing, trial and error, some very simple C# code and about an hour or two of time, we can mess around with games, give ourselves god-like abilities and get the “reverse engineer’s dopamine rush”.
Of course, these are cheap hacks to make the game very easy. It would be more interesting to create more meaningful modifications. To achieve that, we would have to use Ghidra and other tools to dig through the game’s code and find out how it’s systems work. For instance, most game engines use a proprietary format to store assets, like textures and sounds. By reverse engineering the code that loads these resources, we could make our own tools to read these proprietary formats, in order to alter them. This would allow us to change how the game looks, or load our own sounds.
Another possibility is function hooking. While technically possible from C#, this is easier from C/C++/Rust. Function hooking is when you load a chunk of code into a game process (usually via DLL injection). You then overwrite the first few bytes of a game function with a jump instruction, that jumps to your own code. You effectively “hijack” some of the games functionality. An example of this is hijacking a graphics related function, like a direct X function that renders the game. We could then, before rendering the game itself, use the hijacked graphics context to draw our own stuff on top of the game, before jumping back and letting the game render. This would allow use to draw our own menu/overlay inside the game!
(in-game GUI overlay hack in Sekiro: Shadows die twice)