Hacking AssaultCube

Two months ago, I took a stab at DLL Injection and blogged about it here. My goal was to implement the equivalent of unlimited health, ammunition, and armor. AssaultCube, an open source cross-platform first person shooter, seemed ideal for the occasion. I already found websites containing public information on hacks targeting AssaultCube and felt that the goal was obtainable within a 7 week implementation period. This post will serve as a basic explanation of achieving the hack described.


To accomplish 1337 hacks, we need to:

  • Create a DLL to spawn a new thread hosting our malicious code.
  • Inject the DLL into the Assault Cube process, ac_client.exe
  • Obtain memory addresses for health, ammo, etc.
  • Jump desired function, updateworld(), with a detour.
  • Update the memory values with a constant value, 999.


As with any program, we start with the entrypoint of the DLL. The only responsibility here is to catch the DLL_PROCESS_ATTACH case of DllMain’s second argument. Now we can create a new thread in the host process using any of the methods I described in my previous post. For this project, I used CreateThread and passed in a sister function to initialize the rest of my hack. The code would look similar to the following:

HANDLE gHackThread;
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved)
   switch (fdwReason){
      case DLL_PROCESS_ATTACH: {
         gHackThread= CreateThread(NULL, 0, &HackThread, NULL, 0, NULL);
      } break;
      case DLL_PROCESS_DETACH: {
         if (gHackThread)
      } break;
DWORD WINAPI HackThread(LPVOID lpvArguments)
   return TRUE;

Managing Memory

I chose to use AssaultCube mainly for the fact that all of the header information would be available. Finding and handling the player’s data is much easier when you already have the structure. The first problem being that we need a method to extract data segments from the game’s process. Thanks to users sk0r, GU.Firehawk, exploder2013, and plenty more over at Unknowncheats.me for providing great insight in this field. The linked thread describes a method for obtaining the local player inside a list containing other players in the session. The process was outlined by exploder2013:

playerdebug_1 Using OllyDbg, a popular 32bit windows debugger, we can scan the ac_client.exe module for specific strings used in the game. Logically thinking, a string containing “Player %s” would belong to a function containing such information. An array can be scanned by searching for EDX+ECX multiplied by the size of the object, which as we find is 4 bytes per player. In this case, an EDX register was declared with the address [50F4F8] and what follows is the move instruction [EDX+ECX*4].

playerdebug_2 Taking that memory address over to Cheat Engine, we can search for it and dissect the data structure. The picture displays a recording of a session containing 5 players, and the result shows 5 pointers in said structure.

playerdebug_3Within one of the player structures, the player’s name “Peppermint” is found 549 bytes from the head.

In code we wouldn’t have to do much more than storing a copy of the head address of ac_client.exe, then creating a variable to point to that address plus the offset of the player address ( 50F4F8 minus the main address in the previous example ).

Jump some

With the player information in our control, we now need to intercept a function call from the game in place of our own function. Specifically, the updateworld() function would be a prime candidate as it is called framely (as opposed to daily, heh). Thanks to sk0r for having the memory offset available, all we have to do in code is to store the function pointer by adding 0x25EB0 to the head address. This pointer is pivotal for creating a jump detour when the process calls this function. We want to push assembly code instructions to call our function, then return by calling the original function in the game. The basic process for a jump detour is outlined in the following:

  • Obtain the target address with (Head address + function offset) – sizeof jump address.
    • To jump from 0x55555555 to 0x12345678, we would use (0x12345678 + 0x55555555) – JMP32_SZ
  • Call VirtualProtect on the memory at the original function to grant us read & write access.
  • Copy the memory of the original address, and add a jump instruction targeting the original location
    • So something crazy like:
      BYTE *jmp = (BYTE*)malloc(len+JMP32_SZ);
      VirtualProtect(orig, len, PAGE_READWRITE, &dwProt);
      memcpy(jmp, orig, len);
      jmp += len;
      jmp[0] = JMP;
      *(DWORD*)(jmp+1) = (DWORD)(orig+len - jmp) - JMP32_SZ;
    • If there are any bytes left over, they need to be set to NOP (No operation)
  • Next, a jump to our custom function must be pushed at the original location of memory
  • Finally, call VirtualProtect again to re-apply the previous permissions.


What’s left is just creating a function to be called every time the host process would call its updateworld() function. For the purpose of my project, such function updates the player’s health and armor to 999 and sets the current clip to full each frame – as evident in the video. While some advocate that “hacking” should be considered a malpractice, it is a huge field of study regardless. It takes some serious memory mojo to manipulate programs and processes to do what you wish.