Skip content
LRQA Cyber Labs

Hacking Games, Hacking Systems

Ian Chong Senior Security Consultant

I recently started poking around in game hacking — partly out of nostalgia, partly out of curiosity. You know, those old-school cheats we used to love: god mode, infinite mana, bottomless gold bags. I wanted to peel back the curtain and actually see how that magic worked. Not just clicking a trainer and watching my HP bar never drop, but really understanding what’s happening under the hood — and whether those same tricks line up with the stuff I read about in reverse engineering and Windows API abuse.

Messing with a few tools and writing my own code made one thing obvious: the skills you pick up from game hacking are transferrable to offensive security. DLL injection, memory patching, playing with Windows functions. The bonus? It’s a whole lot more fun than reading dry API docs line by line.
I have been gaming forever... from wandering Pallet Town with Pikachu to getting creeped out by Lavender Town’s soundtrack. These days I’ve been studying guides, watching videos, and hands-on hacking little RPGs just to make myself the richest guy in the starter town. Turns out, tweaking an in-game gold balance isn’t so different from injecting shellcode into a real target. Same technique, just different goals.

For this write-up, I’ll use Schedule 1, a Unity-based game, as the example. Unity games mainly run on two backends: Mono and IL2CPP. Knowing which one you’re dealing with matters, because it decides which tools and methods will work.

In Schedule 1 Demo, the game is built with Mono.

 

Step 1 - Find the cash memory address

First things first, the starting point of most game hacking - we want infinite money. We turn to Cheat Engine (CE) to pinpoint the memory address that's storing the value of the cash on hand we're targeting.

The value of the amount of cash we have has to live somewhere in memory, the first step is to find where that is.

Once Cheat Engine is attached to our game, we start a new memory scan with the exact value of the cash we currently have. We then change the amount of cash and run next scan with the new value, which will filter out any false positives in our list. Rinse and repeat until we get a reasonable amount of memory addresses that are likely to actually point to the correct bit of memory where our cash is stored. In this case, I managed to filter it down to two.

 

Once we’ve identified the correct memory address for cash, we can investigate which instructions are interacting with it by right-clicking and selecting “Find out what accesses this address

 

It shows up with three instructions, and we can then reduce the cash one more time to see which instruction it invoked. Turns out, there are lots of movss (move scalar single-precision) which shows they are moving floating-point number from the respective registers.

 

They're not telling us a lot at this point, but fear not, Cheat Engine has Mono integration built in which allows us to collect assemblies, namespaces, classes and methods loaded by the game.

 

After activating Cheat Engine's Mono Features, we try to read the memory again. This time round, CE maps the native addresses to the managed methods/classes/assemblies. Of particular interest we see the method "SetBalance" and “set_Balance”.

 

Searching for the class name “CashInstance” brought us to something, and inspecting its methods confirms that it does indeed contain “setBalance” and “set_Balance”.

 

To validate whether this is the right location, we can right click on the fully qualified method name of "ScheduleOne.ItemFramework.CashInstance" and find instances of the class. Many instances would pop up, but we must pinpoint to the one that's holding the value of our cash.

Luckily for us, we seem to get a hit on the first one. We can see our cash value in the property "k_BackingField".

 

We test it out by invoking the "set_Balance" method on the discovered instance and set the value 1000. 

 

Back at the memory address, it’s sitting at 1000 - so that method call is the one making the change. We can now change our money at will! 

 

Back in game, we got a thousand bucks! 

 

Step 2 - Create a Hack (The Long-Winded Way) 

So, we’ve manually created a one-time hack for infinite money, but the last thing we want to do every time we start the game is go through this whole process again. In fact, I don’t even want to start Cheat Engine at all.

So far, we know the game is Unity-Mono, we have identified the namespace, class, method and field of the function that would give us the very thing we want. I want to create a native DLL that would: 

  • Burrow into the Schedule 1 process.
  • Locate Mono and its exported functions.
  • Wait until the game finishes loading its assemblies.
  • Resolve the target class and methods by name.
  • Request Mono to JIT-compile those methods in order to obtain their native addresses.
  • Use MinHook to intercept the calls, redirecting execution first to my hook and then back to the original method.
  • Inside the hook, capture the CashInstance object and, on a chosen hotkey press, update its value. 

To do that we would need to the following: 

  • Find the method address for set_Balance using Mono's exported functions to resolve name with mono_class_from_name, mono_class_get_method_from_name, mono_compile_method  
    • Hook it with MinHook (A lightweight API hooking library) - https://github.com/TsudaKageyu/minhook 

We can find some form of Mono embedded APIs directly on GitHub  since I couldn’t access the updated Official API Documentation via the mono-project page.

 

Taking a lap through the code pool 

Time to swim through the C++ code for my DLL. 

 

This Includes & MinHook Linking

Declare your header files so the DLL knows what to use.

If MinHook isn’t compiled directly into our DLL, it must ride along as a separate MinHook.x64.dll sitting next to the game executable. That means the game has to go hunting for that extra DLL every time it boots up. Not exactly elegant. Much cleaner to just fold it in and bake MinHook right into our DLL.

To do that, we define MH_STATIC so it will link directly to the MinHook source files we include in our code directory.

 

I took buffer.c, hook.c, trampoline.c and hde64.c and place it in the same directory as my code. Next, I also added the MinHook include directory to our project.

 

Declaring the Mono API Blueprints

Before we can set off to hunt for the game’s classes and methods, our program needs a way to speak Mono’s native language. To facilitate that, Mono exposes a set of functions that external code (like our DLL) can call to interact with its runtime.

The following shows the declarations of the blueprints for Mono's API so that my DLL can speak to the embedded Mono runtime.

 

Declaring the Game Methods TypeDefs

Before we go into this, let’s get a better idea of how the language works. Unity games are primarily written in C#, and what do we know about C#?

  • It first compiles into something called Intermediate Language (IL), which normally runs under the Common Language Runtime (CLR) in .NET.
  • In Unity’s case, the runtime is Mono, an open-source implementation of the CLR. When the game runs, Mono’s Just-In-Time (JIT) compiler takes the wheel. The first time a method like CashInstance.set_Balance is called, the IL for that method is translated on the fly into native machine code that the CPU can execute.

Unity (Mono) has the game logic as IL inside "Assembly-CSharp.dll". Like our explanation above, when the method CashInstance.set_Balance is first used, the Mono JIT compiles it into native code in memory. Therefore, we're setting up the typedefs to call the JIT-compiled C# methods, and the globals/states (like the captured instance, the cash value, and them sizes and flags).

 

MinHook comes to the rescue

MinHook reroutes calls to the real methods into these hooked versions and then forwarded back to the original. During the “hook”, we retrieve the cash instance and the last value of the instance.

 

Perhaps a diagram would help visualise the behind-the-scenes.

 

Little Helper

Before loading the game’s DLLs and resolving the function exports, it helps to know where the game executable lives on disk. This here is a little Windows helper function for finding the directory of the game executable.

 

Loading in all the Mono Goodies

Before we can resolve any exports, we need to do some checks to ensure Mono runtime is present and active in the game’s memory. We’re sending out a bounty hunter to track down Mono and return home with a handle. 

First, it checks if mono-2.0-bdwgc.dll is already loaded. If not, it falls back to inspecting UnityPlayer.dll, since some games bundle Mono there. And if that still doesn’t work, it tries the well-known installation path.

 

Collecting all the Mono APIs

In this section (assuming that Mono has been found), this function will be trying to get the memory address of all those exported functions inside the Mono DLL which will be coming in handy for us later.

 

Make a Call back to the Game Assembly

Once we can talk to Mono, we need to find out where the game’s actual code lives.

Among all the assemblies roaming around, this code cherry-picks the one we care about - Assembly-CSharp.dll, the home where the game’s actual code and logic reside.

 

Further Loading...

Even though we’ve hooked into Mono, we can’t assume that the game has finished setting up everything by the time our DLL loads. If we are doing things faster than the game loads, we’ll most likely just mess up the game.

Therefore, the following functions just acts as guards making sure that all the game’s initialization has been done and are ready for us.

 

The Heart of our DLL

This is the thread that does all the setup (loads all the Mono function pointers, attaching our thread to Mono, find the class "ScheduleOne.ItemFramework.CashInstance", resolve the methods, JIT compiles them, create hooks, loop to top-up money) and then sits in a loop waiting for us to press 'Z'.

In conclusion:

  • Setup -> It resolves Mono and grab the relevant assembly, class and methods
  • Hook -> Utilise MinHook to install hooks to our own function
  • Hotkey -> Use the captured instance to add money when Z is pressed

 

Our DLL Entry

When our DLL is successfully loaded, this DLL entrypoint (DllMain) is called, and new thread is created inside the game process to run the worker function (the heart of our DLL).

 

Now to test out our hack, without any methods for DLL injection we will just try it out with the LoadAppInit_DLLs Windows Registry. What this does is that it will get our specified DLL loaded into every process that loads user32.dll (for testing purposes only obviously).

This can be found in Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows for Windows x64 architecture.

  • Modify the registry “AppInit_DLLs” with the full path to your DLL
  • Change the registry “LoadAppInit_DLLs” to 1

 

To verify that the DLL was successfully loaded, the hooks were executed, and the instance was captured, I added conditional message box pop-ups as clear indicators.

Starting the game gave me an indicator I've coded to show that the DLL has been loaded.

 

Once you've started the game, you would need to make some changes (like spending your cash) to grab the valid pointer to them. Once the game starts, the "CashInstance" object is stored in memory, but we can only receive the pointer to the object instance when it is called by the game, in this case by spending or some other means of reducing the money.

 

Press the ‘Z’ key twice to trigger the hack!

We already have our game hack DLL doing its magic, so now it’s time for the fun part – writing a DLL injector to slip it into the game the proper way, because honestly, LoadAppInit_DLLs is not too elegant.

 

The Rise of DLL Injectors

A DLL Injector:

 

Finding the Process

To start the injection, the injector first needs to locate the target process. Using CreateToolhelp32Snapshot, it takes a snapshot of all running processes. The injector then loops through this snapshot, checking each entry until it finds the process with the matching name.

 

Preparing for the Injection

There are a few things we do here.

  • With the PID found, we then get a handle to the target process using OpenProcess API with some almighty permissions (PROCESS_ALL_READ).
  • Allocates memory inside the target process and make it big enough to hold the DLL path string using VirtualAllocEx API.
  • Finally, we can write the DLL path into that remote memory using WriteProcessMemory API.
  • Forcing LoadLibraryW since this is the Windows API that loads DLLs.
  • Then, the critical step – starting a brand-new thread inside the target process which executes LoadLibraryW.
  • Finally, some clean up to make sure we close off elegantly.

 

The Driver

Here we declare all the values needed for the APIs to work its magic. 

 

Run the injector and we see that it has been successfully loaded.

 

DLL Hijacking

We have seen how DLL Injection works, now let's investigate DLL Hijacking. First off, let's get our understanding right. 

DLL Injection 

  • What it is: Forcing a running process to load our DLL at runtime
  • How it works: Poke the memory using CreateRemoteThread and all the other pretty Windows API
  • Here we are targeting a live process, making it load something it didn't plan to load.

DLL Hijacking

  • What it is: Tricking an application to load our malicious DLL instead of the real one, usually at process start up.
  • How it works: Many Windows apps look for DLLs in a set order (DLL search order), if the app calls LoadLibrary("haha.dll") without a full path, Windows searches directories in a specific order to find it.
  • We don't need to inject it into a running process, and we let the vulnerable program load the DLL on startup.

But first, we need to find what library the game is trying to load but doesn't exist, so it turns to DLL search order.

Here we can use ProcMon from Sysinternals Suite to cleanly find the gold with the following filters:

  1. (optional if you like to scroll) Process Name is "Schedule I Free Sample.exe"
  2. Result is "NAME NOT FOUND"
  3. Path ends with ".dll"

 

Scrolling through the results we quickly see a DLL called "NVUnityPlugin.DLL" that goes down the DLL search order:

 

There are many other DLLs that weren't found, but only this one triggers the search order which means more attack surface!

We renamed our DLL to NVUnityPlugin.dll and put it nicely in the game directory.

 

Run the game, and voila! 

 

DLL Sideloading

Now that we've covered DLL Hijacking, we can talk a little about DLL Sideloading.

They are relatively similar, except that this is a technique typically used to be stealthier and more op-sec safe. You would use a trusted, signed, legitimate executable to load the DLL – well-loved by attackers because the detection tools often trust Microsoft signed binaries and so the DLL sneaks past.

Let's demonstrate this with Notepad++. I have downloaded the latest portable version which is version 8.8.5.

 

Put the DLL called MSASN1.dll bundled with the portable; it's just a simple DLL that pops a message box with "DLL Loaded" once it has loaded successfully.

 

Running the executable shows that the DLL was loaded. This demonstration uses a simple MessageBox payload to show how the same fundamental skills you learn from game hacking can be transferrable into security tradecraft.

 

Closing Thoughts: Hacking Games, Hacking Systems

What began as a simple game hack such as writing a DLL to change in-game values and expanded into building a custom DLL injector and, later, demonstrating DLL sideloading.

Each step might feel like just a fun experiment, but they’re all grounded in the same Windows internals: API calls like OpenProcess, VirtualAllocEx, WriteProcessMemory, and LoadLibrary that are just as useful to a malware author or red team operator as they are to someone tinkering with games.

The message box DLL we used was deliberately harmless and visible, but the underlying techniques could just as easily support stealthy payload droppers, persistence mechanisms, or offensive security tradecraft.

I'm writing this article to show how game hacking can be a fun and accessible path into understanding process manipulation, memory injection, and how attackers really abuse Windows APIs in the field.

Latest news, insights and upcoming events