Fabian Beier-Trampusch Blog

» Knowledge through passion. «

Hacking X Rebirth for fun and education
#Projects 

Hi there! The last two days I had a lot of fun hacking a game called X Rebirth. The task seemed simple: I wanted to see my current in game money on e.g. my tablet. Another use case would be to use excel to analyze the changes of my in game money or draw diagrams based on the money.

So, how can one access the in game money? One could use the in game scripting engine and write debug logs (german thread in the official game forum). This was the way I used in previous X-games. Although that was my first approach, I think I had more trouble getting into the game scripting engine / modding, than …

  • write a DLL which dumps numbers into a file
  • inject it into the game
  • hook the money-setter to call the DLL function

and, honestly, this approach was a lot more fun!

Step 0: Discarded approaches

  • Use the in game lua scripting engine, run it periodically and write the money value to a file (debug/os/io libraries are unfortunately not available)
  • Follow a chain of pointers to find the current in game money and read it (I did not find a valid pointer chain, the address of the money changed each start of the game)

Step 1: Find a good location for the hook

After playing a bit around with Cheat Engine, I found out that only one address writes to my current money. To find this address, I did the following steps:

  1. Start Cheat Engine
  2. Set Scan Type to Exact Value
  3. Set Pause the game while scanning
  4. Entered my current money
  5. Filtered addresses
  6. Changed the in game money
  7. Go to step 4. until exact one address remains

After that, you can right-click on the address and add it to the address list. In my case it is 7FF A649 44E8. Right click on the new entry in the address list and select Find out what writes to this address. After playing a while, again exactly one entry appeared.

I opened the address in the debugger of cheat engine, set a breakpoint on the instruction, went in the game and started a trade. Yay! The breakpoint stopped the execution of the game.

I never did any x86 / x86-64 ASM (at the Munich University of Applied Sciences we were teached MMIX, an imaginary RISC instruction set. This helped me a lot for the basic understanding of what I was doing here.) The mov Instruction copies from the second operand to the first operand. The second operand, RBX, is a register. The first operand, [rdi+00000108], is an address in the memory. It is computed by the contents of the register RDI plus 108 -> which gives 7FF A649 44E8. Yes, this is the same address we had before. However, the content of RBX is 0x2402C, 147.500 in decimal. Did I something wrong?

I resumed the game and the breakpoint was immediately hit again. RBX changed to F 72EB, which is 1.012.459 in decimal. Yes! Thats it! This is my current in game money.

mov [rdi+00000108],rbx is the instruction I need to extend with my debug call. I resume the game and the breakpoint is not hit again until the next transaction. Then the same pattern of one false and one right hit (in this order) continued.

Figure 1: The main Cheat Engine screen with memory scan and write address lookup.

Figure 2: The debugger.

Figure 3: The transaction screen.

Step 2: Write a DLL which dumps numbers into a file and inject it

I opened Visual Studio and wrote a little DLL with one function, which is called fnTestDll. The function takes exactly one parameter (unsigned int money) and writes it to a file. Note: I had some trouble injecting the DLL into the game. The reason was, that I compiled it as 32-bit DLL, while the game is 64-bit compiled. I know, I know … but hey, as soon as I realized and fixed it, it was injectable via Cheat Engine.

#include "stdafx.h"
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <wchar.h>

__declspec(dllexport) void __fastcall fnTestDll(unsigned int money)
{
    FILE* file;
    // yeah, I know, not exactly best practices ... but it does work ;-)
    fopen_s(&file, "C:\\Users\\trampi\\Desktop\\temp\\temp_rebirth.txt", "w+");
    fprintf(file, "%u", money);
    fclose(file);
    return;
}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Step 3: Rewrite the instructions to call the dump function

So, I injected the DLL into the game. Now all I needed to do was to rewrite the mov [rdi+00000108],rbx to call my function after setting the value. Cheat Engine had here again some convenience functionality, which overrides a given instruction with a jump to a nearby Code Cave, in which you can place instructions as you like. I selected the mov-instruction and choose Tools -> Auto Assemble. In the now opened code window, I selected the template Code injection.

After this, I lost some time. I can now enter code to call my DLL. But how do I do this? Cheat Engine can import symbols from other places, (e.g. my DLL), but how do I call the function correctly? Some googling and a question in the Cheat Engine-Forum later I read about the x64 Microsoft calling conventions. The for me relevant part is:

First 4 parameters – RCX, RDX, R8, R9. Others passed on stack.

What I instead tried at this point was PUSHing the argument on the stack, in the hope that the compiled DLL would POP the arguments from the stack. If I remember correctly, that was how it was done in MMIX. I crashed the game like 60 times at that point, because I had no idea how to call my DLL. Instead of looking it up in the first place, I tried to understand the compiled DLLs instructions. Silly me.

The following code is the complete auto assemble script necessary:

[ENABLE] // my modified instructions
// assert that the instruction we overwrite is the mov mentioned earlier
assert(XRebirth.xmlInitializeDict+A809A, 48 8B 5C 24 30)
alloc(newmem,2048,XRebirth.xmlInitializeDict+A8093)
label(returnhere)
loadlibrary(C:\Users\trampi\Desktop\XRebirthWriteToFile\x64\Debug\XRebirthWriteToFile.dll)

newmem: //this is allocated memory, you have read,write,execute access
mov [rdi+00000108],rbx             // execute the original mov
mov rcx,rbx                        // the x64 Microsoft calling conventions state
                                   // that the first parameter shall be stored in rcx
                                   // if it is an integer or a pointer
call XRebirthWriteToFile.fnTestDll // the debug print function
jmp returnhere                     // return to the normal program flow

XRebirth.xmlInitializeDict+A8093: // overwrite the original instruction
jmp newmem   // jump to newmem
nop          // the original mov was 7 bytes long. The jmp instruction above is
nop          // 5 bytes long. so we need to nop to get the right alignment of the
             // following code
returnhere:  // this points right after the original instruction

[DISABLE] // the original instructions
XRebirth.xmlInitializeDict+A8093:
mov [rdi+00000108],rbx // the original code

The above script can be enabled and disabled in Cheat Engine on the fly.

It is done. It works. It does export the money in the given file whenever my money changes. And: the game does not crash anymore!

Step 4, bonus: A little node.js web-app to display it on my tablet

npm install express-ws express

app.js:

var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var fs = require('fs');

app.use(express.static('static'));

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Example app listening at http://%s:%s', host, port);
});

app.ws('/echo', function(ws, req) {
});
var listener = expressWs.getWss('/echo');

var watchedFileName = "../temp_rebirth.txt";

var oldMoneyValue = null;
setInterval(function() {
  fs.readFile(watchedFileName, {encoding: "utf8"}, function(err, newMoneyValue) {
    if (oldMoneyValue != newMoneyValue) {
      oldMoneyValue = newMoneyValue;
      listener.clients.forEach(function(client) {
        client.send(newMoneyValue);
      });
    }
  });
}, 500);

static/index.html:

// ... snip ...
var ws = new WebSocket("ws://192.168.178.100:3000/echo");
ws.onmessage = function(message) {
  $("#credits-container").html("<span id='credits'>" + convertToThousandSeparated(message.data) + "</span>");
}
// ... snip ...

Run it with node app.js.

Summary

I have learned a lot from this. This was my first time with assembler for a real CPU, my first DLL and the first time I have called a DLL. Finding and patching the addresses was also a lot of fun.

One of the most valuable experience from this little project was to never give up. At some points I almost gave up. But I did not - I left my comfort zone to learn some new amazing things.

Questions? Suggestions? Comments? I would love to hear from you!