Patching the Wargaming launcher to allow multiple clients
Published on: 2020-10-25
This post concerns the Windows win32 API, and is probably not very interesting if you have patched a binary before. For anybody who wishes to read about an easy patch, I hope you enjoy.
I have been playing a video game called World of Tanks for quite some time now with my father. For a few weeks now, we have been going back and forth between World of Tanks, and Wargamings newer installment, World of Warships. Unfortunately, you cannot by default launch both game clients at the same time. It's time we fix this.
When you want to implement a restriction like that, there are pretty many options:
- You can create some file on start that gets deleted when your tools exits. Crusty, comes with many problems.
- You can search for the window title of your tool, or the name of your ELF/PE file. Better, but what if a user uses a program with the same name?
- You could use a mutex or an atom, or whatever global named object your OS offers that we can abuse for testing if something already exists.
The last point has many advantages for the mutex. First, a mutex actually gets closed by the system once your application terminates (or crashes), while an atom does not. This automatically prevents a whole class of bugs that would arise if we needed to manage the flags lifecycle ourselves. Second, the implementation is extremely simple, we only need two functions on Win32: CreateMutexW and OpenMutexW. Or the *A variants if you're inclined.
The tool checks for a mutex with a globally known (and unique) name. If it does not already exist, it creates it and continues execution. If it exists, the tool can exit with some sort of nag screen. Simple right?
Well, that's what the Wargaming launcher does. The API to this launcher lives in wgc_api.dll, which is loaded dynamically by every wargaming game. There, it tests for a mutex named wgc_running_games_mtx. Of course, there is lots of wrapping going on here, and the OpenMutexW function is called by 3 levels of indirection. It boils down to this:
.text:251C43C0 push edi
.text:251C43C1 push eax
.text:251C43C2 lea eax, [ebp+var_C]
.text:251C43C5 mov large fs:0, eax
.text:251C43CB mov [ebp+var_10], esp
.text:251C43CE mov esi, ecx
.text:251C43D0 cmp dword ptr [esi+40h], 0
.text:251C43D4 lea eax, [ebp+var_15]
.text:251C43D7 mov [ebp+var_4], 0
.text:251C43DE lea ecx, [ebp+var_24]
.text:251C43E1 mov [ebp+var_24], eax
.text:251C43E4 setnz [ebp+var_15]
.text:251C43E8 lea eax, [ebp+arg_0]
.text:251C43EB mov [ebp+var_1C], esi
.text:251C43EE push offset wgc_api.252C5E7C
.text:251C43F3 mov [ebp+var_20], eax
.text:251C43F6 call sr_test_mutex ; nop this and set eax to 0
.text:251C43FB test eax, eax
.text:251C43FD js short loc_251C4433
.text:251C43FF cmp dword ptr [esi+38h], 0
.text:251C4403 jz short loc_251C4433
.text:251C4405 cmp dword ptr [esi+3Ch], 8
.text:251C4409 lea eax, [esi+28h]
.text:251C440C jb short loc_251C4410
sr_test_mutex (my naming skills are sick) returns a non zero value if the mutex is found. This value is always static, and represents a static error code they must have definded in some header. Anyhow, we need to survive the test eax, eax
. Fortunately, windows is a fan of __cdecl, which means the caller cleans up the stack and we can get away with nuking parametrized calls. So that's what we do.
.text:251C43F6 xor eax,eax
.text:251C43F8 nop
.text:251C43F9 nop
.text:251C43FA nop
.text:251C43FB test eax, eax
.text:251C43FD js wgc_api.251C4433
Since js tests the sign flag, we want test eax,eax
to never set this flag, and this in turn means eax must always be 0. Since we nuked the CALL instruction, we have 5 bytes to set eax to 0. The shortest way to do that is xoring eax with itself, which sets it to 0 by the nature of the xor operation. This consumes two bytes, so the three nop
s below are just padding and do nothing. That's it, we are done.
Here is a patch file for x64dbg, in case anybody wants it. The wgc_api.dll it works on has a sha256 sum of c9d48273984bc369a19b44b01b671c629acb6ec2886e52231f1854402d559258.
>wgc_api.dll
000043F6:E8->33
000043F7:65->C0
000043F8:00->90
000043F9:00->90
000043FA:00->90