Have I caught your attention? Obviously I did, you’re reading this right now. Have you ever wanted to cheat in a video game? I bet you have. Everyone wants to win in a video game. It would be weird not to. Well, I’m here to help you achieve that dream. All you need is a lot of patience and the willingness to throw yourself at stripped executables. Sounds easy enough, right?
Types of Cheats
In video games, there are two types of cheats.
External
External cheats are, well, external. An external cheat is a program that you run plainly, and it performs the cheating features on a game that is already running on your computer. It makes no attempts to inject itself into the game.
Internal
Internal cheats are the opposite (duh). These are DLLs which are injected into the game so that the game launches it to give the cheat full reign on the process. You’ve already done something similar, right anon?
To summarize: external = EXE, internal = DLL.
What
Until now, I have manually written the programs that you have reverse engineered. Now, it’s time to raw dog it. The luxury of video game cheating is that there are pretty much 0 constraints with what you can and can’t do to give yourself an unfair advantage. Perhaps the game you want to cheat on has a lives or score feature. An attack vector can be those 2 features. Maybe there’s movement involved, and you can give yourself super speed. As you run and play the game, you become aware of all of the possible points of attack that you can look for while reverse engineering. This isn’t too far from exploit development. So if that’s something you want to do as a career, video games can be a great starting point. Truth be told, that’s how I got started as well.
I found this fun game on GitHub. It’s pong. Now hack it.
Sounds slightly intimidating, but if you look back on previous posts, there isn’t much that is out of reach. The process is the same, it’s just a larger and more jumbled mess that you have to trudge through.
Methodology
Play the pong game to discover possible attack vectors and clues to aid in the reverse engineering process.
Reverse engineer the game and find points of memory that can be cheated on.
Write a Windows C++ executable that performs your desired functionality.
Requirements
Microsoft Visual Studio with the C++ workload.
A disassembler (IDA Free can be used if you compile the game with x64).
A debugger.
Patience.
Building the Game
Just clone the project and open Pong Game.vcxproj with Visual Studio. There’s no .sln file which is a bit weird but Visual Studio is smart enough to sort it out.
Next, you’ll want to build the game. I built it in x86 but if you want to use IDA Free, then you’ll probably want to do x64. The game fortunately builds fine for both architectures.
Obviously, most video games you come across aren’t going to provide the source code to it, so pretend that it doesn’t exist. Secondly, video games also aren’t going to provide you with a Program Debug Database (PDB) file either. Buuuut since this game was a bit of a headache to reverse, I will use the PDB to help reverse the plugin.
Wanna know a secret? Pretty much all of the AAA game title engines are mostly the same code, just repackaged.
Once the game is built (Ctrl+Shift+B in Visual Studio), you should be ready to go.
Dynamic Analysis
Since games obviously aren’t malicious, my usual methodology of property analysis → static analysis → dynamic analysis wouldn’t be as effective. We will know pretty much exactly how the game works just by running it, so let’s do that.
Launching the game presents a full screen “game mode select” screen. I picked single player. Perhaps we can make the computer move slow, or perhaps we can make our score ridiculously high.
Next is the actual gameplay. W and S move your paddle up and down and, impressively, the physics of the ball actually rely on the collision location and velocity of the paddle. Other attack vectors could be the size of the ball, the speed of the ball, and the physical contact of the ball with the paddle.
Alt+F4 will close the game.
Static Analysis
Next, toss that thing into IDA to get to work analyzing the assembly. If you have the PDB in the same directory as the game, it should be loaded automatically. You’ll have pseudocode that looks like this.
Fortunately, there are a ton of global variables. And a nice one we can look at is score. There’s one score variable for each player, so let’s assume that we are player 1.
If you double click it, you should jump to its declaration. Yes, I know it can just be patched, but let’s make it fun.
If you get memory address BFD578
and subtract it by the base of the program BE0000
, then you get 1D574
. So all you need to do in the cheat is to get the base address of the game and add 1D574
, then write whatever score you want to the memory location.
This is much easier than having to generate a signature and have to scan the program to find it. A lot of public game cheats just use simple offsets as well.
Let’s slap some code down real quick.
#include <Windows.h>
#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")
#pragma comment(lib, "User32.lib")
#include <iostream>
int main(int argc, char **argv)
{
HWND hWindow = FindWindowA(NULL, "Pong Game");
if (hWindow == NULL)
{
std::cout << "Could not find Pong Game.exe" << std::endl;
return 0;
}
DWORD dwID;
GetWindowThreadProcessId(hWindow, &dwID);
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwID);
if (hProc == NULL)
{
std::cout << "Could not open process" << std::endl;
return 0;
}
HMODULE hMod;
DWORD cbNeeded;
if (!EnumProcessModules(hProc, &hMod, sizeof(hMod), &cbNeeded))
{
std::cout << "Could not enum proc modules" << std::endl;
return 0;
}
MODULEINFO miInfo;
GetModuleInformation(hProc, hMod, &miInfo, sizeof(miInfo));
uintptr_t base = (uintptr_t)miInfo.lpBaseOfDll;
uint32_t *offs = (uint32_t *)(base + 0x1D574);
DWORD dwOld;
VirtualProtectEx(hProc, offs, sizeof(uint32_t), PAGE_EXECUTE_READWRITE, &dwOld);
uint32_t score = 1000;
WriteProcessMemory(hProc, offs, &score, sizeof(uint32_t), NULL);
VirtualProtectEx(hProc, offs, sizeof(uint32_t), dwOld, &dwOld);
return 0;
}
We build and run (while the game is running) to achieve this:
It worked, excellent. We have cheated in a video game. Let’s see what else we can cheat on.
Let’s see if we can make our paddle move faster (or slower). There’s player_1_ddp
and player_2_ddp
, but those aren’t global variables. However, we can see how they are used in the pseudocode.
It appears that these are incremented/decremented by 2000.0 if they aren’t controlled by the AI. So let’s make ourselves faster. We need to just change these 2000.0’s to something like 4000.0.
But wait. If we look at the disassembly that references these numbers, we’ll see something a bit special.
Our register is being incremented by a global variable. That’s a bit strange, don’t you think? If we jump to it, we can clearly see that this is 2000.0, but why did it get stuck into a the heap?
This is actually an optimization that Windows does. There are no immediate loads in SSE, therefore it takes fewer instructions to do float arithmetic with variables that already exist. This speeds up program execution by a few microseconds at the cost of slightly expanding the size of the heap.
This actually makes our job easier, so all we have to do is get BFAC74
and subtract it by BE0000
to get 1AC74
. We can just add this to our preexisting cheat.
offs = (uint32_t *)(base + 0x1AC74);
VirtualProtectEx(hProc, offs, sizeof(uint32_t), PAGE_EXECUTE_READWRITE, &dwOld);
float speed = 4000.0f;
WriteProcessMemory(hProc, offs, &speed, sizeof(float), NULL);
VirtualProtectEx(hProc, offs, sizeof(uint32_t), dwOld, &dwOld);
I can’t exactly share a video for something so trivial, so you’re going to have to take my word for it when I say that it worked.
Let’s try one more thing. Let’s make the ball really big. There’s a variable called ball_half_size
, so let’s see if we can make it super large.
IDA turns it into a hex representation, but if you click on it and go to Edit → Operand type → Number → Floating point, it’ll show up as 1.0.
Do the math again. It’s 1D324
. And we add it to our cheat.
offs = (uint32_t *)(base + 0x1D324);
VirtualProtectEx(hProc, offs, sizeof(uint32_t), PAGE_EXECUTE_READWRITE, &dwOld);
float size = 5.0f;
WriteProcessMemory(hProc, offs, &speed, sizeof(float), NULL);
VirtualProtectEx(hProc, offs, sizeof(uint32_t), dwOld, &dwOld);
I don’t think that that worked… or did it? Lol.
Anyways that concludes this week’s post. I hope you learned something new and fun. Try and see if there’s anything else you can cheat on!
Go!
-BowTiedCrawfish