Game Hacking: Hack, Slash, Loot

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++


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. = 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.


#ifndef __TRAINER_H__
#define __TRAINER_H__


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 {
	unsigned int baseAddress;
	player *p;
	bool isGodmode;
	static Trainer *t_instance;    // Singleton; ignore

	Trainer(Trainer& other) {}    // Singleton; ignore
	Trainer& operator=(Trainer& other) {}    // Singleton; ignore

	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) {
			::CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(StartInterface), reinterpret_cast<LPVOID>(hInstDll), 0, NULL);

	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:


So what have we (hopefully) learned?

  • DLL injection has a significant advantage over the usage of ReadProcessMemory and WriteProcessMemory 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!



Did you use a special library for the interface you made? Or just win32?

There’s a typo between Trainer.cpp and Trainer.h. Rename variable in _player struct in Trainer.h from baseArmour to baseArmor. I was going to make pull req. to fix, but idk what this thing is.

edit: looks like this was fixed, if not, errors read through IDE should make it easy to fix


0x00sec’s gitlab go to init and you can navigate to the gitlab and setup an account

This is pure Win32, I used the resource editor in Visual Studio for the GUI. If you’re talking about the different looking “skin”, it’s a theme I use.

@jtara Thanks for spotting that!

I couldn’t get this to work.

Why is the base address of the process of the game consistently 0x400000 ?

Just to test my offsets I calculated, I should be able to dissect data/structures using the address (baseAddress + offset) right? Well, this isn’t giving me the expected player struct.

The developer may not have enabled dynamic base addressing when compiling it.

Did you leave all the values as default? It should look something like this:


Thanks, I got it working with

void StartInterface(LPVOID lpParam) {
	Trainer::instance()->ModifyAttack(0, 999);
	Trainer::instance()->ModifyDamage(0, 123);
  1. I had to figure out which static address was needed for player struct and health as there were several for each. I attached a debugger to find out what writes to this other address whose value is health (usually) which changed the value of the incorrect static addresses.

  2. Got the correct offsets, then had to clean, rebuild and compile the solution / project in Visual Studio to get the dll to inject.

1 Like

Loving the article, it will help me refresh myself on cheat engine :slight_smile: - but just a tip, if possible please use PNG images for screenshots rather than JPEG. Because PNG uses a lossless compression, the images stay clean and crisp around text and sharp pixel-perfect details; unlike a lossy compression such as JPEG. For screenshots especially PNG will typically result in smaller file sizes too!

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.