Writing a Bot for Multiplayer Games with Code Injection

How does Honorbuddy work? How are cheats and/or bots written? This article is an introduction on how to reverse engineer a game and where to look after variables and pointers. I guide through a simple example in an MMORPG running on MacOS, where we will make a player move automatically. As main injection library, OsxInj is used.

Prerequisites

  • Basic knowledge of C and C++
  • Basic knowledge of how pointers work
  • A little knowledge in assembler

Software

If you are simply interested in how cheats or bots work than you can continue to the next section.

The practical section for injection is written for MacOS. But you could also use Windows or (better) Linux, but then you need to find other methods (or libraries) which help you in in the task.

MacOS/Linux

Windows

Considerations

Basically, there are two methods for writing software which automatically operates another (GUI) software.

Screen recognition…

… is one of the simplest methods, but nevertheless very elaborate. It works by reading the screen and recognizing specific elements, like images of spells or names of NPCs. When the bot wants to do something it looks for specific patterns on the screen, then it simply moves the mouse and clicks at a point where the desired action happens.

Of course, writing such a bot is a lot of work:

  • Making many screenshots and cutting out images of spells
  • Writing macros for mouse movement and keyboard presses (e.g. open inventory with I, drag and drop something)
  • Modifying the settings of the game-UI to suit macros

Also the main disadvantages:

  • The game has to be kept in the foreground, as keyboard and mouse are used
  • The bot is rather dumb. Often it can’t react to unexpected events (e.g. a window popping up). Also, it is hard to write smart movement without a lot of information. When moving (clicking in one direction) there could be obstacles, which are hard to recognize.

Because those mentioned disadvantages and because writing such a bot is kind of boring, it is not discussed here.

Memory manipulation

We can get information about the state of the game by simply reading out memory. Useful information could be health points, the position of the player, number and position of enemies.

Reading memory is normally safe from detection software on all operating systems. However, the game could of course search for suspicious process names.

Finding variables

The process for finding a variable is really easy.
Simple example – finding the player health:

  1. Start the game and your memory analyzing software (like cheat engine)
  2. Look for the value of the variable you want to find in-game. (So look for your health bar, and memorize your health)
  3. Enter the value into the software and click Scan. You will see a lot of results which need to be filtered out
  4. Change the value ingame (e.g. by fighting mobs and reducing your HP)
  5. Enter the changed value in the software and click rescan
  6. Repeat step 4 and 5 until the number of found variables won’t reduce

You can now walk around or fight and see how the variable updates live in the software. Note that the address of the variable will change after a restart of the game. For permanency, you would have to find the base pointer and add an offset to it (not discussed here).

Writing of memory

Reading memory will give us some information, which may be helpful while playing. But it won’t let us control anything, so we need some way to modify the state of the game.

A simple way of experimenting is experimenting with the found variables with CheatEngine or BitSlicer. Changing a variable of the position of the player should also change the location where your character is. Whether it is successful depends on whether the game is multiplayer and whether it calculates the position server or client side (e.g. on EVE Online nearly every calculation is done on the servers, so there is no way in simply modifying memory) and of course whether you found the correct variable.

MMO servers usually check for memory manipulation. (Wouldn’t it be weird when a character simply moves, without doing something?). So let’s look further.

Often games have the “click-to-move” functionality (Diablo, WoW…). You click somewhere in the game world, and the character automatically moves to it.
You could try to find the associated variables for this action, by clicking on a point where you know its coordinates, then scan for it. Then watch if it changed if you click somewhere else. Changing the variable while you are moving while under the state “click-to-move” should change your destination.

Click to Move: The player walks to the position where the mouse was clicked
Game logic: Clicking into the world switches a variable. As long as it’s true, the player moves to the target.

Triggering the click-to-move by simple memory writing is hard, you would have to find a state variable which switches the players’ movement from standing to following the click-to-move target.

Also writing memory is simple to detect, as the game could have shadow variables which periodically check the consistency of the original variable.

Code injection

A much better method with broader capabilities is code injection. Of course, you could simply change the game binary directly, but that would make detection much easier and keeping up with games updates is hard.

Injection works by replacing a function call with a new (injected) function. This way the injected function is in the same memory region and thread context, where other functions can safely be called. At the end of the injected function, the old (replaced) function should be called.

Let’s look at two diagrams.  The first one is the game before injection. Game::fetchMouse is called periodically to check for mouse clicks. In this game, the player clicks into the world and so Player::MoveTo is called by the game loop.

To call our own functions while the game is running, we replace the Game::update() function (just some function which is called every frame). There we can call all game related functions, like Player::MoveTo.
Afterward Game::update has to be called in the injected function or simply nothing would happen.

Practical Example

In this section, injection is demonstrated with osxinj.

We try to call the function which handles a Click-To-Move.

Finding the addresses

First the pointer to the Click-To-Move function has to be found.

I noticed that the game shows the current coordinates while hovering the minimap, this makes it easier:

Ingame I walk to some distinctive place (like a little stone) and write the coordinates into the Bit Slicer search (but don’t hit enter yet). I go a bit away, Click-To-Move on that Stone ingame and quickly press enter (scan) in Bit Slicer (this needs to happen before the character arrives, so the player coordinates don’t match the target).

I selected float, as coordinates are almost always decimal. Also, I set the round error to 0.7, so it looks after variables from 276.3 to 277.7. This way I don’t have to be that exactly.
The procedure is repeated until the number of found variables don’t decrease (I ended at 18 variables).

Next, some of the variables are frozen in Bit Slicer, to see if they are responsible for the function.

Indeed, no matter where I click, the character always walks to the x-coordinate 296.
Every single variable is now frozen separately until the one responsible is found (other variables shouldn’t lock the x-coordinate).

The address, which seems to be a part of the Move-To-Click function, is now found. The next step is to watch which functions are modifying it. Right-clicking on the variable-> Watch Write Access shows such functions. For it to pop up, Click-To-Move has to be triggered, by clicking in-game into the world. The next screenshot shows the entry.

Next, the debugger is opened. Right Click -> Show Debugger. A breakpoint on the found address is set, Click-To-Move has to be triggered again in-game.

The stack trace is the important thing here. The assembly instruction where the Click To Move happens is provided (the address has to be written down / memorised).

But this is not enough, also a function which is called by the game loop periodically has to be found, as we simply can’t call the function from a context/thread outside. That’s quite simple: Go through the backtrace, set a breakpoint at every function separately, look which function triggers every frame.

Understanding and Injecting

As we have the relevant addresses, we can start opening the disassembler (Hopper). The reason why it’s better to use a professional disassembler instead of just a hex editor is, that a disassembler can analyze the code and tell us where functions are and which arguments they have. Additionally, annotations can be made and found functions can be named.

I start with the function which was called every frame (0x5B37A2). Pressing G opens a window where it is possible to jump to an address.

Scroll up a bit, the beginning of the function reveals:

The function starts at 0x5be6e0. It has multiple variables (which is not really interesting) and one argument. This software is likely written in C++, this is important as this means, that the first argument is nearly always the this pointer of a class. This is the way classes are implemented in C++.

So, for example, this C++ Code

class Test
{
    int anumber;
    void foo(int another)
    {
        anumber = anumber + another;
    }
};

is practically the same as this C code:

struct somestruct_s {
    int anumber;
} somestruct;

// called with the struct
void foo(void* classpointer, int another)
{
    // like this->anumber in c++
    classpointer->anumber = classpointer->anumber + another;
}

It is important to note, that it is not possible to call such functions without the correct this pointer.

Injecting

For making injecting simpler,  the osxinj library is used. For the sake of simplicity, the repository is cloned and the testdylib/main.cpp edited.

The install() function is called when the library was injected into the victim software.

At the top of the file, a function variable is declared by me where I store the victim function. Notice that I defined one argument, just like in the assembly seen (the type whether void* or int* etc. is not important, as machine code doesn’t know types):

void (*injected_function)(void* thisptr) = 0;

Also, a new function which will replace the old one is created:

void ourfunction(void* thisptr)
{
    // we will do something here later
}

Inside void install() we declare a variable of the address of the found function…

long *func_ptr = (long*)0x5b36e0;

… and save it inside the function variable we declared before:

injected_function = (void (*)(void*))func_ptr;

Now overriding is possible:

mach_error_t me;

me = mach_override_ptr(
                  func_ptr,
                  (void*)&ourfunction,
                  (void**)&injected_function);

Injecting now wouldn’t be a good idea. As a function was simply replaced (more precisely: removed), the game would behave weird. So inside ourfunction the old function is called:

(*injected_function)(thisptr);

Compile and inject:

./osxinj NameOfTheGameProcess testdylib.dylib

Nothing happens for now, except that the function should show up in backtraces of Bit Slicer.

Making the character move

Back to Hopper, the address 0xD8785 is looked up and hopefully, there is a function which accepts two or three arguments (our player position, xy or xyz).

Looking into the args, there is only one real argument (the other one is the this pointer). So is this the wrong place?

Following the argument (arg_4), on address line 0xd8720 it is loaded into eax mov eax, dword [ebp+arg_4] and then something interesting happens: It gets dereferenced (the square brackets) and loaded into xmm0 movss xmm0, dword[eax], then again dereferenced 4 bytes further and loaded into xmm1 movss xmm1, dword[eax+4]. Then some calculation is done with xmm0 and xmm1, finally, eax is 8 bytes further dereferenced and stored.
The xmm registers are registers used for floating point numbers and a float is 4 bytes long. That leads to the conclusion that the argument is a float array with 3 elements.

Next: Test if these are the x, y, z coordinates.

The function declaration (seen in assembly) is mimicked and a variable is defined, which holds the address:

long* gotoworld_ptr = (long*)0x000D86F0;
// needed later
void* gotoworldthispointer;
void (*gotoworld_func)(void* thisptr, int x) = 0;

Again the type of the arguments doesn’t matter, as the machine doesn’t know any types. For demonstration, I chose an int.

Now a function which calls the function found (a wrapper basically).

void gotoworldcaller(void *thisptr, int x)
{
    float test[3];
    test[0] = 272;
    test[1] = 500;
    test[2] = 5435;
    
    gotoworldthispointer = thisptr; // explained below
    (*gotoworld_func)(thisptr, (int)test); // explained below
}

Simply calling it isn’t possible. There is a problem with the this pointer. We have don’t have any information about the class which contains the found function. For that reason I do a little trick, I also inject into this function and save the this pointer when it’s called, so a later use is possible. That means before the function can be used, the player has to right click one time manually. To match the function prototype an unused argument x is declared (Maybe not really needed).

So injecting this function:

// inside install
gotoworld_ptr = (long*)0x000D86F0;
gotoworld_func = (void(*)(void*, int))gotoworld_ptr;

mach_error_t me;

me = mach_override_ptr(
                       gotoworld_ptr,
                       (void*)&gotoworldcaller,
                       (void**)&gotoworld_func);

It is now possible to call it inside the first injected function (the one which is called every frame):

// put in a little counter, so it is not called on every frame
int counter = 0;

void ourfunction(void *thisptr)
{
   counter++;
  
   if (counter == 10000) // call every 10000 frames
   {
        counter = 0;
        if (gotoworldthispointer != 0) // wait till we have the this pointer
        {
            gotoworld(gotoworldthispointer, 0);
        }

    }
}

Restarting the game (to get rid of the old injection), injecting and right clicking somewhere for the first time results that our little bot always walks to this position, no matter what we do.

Here is the complete code. I randomized the positions, so it walks always somewhere randomly every 10000 frames:

#include <cstdio>

#include "mach_override.h"
#include "mach-o/dyld.h"
#include <CoreServices/CoreServices.h>
#include <string>
#include <fstream>
#include <iostream>
#include <time.h>

long* gotoworld_ptr = (long*)0x000D86F0;

void* gotoworldthispointer;
void (*gotoworld_func)(void* thisptr, int x) = 0;

void gotoworldcaller(void *thisptr, int x)
{
    float test[3];
    test[0] = 272+rand() % 15;
    test[1] = 500;
    test[2] = 5435+rand() % 15;
    
    gotoworldthispointer = thisptr; // save the thisptr, used when the user clicks manually, so we now the address of the class
    (*gotoworld_func)(thisptr, (int)test); // call overwritten function
}

void (*injected_function)(void* thisptr) = 0;

// called by the game every frame after injection
void ourfunction(void* thisptr)
{
    // put in a little counter, so it is not called on every frame
    int counter = 0;
    void ourfunction(void *thisptr)
    {
        counter++;
        
        if (counter == 10000) // call every 10000 frames
        {
            counter = 0;
            if (gotoworldthispointer != 0) // wait till we have the this pointer
            {
                gotoworld(gotoworldthispointer, 0);
            }
        }
    }
    (*injected_function)(thisptr);
}

void install(void) __attribute__ ((constructor));
void install()
{
    // initialize random numbers
    srand (time(NULL));
    
    
    gotoworld_ptr = (long*)0x000D86F0;
    gotoworld_func = (void(*)(void*, int))gotoworld_ptr;
    
    mach_error_t me;
    me = mach_override_ptr(
                           gotoworld_ptr,
                           (void*)&gotoworldcaller,
                           (void**)&gotoworld_func);
    
    
    
    long *func_ptr = (long*)0x5b36e0;
    injected_function = (void (*)(void*))func_ptr;
    

    me = mach_override_ptr(
                           func_ptr,
                           (void*)&ourfunction,
                           (void**)&injected_function);
}

Conclusion

It was shown how a bot can control an in-game character and it even works when the window is minimized! Next step would be to implement a socket, so controlling the injected function (and as such the player position) is possible.

If you have any questions or suggestions, feel free to leave a comment.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.