So recently I’ve been re-motivated to do some more game hacking and I thought that I’d do another article to introduce more aspects on top of what I’ve already shown from my previous ones. In the following document, I’ll be detailing the usage of DLL injection and its advantages, a fundamental concept of how games may store specific data and how to identify them. Along the way, I will be talking about the ASLR environment and how to adapt code to such scenarios. Finally, I will be discussing how to code a trainer in the form of a DLL.
The game that we will be using to demonstrate these ideas is Hack, Slash, Loot which has a demo version that you can download if you wish to follow along.
Author Assigned Level: Newbie
Community Assigned Level:
- Newbie
- Wannabe
- Hacker
- Wizard
- Guru
0 voters
Assumed Knowledge
- Windows API
- C++
- x86 Intel Assembly
- Basic Cheat Engine usage
- Virtual memory
- DLL injection
- Data Execution Prevention (DEP)
Expected Outcomes
- Understand the advantages of using DLL injection over external process data manipulation
- Understand the basic concept of data structures in games
- Learn how to adapt code in an ASLR environment
- Learn how to modify assembly inline
- Learn how to program a basic trainer in C++
Disclaimer
This information is based on the information that I have researched myself and is therefore subject to the possibility of providing incorrect data. If such incorrect data exists, please notify me and I will fix it when I am available.
DLL Injection
I’ve covered DLL injection in previous articles but just for completeness, I will give a brief overview of it. DLL injection is simply the act of forcing a process to load a DLL. There are many different ways to perform this but the most common and easiest method is to call LoadLibrary
from within the target process and passing the full path of the DLL as a parameter. Here is some sample code that represents the procedure.
// Get size of the full DLL path
DWORD dwDllPathLen = strlen(lpszDllPath);
// Allocate space for the DLL string
LPVOID lpDllPathString = VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// Write the DLL string into the allocated space
SIZE_T dwWritten = 0;
WriteProcessMemory(hProcess, lpBaseAddress, lpszDllPath, dwDllPathLen, &dwWritten);
// Get handle to the module which has LoadLibrary
HMODULE hModule = GetModuleHandle("kernel32.dll");
// Get address of LoadLibrary
LPVOID lpLoadLibrary = GetProcAddress(hModule, "LoadLibraryA");
// Execute LoadLibrary with the DLL string as the parameter
CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpLoadLibrary, lpDllPathString, 0, NULL);
Space will first be allocated for the full path of the DLL in the target process as a parameter for LoadLibrary
. Once space has been allocated, the path string will be written using WriteProcessMemory
. The LoadLibrary
address will be retrieved using GetModuleHandle
with the kernel32.dll
parameter and GetProcAddress
. Now that we have LoadLibrary
and the DLL path string, we simply execute a thread in the target process at LoadLibrary
using CreateRemoteThread
, passing the DLL path string as a parameter.
In summary, we are forcing the target process to execute
LoadLibrary("DLL's full path string");
DLL Injection vs External Process Data Manipulation
So just some motivation, we want to be able to modify data within a process to be able to force it to do what we want. This can be done in two ways, the first I call external process data manipulation, I’ve already covered in previous game hacking articles. Essentially, it uses the ReadProcessMemory
and WriteProcessMemory
functions to query and change data given a specified process handle. But constantly calling these functions is a bit awkward and wouldn’t it be a bit better if we could just modify it by simply assigning it like a normal variable, i.e. player.health = 9999
?
DLL injection offers the solution to this problem. In the context of an injected DLL, the target process’s data in memory is treated like normal meaning that we can read and write data by reading an address and then assigning it a value using the assignment operator.
int *health = (int *)ADDR_HEALTH_VALUE;
*health = 9999;
So obviously, it is more favourable to be in this situation purely due to its simplicity.
Finding Values
Since basic use of Cheat Engine is assumed knowledge, we will skip the setup and dive straight into scanning for values of interest. So boot up the game and CE and let’s first find the address of the health. Here I’ve found mine and have modified it to confirm that it’s correct.
Cool. So I can change my health to whatever I want but that’s easy and not very exciting. Let’s go a bit further and identify the underlying assembly which changes our health value by right-clicking the address and then selecting Find out what writes to this address
. It will ask if you want to attach a debugger so just confirm that you want to and then it will open up a window. Simply trigger your health value so that CE can detect the instruction(s). When it shows up, select it and click Show disassembler
on the right.
Identifying Data Structures
Immediately we can deduce that the sub
instruction is where the program decreases our health value where eax
holds the value of the amount and directly after it, there is a cmp
instruction with 0 which is very likely where it checks if we are dead. Note down the address of the sub
instruction as we will be using it later in our trainer. What’s interesting to note is that the health value is obtained with an offset from ebx
because that is exactly what it would look like if it was referencing some sort of data structure like an array or a struct. To examine what ebx
is, let’s place a breakpoint (F5) and then re-trigger our health.
As the breakpoint is hit, it will show us the value of ebx
. Let’s use CE to examine this data structure by going to the Tools
menu of the disassembler, then selecting Dissect data/structures
. Plug in the value of ebx
in the edit box labelled Group 1
then go to the Structures
menu and select Define new structure
. Leave everything as default and let CE do all the heavy lifting and it will present a list of values at different offsets.
Notice that at offset 50, we can see our health. But what are all these other values? Let’s head back to the game and see if we can find out. Actually, there is a sort of slight matching of values.
Let’s confirm it by modifying the values in the struct and see if it is reflected in the game…
Neat stuff! We can spend hours trying to decipher the structure members but I will leave that up to you if you wish to do it.
The final task that we need to do before moving onto coding the trainer is to know where we can find the address of the player struct. Luckily, this is easy for us to do. Return back to the main CE window and scan for the address of the player struct (make sure the hex checkbox is ticked) and we will be given an address in green text. This means that this is a static address and will never change.
Programming a Trainer
Dealing with ASLR
Before we start coding the trainer, I’d like to bring up the concept of Address Space Layout Randomisation (ASLR). ASLR is a technique used by the operating system as a security measure to counteract exploitation methods like buffer overflows. The idea is to have the process’s addresses randomised so that it would be impossible to tell where certain data existed.
What’s relevant to game hacking is the randomisation of the process’s base address and it needs to be understood so that it’s possible to adapt our code to such a volatile environment. Do not worry, the solution is pretty simple, at least for what we are doing. All we need is the offset of a certain address and then dynamically calculate the address in accordance to the base address. How do we get the offset of a target address? Simply subtract the base address of the process from it: offset = targetAddress - baseAddress
. How do we dynamically calculate the new address in an ASLR’d process? targetAddress = getBaseAddress() + offset
. Easy!
We can see here that the process was loaded at address 0x400000
which means that we can calculate the offsets of certain addresses of interests, namely the address which has the instruction to subtract our health (0x547D6E
) and the address which holds the address of the player struct (0xBA6140
).
Programming the Trainer
We’ll start off by creating a Trainer
class where we will define the player struct and the methods we want. I’ve already gone ahead and deciphered some other members of the struct.
Trainer.h
#ifndef __TRAINER_H__
#define __TRAINER_H__
#define ADDR_OFFSET_PLAYER_STRUCT 0x7A6140
#define ADDR_OFFSET_SUBTRACT_HEALTH 0x147D6E
typedef struct _player {
void *unknown1;
int unknown2;
void *unknown3;
void *unknown4;
int unknown5;
int unknown6;
int unknown7;
int unknown8;
int unknown9;
void *unknown10;
int unknown11;
unsigned int faceDirection; // 1 (right) or 0xFFFFFFFF (left)
int unknown12;
int baseMeleeAttack; // accuracy
int baseRangedAttack;
int baseMagicAttack;
int baseArmour;
int baseMeleeDamage;
int baseRangedDamage;
int baseMagicDamage;
int health;
int resistance;
int vulnerability;
int unknown14;
} player;
class Trainer {
private:
unsigned int baseAddress;
player *p;
bool isGodmode;
static Trainer *t_instance; // Singleton; ignore
Trainer();
~Trainer();
Trainer(Trainer& other) {} // Singleton; ignore
Trainer& operator=(Trainer& other) {} // Singleton; ignore
public:
static const int MELEE = 0;
static const int RANGED = 1;
static const int MAGIC = 2;
static Trainer *instance(); // Singleton; ignore
void Reset();
void ModifyHealth(int health);
void EnableGodmode(bool enable);
void ModifyArmour(int armour);
void ModifyAttack(int type, int attack);
void ModifyDamage(int type, int damage);
bool IsGodmode();
};
#endif // !__TRAINER_H__
I’ve defined this class as a Singleton meaning that it will only allow a single instance of this player class to exist at any given time since this is a single-player game. This is an optional trait so ignore this if it complicates things.
When defining the player struct, it is important that we replicate the members exactly in terms of order and data type however, it is possible to truncate it if we do not know any other further details. At the top, I’ve defined some offset values for the location of the player struct and the instruction that subtracts health. Do you understand how I calculated those values?
Let’s now define the methods.
#include <Windows.h>
#include "Trainer.h"
// initialise trainer instance
Trainer *Trainer::t_instance = 0; // Singleton; ignore
/*
* Initialise the base address and obtain the
* address of the player struct. Default disable
* godmode.
*
* Note that we must start a new game before
* applying any h4x otherwise the address will
* not contain a valid player struct.
*/
Trainer::Trainer() {
// player struct at 00BA6140
this->baseAddress = reinterpret_cast<unsigned int>(::GetModuleHandle(TEXT("HackSlashLoot.exe")));
this->p = reinterpret_cast<player *>(*reinterpret_cast<unsigned int *>(this->baseAddress + ADDR_OFFSET_PLAYER_STRUCT));
this->isGodmode = false;
}
Trainer::~Trainer() {
}
/*
* Return the existing instance of the Trainer
*/
Trainer * Trainer::instance() {
if (!t_instance)
t_instance = new Trainer();
return t_instance;
}
/*
* In case we retrieve the player struct
* before starting a new game.
*/
void Trainer::Reset() {
this->p = reinterpret_cast<player *>(*reinterpret_cast<unsigned int *>(this->baseAddress + ADDR_OFFSET_PLAYER_STRUCT));
}
/*
* Change the health of the player.
* Param : new health
*/
void Trainer::ModifyHealth(int health) {
this->p->health = health;
}
/*
* Change the player's base armour.
* Param : new armour
*/
void Trainer::ModifyArmour(int armour) {
this->p->baseArmour = armour;
}
/*
* Change the attack (accuracy) value.
* Param : type of attack
* Param : new attack value
*/
void Trainer::ModifyAttack(int type, int attack) {
if (type == Trainer::MELEE)
this->p->baseMeleeAttack = attack;
else if (type == Trainer::RANGED)
this->p->baseRangedAttack = attack;
else if (type == Trainer::MAGIC)
this->p->baseMagicAttack = attack;
}
/*
* Change the damage value.
* Param : type of damage
* Param : new damage value
*/
void Trainer::ModifyDamage(int type, int damage) {
if (type == Trainer::MELEE)
this->p->baseMeleeDamage = damage;
else if (type == Trainer::RANGED)
this->p->baseRangedDamage = damage;
else if (type == Trainer::MAGIC)
this->p->baseMagicDamage = damage;
}
/*
* Enables or disables godmode.
* Param : true to enable, false to disable
*/
void Trainer::EnableGodmode(bool enable) {
this->isGodmode = enable;
void *takeDamageAddr = reinterpret_cast<void *>(this->baseAddress + ADDR_OFFSET_SUBTRACT_HEALTH);
DWORD flOldProtect = 0;
::VirtualProtect(takeDamageAddr, 3, PAGE_EXECUTE_READWRITE, &flOldProtect);
if (this->isGodmode) {
*reinterpret_cast<unsigned char *>(takeDamageAddr) = 0x90;
*(reinterpret_cast<unsigned char *>(takeDamageAddr) + 1) = 0x90;
*(reinterpret_cast<unsigned char *>(takeDamageAddr) + 2) = 0x90;
} else {
*reinterpret_cast<unsigned char *>(takeDamageAddr) = 0x29;
*(reinterpret_cast<unsigned char *>(takeDamageAddr) + 1) = 0x43;
*(reinterpret_cast<unsigned char *>(takeDamageAddr) + 2) = 0x50;
}
::VirtualProtect(takeDamageAddr, 3, flOldProtect, &flOldProtect);
}
/*
* Returns true if godmode is enabled, otherwise false.
*/
bool Trainer::IsGodmode() {
return this->isGodmode;
}
Most of these methods are self-explanatory and you can see that they modify values using the assignment operator because the DLL will be under the same context as the game’s process.
The first important thing I want to cover is the baseAddress
field which is initialised in Trainer
's constructor. Using GetModuleHandle
with the string of the process’s executable module, we can dynamically retrieve the base address which we can then use to dynamically calculate specific addresses. Directly underneath, the line of code utilises the baseAddress
field is to calculate the address that stores the player struct.
The second important thing is the EnableGodmode
method. Note how I’ve used VirtualProtect
to allow write access of the address where the instruction to subtract health exists. This is due to DEP which I’ve covered previously. To apply godmode, we can just nop
the subtraction and then we can restore the original bytes to disable it.
Coding the DLL
The DLL is not much different than coding a standard executable. Instead of using main
or WinMain
, we use the DllMain
entry point.
BOOL APIENTRY DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpReserved) {
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
::CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(StartInterface), reinterpret_cast<LPVOID>(hInstDll), 0, NULL);
break;
}
return TRUE;
}
The only major difference here is the fdwReason
parameter which defines the circumstances under which the entry point is called. There are four of them but we will only focus on when the DLL is initially attached to the target process, AKA DLL_PROCESS_ATTACH
. Once the DLL has been injected, it will call DllMain
with the DLL_PROCESS_ATTACH
reason and execute our code. Since I wish to persist the interface to allow the user to continuously access the trainer, I must use the CreateThread
function to start another function to handle it, otherwise the main application will hang. Finally, we must return TRUE
else the DLL will be detached from the process as a result. I will leave the interface for you to design; it may be a console or graphical interface, or perhaps you could set global hotkeys or hook the keyboard. The choice is yours.
Injecting the DLL
Thankfully, we do not need to download any DLL injectors because CE comes with one! To access it, open the memory viewer (or disassembler/debugger), then go to the Tools
menu and select Inject DLL
, then proceed to locate your DLL and press Open
in the dialog. Here is what mine looks like:
Conclusions
So what have we (hopefully) learned?
- DLL injection has a significant advantage over the usage of
ReadProcessMemory
andWriteProcessMemory
because of the simplicity it provides - We’ve also identified a member of a data structure which we then looked further to locate the entire struct and then played around with to see what would be reflected in the game, then translated this structure into our own trainer so that we can change them on demand
- Understanding how to dynamically calculate addresses using offsets and the process’s base address will enable us to handle ASLR environments
- Modifying assembly inline and how to bypass DEP by using
VirtualProtect
As usual, you can find my code on the GitLab or GitHub.
Thanks for the read!
– dtm