Bloog Bot

Chapter 4
Drew Kestell, 2024
drew.kestell@gmail.com

Hacking In Progress

Our Player is constantly losing health, and has no way to regain it, so he'll eventually die. Let's build a simple hack to fix that. I'll be using a console application written in C#, targeting .NET Framework v4.6.1. In the previous section we found the static pointer that will allow us to consistently find the memory address where the Player's health is stored. To start, let's build a hack that lets us peer into the BloogsQuest process and read the Player's health from that process's memory. To help us, we'll import two functions from kernel32.dll, part of the Windows library. Here's the code:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    const int PROCESS_VM_READ =        0x10;
    const int GAME_POINTER_OFFSET = 0x1C2D0;
    const int PLAYER_HEALTH_OFFSET =    0x4;

    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(
        int dwDesiredAccess,
        bool bInheritHandle,
        int dwProcessId);

    [DllImport("kernel32.dll")]
    public static extern bool ReadProcessMemory(
        int hProcess,
        int lpBaseAddress,
        byte[] lpBuffer,
        int dwSize,
        ref int lpNumberOfBytesRead);

    static void Main(string[] args)
    {
        var process = Process.GetProcessesByName("BloogsQuest")[0];
        var processHandle = OpenProcess(PROCESS_VM_READ, false, process.Id);

        var bytesRead = 0;
        var buffer = new byte[4];

        var gamePointerAddress = process.MainModule.BaseAddress + GAME_POINTER_OFFSET;

        ReadProcessMemory((int)processHandle, (int)gamePointerAddress, buffer, buffer.Length, ref bytesRead);
        var playerPointerAddress = BitConverter.ToInt32(buffer, 0);

        ReadProcessMemory((int)processHandle, playerPointerAddress, buffer, buffer.Length, ref bytesRead);
        var playerAddress = BitConverter.ToInt32(buffer, 0);

        ReadProcessMemory((int)processHandle, playerAddress + 4, buffer, buffer.Length, ref bytesRead);
        var playerHealth = BitConverter.ToInt32(buffer, 0);

        Console.WriteLine($"Player Health: {playerHealth}");
        Console.ReadLine();
    }
}

We have three constants at the top of the file. The first is a flag that we'll pass to the OpenProcess function that signifies we want permission to read from the process's memory. The second is the relative memory offset from the base address of the process where the pointer to the Game object is stored (this is what we found in the previous section). The third is the offset from the Player object where we can find the health field.

Next we import two external functions from kernel32.dll using the DllImport annotation. This annotation is found in the System.Runtime.InteropServices namespace. DllImport paired with extern allows us to call into unmanaged code from our managed C# application. For more information on how this works, check out the C# language reference. The first method OpenProcess returns a process handle that we'll pass into the second method ReadProcessMemory. This is what we'll use to read the memory from BloogsQuest's process.

In the Main method, first we call Process.GetProcessByName() to get a reference to the BloogsQuest process. Then we pass the Id of that process into the OpenProcess method which returns a handle to the process. The ReadProcessMemory method expects us to pass in two more parameters - the first is an int that ReadProcessMemory will set equal to the amount of bytes that were read. The second is a byte[] that will contain the bytes that were read from memory.

Next we calculate the actual memory address of the pointer to the Game object. If you recall from the previous section, we know the Game pointer will always be offset 0x1C2D0 from the base address of the BloogsQuest process, so we can simply add the base address to our offset.

Now we call ReadProcessMemory, specifying the address we want to read from, and how many bytes to read. After this method is called, our byte array will contain the values that were read from the BloogsQuest process. The first call to ReadProcessMemory will return the address of our Player pointer. To convert a byte array into an integer, we call BitConverter.ToIn32. This allows us to parse the pointer from our byte array.

Once we have the address of the Player pointer, we call ReadProcessMemory again, passing in that address, which will return the address of the Player object. Finally, we add our health offset of 4 to the Player's address, and pass that in to ReadProcessMemory one more time, which returns the Player's health value. To confirm this, first start the game, then run the hack. You should see 100 written to the console:

Just reading the health value isn't very useful, so how do we modify it? To do that, we need to import one additional function from kernel32.dll, WriteProcessMemory. See the modified code below:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    const int PROCESS_VM_WRITE =       0x20;
    const int PROCESS_VM_OPERATION =    0x8;
    const int PROCESS_VM_READ =        0x10;
    const int GAME_POINTER_OFFSET = 0x1C2D0;
    const int PLAYER_HEALTH_OFFSET =    0x4;

    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(
        int dwDesiredAccess,
        bool bInheritHandle,
        int dwProcessId);

    [DllImport("kernel32.dll")]
    public static extern bool ReadProcessMemory(
        int hProcess,
        int lpBaseAddress,
        byte[] lpBuffer,
        int dwSize,
        ref int lpNumberOfBytesRead);

    // new function import
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool WriteProcessMemory(
        int hProcess,
        int lpBaseAddress,
        byte[] lpBuffer,
        int dwSize,
        ref int lpNumberOfBytesWritten);

    static void Main(string[] args)
    {
        // we need two additional flags that allow us to write the process's memory
        var permissions = PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION;
        var process = Process.GetProcessesByName("BloogsQuest")[0];
        var processHandle = OpenProcess(permissions, false, process.Id);

        var bytes = 0;
        var buffer = new byte[4];

        var gamePointerAddress = process.MainModule.BaseAddress + GAME_POINTER_OFFSET;

        ReadProcessMemory((int)processHandle, (int)gamePointerAddress, buffer, buffer.Length, ref bytes);
        var playerPointerAddress = BitConverter.ToInt32(buffer, 0);

        ReadProcessMemory((int)processHandle, playerPointerAddress, buffer, buffer.Length, ref bytes);
        var playerAddress = BitConverter.ToInt32(buffer, 0);

        var playerHealthAddress = playerAddress + 4;
        ReadProcessMemory((int)processHandle, playerHealthAddress, buffer, buffer.Length, ref bytes);
        var playerHealth = BitConverter.ToInt32(buffer, 0);

        Console.WriteLine($"Player Health: {playerHealth}");

        // prompt user to enter new health value
        Console.Write("What should we set the Player's health to?: ");
        var newHealthValue = Convert.ToInt32(Console.ReadLine());

        // convert int to byte array
        buffer = BitConverter.GetBytes(newHealthValue);

        // overwrite Player's current health value with new value
        WriteProcessMemory((int)processHandle, playerHealthAddress, buffer, buffer.Length, ref bytes);

        // confirm that the bytes were written
        ReadProcessMemory((int)processHandle, playerHealthAddress, buffer, buffer.Length, ref bytes);
        Console.WriteLine($"\nPlayer's health is now: {BitConverter.ToInt32(buffer, 0)}");

        Console.ReadLine();
    }
}

First we need to specify two additional flags that we pass into OpenProcess which gives us permission to write to that process's memory. The rest of the code should be fairly self explanatory. Notice that we need to convert the integer read from the console to a byte array, and pass that into WriteProcessMemory. To test this, first start BloogsQuest, then start the hack:

Now enter a new health value for the Player, and hit spacebar in the game to confirm that the Player's health was overwritten:

Many hacks for single player games work this way. Unfortunately this isn't going to cut it for a multiplayer game like WoW. WoW is played online, and your client's data is synchronized with the server. So overwriting your Player's health in the client won't do you any good - as soon as your client gets a new packet from the server, your Player's health will be overwritten by the value from the server. The server in this case is the single source of truth, and we have no way of modifying the data on the server. Nonetheless, this reading and writing of memory is still an important tool in your toolkit, and even for a game like WoW it can prove useful in a number of situations. You may not be able to modify some values in the client, but you can still use them to influence the decision making of your bot.

Hacks that directly modify your Player's statistics (making their attacks do a million damage, making them unkillable, etc) are a bit heavy handed. Even if they did work, it wouldn't take long before somebody noticed you were cheating and reported you to the GMs who would inevitably ban your account. The type of hack that's more common in a game like WoW is a bot. One of the main goals of WoW is to level up your character, and only once you get to max level do you gain access to the end game content that most people find appealing. But leveling up your character takes a significant amount of time. So a bot can play the game for you, fighting monsters without your intervention and leveling up your character. How do we go about building something like this?

What we really want to be able to do is call functions in the WoW client from our bot. Reading memory is one thing - it allows us to gain information about the current state of the game, and react accordingly. But to actually act (find and target monsters, cast spells, interact with shopkeepers, etc) we need to be able to call functions in the WoW client that trigger those behaviors.

Up to this point our hack has been "out-of-process". BloogBot runs in its own process and interacts with the BloogsQuest process with the ReadProcessMemory and WriteProcessMemory functions. In order to call functions in the BloogsQuest game, we need to be "in-process". And to achieve that, we're going to inject our bot into the BloogsQuest process using a technique called DLL injection. Note - it is possible for one process to call functions in another process using a variety of techniques (anonymous or named pipes for example), but the client needs to be written in such a way that it can receive commands from the server. It's highly unlikely the game client you'll be working with will be set up in this way; WoW certainly isn't. So the more straightforward approach is to work within the game's process.

As an interesting aside, WoW does provide an API for custom addons written in Lua to interact with a subset of functions in the game client. Therefore it is possible to build an out-of-process bot for WoW, however you're more limited in what you can accomplish. Blizzard has slowly removed certain functions from their Lua API to prevent abuse. So we're going to be working in-process.

Back to Top
...
Subscribe to the RSS feed to keep up with new chapters as they're released