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?
Let’s start with creating something simple.
Let’s start with the
What is a
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.
MultiClient just allows running multiple instances of the game client.
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 (
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
Execution jumps to different cases based on
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 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.
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
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.
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
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
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
Having in mind the previous values, let’s find them in the hex editor.
HxD to find and replace those two bytes.
I saved the patched
.exe file and tried running more than one client with great success.