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.

ss

A 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 (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.

hW8qHA0

Following the Cross Reference I found a big switch statement, which probably handles all the errors.

CaqZHdG

I zoomed in to see the error message I searched for.

vJTrry4

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.

74LmIJj

Caller of print_error_message provides its arg2 as the third argument.

Ur1iVN9

The caller function has 5 references to it.

UaaSNCy

I checked all of them and none made any sense to me, so I gave up with not using a debugger.

8jsr7mN
9pR7DVk
9pR7DVk
BtL2rKN
vphgD7u

Debugging strategy

For debugging I used x64dbg on Windows 10.

I searched for the same string from the error message.

IaQDs1f

I set a breakpoint at this address and hit it after opening the second client.

kQpQCLY

Having Instruction Pointer set on 00302E41 address I set breakpoints on each address of the call stack.

JcSjLFG

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.

nd5rmyE

Mutex explanation

I studied WinAPI documentation page to get more information about this function.

Creates or opens a named or unnamed mutex object.

— Description

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.

— Return value

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.

— Remarks

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.

DptkRUs

To remove this jump I right clicked on this line and chose Assemble.

dtjkoK2

I replaced this je instruction with nop instruction, which does nothing.

PcqUTbJ
90bByA3
2PvnAYw

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.

UCF8PoT

Having in mind the previous values, let’s find them in the hex editor.

QQPdvyg

I used HxD to find and replace those two bytes.

WfMq2OX

Result

I saved the patched .exe file and tried running more than one client with great success.

y5NsUUq