5 minutes
Binary patching - creating Tibia MultiClient
Childhood dreams
During some of my first school years I used to play an MMORPG game. It had a reputation for having a lot of cheats for it. (Bad reputation still counts as reputation, right?)
People have created all kinds of software for it.
Some small and harmless programs enabled you to change the outfit of your character client-side.
Others, much clever ones, made trees invisible, so you could see items hidden behind them.
The most advanced ones (called bots
) could play the whole game for you.
Despite wide range of cheats, people had hard time choosing.
You either had to pay a significant amount of money to buy cheats from a reliable party or
download some sketchy .exe
file and put your account at risk.
Both options stood far from ideal.
I remember I dreamed about having all the necessary skills to create such cheats all by myself. A lot of time had passed since this dreaming, will I live up to my past self’s expectations?
MultiClient
Let’s start with creating something simple.
Let’s start with the MultiClient
.
What is a MultiClient
?
Well.
Unmodified game client allows you to have only one instance of it running.
All subsequent clients will display an error message and then shut down.
A MultiClient
just allows running multiple instances of the game client.
Legal
I do consider creating cheats to online games as unethical, I really do.
Why I write about creating such a cheat then?
In my defence, I will write about an old version of the game (9.44
).
Current version (11
) uses anti-cheat mechanism called BattlEye
, and the described
technique does not work in it anymore.
Naive attempt without debugger
At first, I thought I could achieve my goal without running the application. I wanted to use some decompiler, then patch it and then run it.
I searched for the string from the error message and found only one Cross Reference to it.
Following the Cross Reference I found a big switch
statement, which probably handles all the errors.
I zoomed in to see the error message I searched for.
Let’s backtrack from here and investigate how the switch
value might end as 0x1c
.
Execution jumps to different cases based on eax
value.
This value basically equals to arg3
value, decreased by 1.
To find out the value provided as arg3
we need to find Cross References of print_error_message
function.
Caller of print_error_message
provides its arg2
as the third argument.
The caller function has 5 references to it.
I checked all of them and none made any sense to me, so I gave up with not using a debugger.
Debugging strategy
For debugging I used x64dbg
on Windows 10.
I searched for the same string from the error message.
I set a breakpoint at this address and hit it after opening the second client.
Having Instruction Pointer set on 00302E41
address I set breakpoints on each address of the call stack.
I restarted the client and started looking through the code at each breakpoint.
It took a couple of minutes when I came across a call to CreateMutexA
function.
Mutex explanation
I studied WinAPI
documentation
page
to get more information about this function.
Creates or opens a named or unnamed mutex object.
If the function succeeds, the return value is a handle to the newly created mutex object.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
If the mutex is a named mutex and the object existed before this function call, the return value is a handle to the existing object, and the GetLastError function returns ERROR_ALREADY_EXISTS.
If you are using a named mutex to limit your application to a single instance, a malicious user can create this mutex before you do and prevent your application from starting. To prevent this situation, create a randomly named mutex and store the name so that it can only be obtained by an authorized user. Alternatively, you can use a file for this purpose. To limit your application to one instance per user, create a locked file in the user’s profile directory.
Aha!
So the application uses this WinAPI
function to limit execution to a single instance.
If a second instance tries to create a second mutex GetLastError
will return some error.
Now I only need to patch the application so that it does not call GetLastError
.
Patching
I assume this B7
value means ERROR_ALREADY_EXISTS
so I’d rather not execute the jump from line below the comparison.
To remove this jump I right clicked on this line and chose Assemble
.
I replaced this je
instruction with nop
instruction, which does nothing.
Unfortunately when I tried to use patching functionality of x64dbg
it crashed every time.
I had to patch in kind of manual way, using some hex editor.
Firstly I remembered a fragment of the hex dump, which I needed to patch.
I highlighted the two bytes, which x64dbg
changed to 90
.
Having in mind the previous values, let’s find them in the hex editor.
I used HxD
to find and replace those two bytes.
Result
I saved the patched .exe
file and tried running more than one client with great success.