Saturday, January 4, 2020

Bypassing the Easy Anti-Cheat Launcher in Halo

In the last post, I talked about using the app ID 480 trick to bypass the Steam Family sharing restriction. Even though I used the app ID 480 trick, I still had to start the game from the Easy Anti-Cheat launcher (mcclauncher.exe) in order to play Multiplayer. If I did not start the game with the launcher, I'll get an Anti-Cheat Incident pop up when I try to enter Multiplayer games.
My goal now is to find a way to enable multiplayer without using the Easy Anti-Cheat launcher. In this post, I will explain how I did this.

Finding the Easy Anti-Cheat module

The first thing we want to do is find where the game (MCC-Win64-Shipping.exe) first calls functions in the Easy Anti-Cheat API.

Open the binary in x64dbg and break at the EntryPoint. Notice that there does not appear to be any Easy Anti-Cheat module listed in the Symbols table. If we let the rest of the game run until we see the Halo window and pause the debugger, we will then see that easyanticheat_x64.dll is in the Symbols table.

To find out when the game loads this library, set a breakpoint on Export 0 of the easyanticheat_x64.dll. This is the EntryPoint of the DLL and the loader will execute from this address upon loading the library.





Restart the program and press continue until we break on the EntryPoint of easyanticheat_x64.dll. Since we want to know which function in the game binary loaded this library, look at the call stack at this breakpoint. Recall that the call stack lists return addresses.
Look at the first mcc-win64-shipping function (below the kernelbase function). This is where the game calls LoadLibraryA.
The highlighted line is where the program will start executing after it returns from LoadLibraryA. I've added comments to the right of the code. Recall that Microsoft Visual C++ compiler uses the fastcall calling convention when compiling for x64. That means the first four arguments to a function go into registers RCX, RDX, R8, R9 respectively and that the function returns the return value (if any) in RAX.

We have something like this:

eacModule := LoadLibraryA("EasyAntiCheat/easyanticheat_x64.dll")
fnCreateGameClient := GetProcAddress(eacModule, "CreateGameClient")
gameClientInterface := (*fnCreateGameClient) ("GameClientInterfaceV012")

This block of codes retrieves a Game Client interface. This means that we probably cannot avoid loading the library.

Analyzing the CreateGameClient function

To find out what CreateGameClient does, go to the Symbols table again, and set a breakpoint on the CreateGameClient function in the easyanticheat_x64.dll module. Restart the program and press continue (F9) until we get to this function. Press g to switch to graph view. Click drag to look around.
The function is kind of complicated, but if we look at the bottom left of the graph (third box from the bottom in the picture), there is a call to GetCommandLineW. Set a breakpoint there. Ignore the comments "rcx: GameClientInterfaceV012". That is saying what the current value of rcx is.

GetCommandLineW returns a pointer to the command line arguments in RAX. The program then passes this pointer into the first argument of another function. We can reasonably guess that this other function processes the command line arguments.

Click that function and press Enter to follow it.
This is interesting. Notice that the function is checking for "-eac-nop-loaded" in the command line arguments.

Continue to step until we return from this function. The red arrow indicates that we will take the jump.
We could analyze the function that would have been called had we not take the jump, but let's first try to run the program with -eac-nop-loaded as the commandline. To do this in x64dbg, go to File - Change Command Line, and add -eac-nop-loaded to the end.

Disable the breakpoints and restart the program to see what happens. If we click Multiplayer then Social games, we now see the matchmaking screen instead of the Anti-Cheat Incident pop-up.

A More Permanent Solution

Since this seems like a very simple trick, I figured that other people must have already done something like this for other games. It turns out that people have used this trick for another game and the developers for that game quickly patched it.

The easiest way to not rely on this trick is to patch easyanticheat_x64.dll to always execute the instructions after the command that checks whether we have -eac-nop-loaded in our command line.

Do this by NOPing out the je instruction after the function that is called after GetCommandLine.


If you run the program again, and look in the same graph, it should now look like this.



1 comment:

  1. Nice write up fam! Got a small issue though, I've been trying to bypass EAC per this guide, but I'm unable to find the Easy Anti-Cheat module at any point at all. Any tips or idea's?

    ReplyDelete