Thursday, January 23, 2020

Reverse Engineering the Steam API


Introduction

In this post, I will explain how I realized that writing 480 in steam_appid.txt would enable us to bypass Family Sharing restrictions.

My original goal when reversing Halo was to find a way to play the game with my siblings without buying the game. My first approach was to find a way to start the game successfully without Steam.

Steamworks SDK

One approach to do that would be to look for the first library call that MCC-Win64-Shipping.exe makes in the Steam API module. A way to do that might be to go to Symbols tab in x64dbg, select steam_api64.dll, and break on all the exported functions. I might have done this.

The easier way to do this is to download the Steamworks SDK and read the Steamworks SDK documentation. https://partner.steamgames.com/doc/sdk/api/example

After downloading the SDK, go to Main.cpp. There is a function called RealMain with a comment above it that says "Real main entry point for the program".


SteamAPI_RestartAppIfNecessary


The first function that is called is SteamAPI_RestartAppIfNecessary. To find this in x64dbg, go to Symbols tab, select steam_api64.dll, search for the function. We want to break on this function because we want to find the function in MCC-Win64-Shipping.exe that calls this SteamAPI_RestartAppIfNecessary.

Restart the program and continue until we get to the function.

Then look at the callstack to figure out which function in MCC called this library function. It will be the first mcc-win64-shipping function underneath the steam_api64.dll function.
Double-click on the function to follow it in the CPU window. It should look like this picture below.

The callstack tells us where execution will return after executing the function, so that means the instruction above the highlighted line is the instruction that actually called the function.

The instruction test al, al sets the Zero flag if the value in AL is zero. (See here for the reason why: https://en.wikipedia.org/wiki/TEST_(x86_instruction))

Recall that __fastcall convention places the return value in the RAX register. AL is the lower 16-bits of that register. That means the program is testing whether SteamAPI_RestartAppIfNecessary returned 0.

On the line after the test, (where I commented "jmp if returned false"), the je instruction  means "jump if equal", and it will jump if the Zero flag is set. (In some disassemblers, the instruction will be disassembled into jz for jump zero instead of je.) This means that if SteamAPI_RestartAppIfNecessary  returned 0, then we will take the jump.

Now the question is, do we want to take the jump?

Take the jump? 


The instruction directly after the je is executed if we don't take the jump. xor al, al will clear the bits in AL. There is an unconditional jump after that instruction, which takes us to the line right after the line that has the comment "return 1 (success)". These instructions just clean up the stack, so that means if we don't take the jump, then we immediately return 0.

Technically, there is another function that runs just before we return. See this function in IDA by subtracting the address of this function from the base address of MCC-Win64-Shipping.exe. (Get the base address in Symbols tab. It is the entry in the column labeled "Base", which is left of the mcc-win64-shipping.exe entry.)


Copy the offset, which should be 0x234b2b0. In IDA, press g, paste, and click enter. You should get the same function that has a __security_check_cookie label. This is here because Visual C++ compiler will insert checks for buffer overruns if you compile with /GS. This function will not change the result of AL, so we do not need to worry about it.

Back to figuring out whether we want to take the jump.
Since not taking the jump causes us to return 0, the question now is, do we want to return 0.

Return 0?


To find out, go to the call stack to figure out who calls the function that calls SteamAPI_RestartAppIfNecessary and double click to follow. You should get something like the picture below.


We return to a test al, al again, but this time there is a jne instruction after the test. This instruction is "jump if not equal", which is the opposite of je from before. That means if AL is 1 then we will jump because test al, al will not set the Zero flag.

If we do not jump then we will xor ecx, ecx and call the function at 0x7FF7F413D1EC. Click the function and press enter to follow.


This function terminates the process. We don't want to terminate the process. That means we want to take the jump. In order to take the jump, we need to return 1 in the function on the line labeled "the function that calls RealMain".

How to return 1?


Now in order to return 1, we need to execute the line that says mov al, 1 in the function that calls SteamAPI_RestartAppIfNecessary. This is the line highlighted in the picture below.
The red and blue arrows on the left indicate that there are three ways to get here. Set a breakpoint on all three ways. You can try all three ways, but I will tell you that without changing anything, the only jump that does not cause a Fatal Error is the last one.

Jumping from the other two will cause us to crash here with an Exception Access Violation.


We can go on a short tangent and try to figure out why this fatal errors and whether we can recover from it.

SteamInternal_ContextInit Tangent

Notice that before the fatal error, there was a call to SteamInternal_ContextInit. Double click on it and set a breakpoint. Restart the game and follow the first jump that goes to the mov al, 1 instruction. (In x64dbg, on the right side pane with the registers, under RFLAGS, there is a ZF. Double click the 0 next to the ZF to set the Zero flag.) Press continue. The game should start loading and then break at SteamInternal_ContextInit.

Stepping over the instructions, we can see that we eventually reach a je instruction that jumps to the exit. From reversing other video games, I would say that we usually want to enter the Critical Sections, so let's not take this jump.
To confirm that we want to enter the Critical Section, if you keep stepping, you will see that we copy rcx + 0x10 into rax. The value at this address is 0, so when we dereference the value at rax, we get an exception because it is dereferencing 0, which is not a valid address.

To save time, I will tell  you that if we force execution to not take the jumps, the program will then call SteamInternal_FindOrCreateUserInterface.

Stepping through that, it will take the second je and fail with an error that complains that SteamAPI_Init has not yet succeeded. Notice the test rcx, rcx above that, and the mov rcx, [0x00007FFFC7A73298]. Since we took the jump, that means that the value at 0x00007FFFC7A73298 was 0. The error messages indicate that 0x00007FFFC7A73298 is likely the hardcoded pointer to the Steam interface.

This could be useful in the future. Maybe we want to call Steam interface functions. To figure out the offset, subtract this address from the Base address of steam_api64.dll. You should get 0x43298.

This is probably a dead-end because we cannot easily fake the Steam Interface. But let's not leave any stones unturned.

Fake the SteamAPI?

Run the program again while logged into Steam this time. Continue until we get to SteamInternal_ContextInit.

Go to SteamInternal_FindOrCreateUserInterface to get the address of the Steam API. Open up ReclassEx64.exe and paste the address there.
Opening the first pointer in ReClass, we get an array of addresses. On the plus side, these are not runtime addresses. Perhaps we can just give the program the values at 0x00007FFFC7A73298?
I tried this, but I still got the Fatal Error (at a different place) because there was nothing at 7FFFA41AAA08.

Back to MCC

Now if we get to the last je and decide to not take it, you will get a Sign In prompt. If you click No, everything still seems to work as expected. This suggests that the call above the je checks for an active steam user. This also suggests that once you make it to the game lobby, MCC does not seem to really care whether you are logged in to Steam or not.


SteamAPI_RestartAppIfNecessary must return 0

The entire point of the tangent was to rule out taking the first two jumps to the mov al, 1 instruction. By now, we understand that we need SteamAPI_RestartAppIfNecessary to return 0. I'll show the section of code again with better comments. 

Let's take a look at SteamAPI_RestartAppIfNecessary. Notice the early return at the beginning of the function. There are three arrows going to it.


Pan right to see what happens if we don't early return. It seems that execution looks for the installation path of steam.exe in the registry. If you look farther down, you will see the ShellExecute calls. There are no returns once we take this path, and there is no path to the last return that does not call ShellExecute. 


For a better view of the function, you can open steam_api64.dll in IDA, go to Export, and find the function. 


At this point, I had included the steam_appid.txt file and wrote the app id for Path of Exile. When I tried executing the binary, it took the early return, but the app exited immediately.

This was when I started reading the Unreal Engine 4 documentation for the Steam Online Subsystem.



Very interesting. I tried 480, and the game launched :)

Only 480?

I wondered if only 480 works. It seemed to me that any app id for a game that I own should also work. Then I remembered that I had uninstalled Path of Exile, so I tried the app id for CS:GO, a game that is currently installed on my computer, and as predicted, Steam reports that I am playing CS:GO.




Thursday, January 16, 2020

Reversing Halo and Unreal Engine 4

Since my last post, I have been reversing Halo: The Master Chief Collection game in order to find the functions and offsets needed to make an aimbot. In the past, I only looked at Source Engine games, so it was very exciting and fun to work with Unreal Engine 4.

This post will be about tools and techniques for reversing the game. In the next posts, I will explain how I found each of the offsets that I'll be using to make the aimbot.

Aimbot Specifications

An aimbot is a cheat, and the game developers are within their rights to ban us if they detect that we are cheating. In the previous post, however, we already figured out how to run the game without Anti-Cheat.

Side Note about Anti-Cheat

Recall that we need to load the Easy Anti-Cheat (EAC) DLL regardless of whether we want to play in matchmaking because the EAC module calls CreateGameClient and makes the GameClientInterface.

I placed a breakpoint at the EntryPoint of the EAC library, and at first, I thought that EAC had hooked the CreateThread function because the debugger kept hitting that breakpoint, but actually, in the MSDN documentation for DllMain, it says that when the current process creates a new thread, the system calls the entry-point function of all DLLs currently attached to the process. This means that we want to load our game hack DLL as early as possible since all the DLLs loaded before us will have the opportunity to run whatever code they have in their DllMain and will be in a better position to mess with us.

Desirable Features

  • Only aim at enemy players that are visible to us (don't want to aim through walls or at allies)
  • Change aim gradually in order to prevent snapping to targets
  • Have a way to prioritize targets in order to choose among multiple possible target

Information Needed

At minimum, we need the following information to make an aimbot.
  • A way to set our aim
  • The list of (enemy) players currently visible to us
There are also sub-items we need. For example, in order to get the list of enemy players, we might need a way to distinguish between enemies and allies. We might also need a way to distinguish whether we are in-game or not.

Setup

These are the tools I used. I recommend Visual Studio Code for viewing the Unreal Engine 4 source code. The Peek Definition and Find References features are very helpful. Visual Studio 2019 has the same features and seems less buggy, but my computer will lag if I have that and the game running.

Tools

I find that sometimes the VS Code search tool fails to find keywords that are definitely in the files, so I use PowerShell's Select-String to recursively search the code. 

Get-ChildItem * -Recurse | Select-String -Pattern " GObjects"

Notes

When reversing, it helps to take notes and keep those notes organized. I find that I sometimes get really into the process of reversing and forget or get lazy about taking notes. Therefore, we should setup our document to make taking notes as convenient as possible.

Resources

There is a great tool for generating SDKs for Unreal Engine games, but since this is my first time working with Unreal Engine, I wanted to take the chance to find things myself and learn about the engine.

Dumping the Process

When looking at executables that employ anti-reversing techniques, we are usually better off dumping the process. For this, I used Scylla. Select the MCC-Win64-Shipping.exe process, click IAT Autosearch, and then click Dump to save the dump.


I would suggest saving the dump in the same folder as the original MCC-Win64-Shipping.exe file because the program references DLL dependencies relative to its current directory. If you decide to not do this, IDA will ask you to locate the DLL dependencies.

Rebase at Address 0

When you open the file in IDA, opt to manually load the binary, and then when IDA asks you to specify the base address of the image, enter 0. By loading the image at address 0, all addresses will be offsets, which is nice since the base address of the binary will change pretty much every time you restart your computer but offsets do not change.

If you forget to do this, you can rebase the image by selecting Edit->Segments->Rebase. When rebasing, make sure the Names window is not open. For some reason, this makes the rebase very very slow.

Unicode

Microsoft's Visual C++ compiler uses Unicode. IDA does not process Unicode strings by default, so select right click anywhere in the Strings window, select Setup, and check the Unicode box.




Thing to Remember

  • Make sure to have the correct version of the source code
  • Enable Unicode in IDA
  • Try dumping the game process when in game AND when in lobby

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.



Wednesday, January 1, 2020

Bypassing Steam Family Sharing Limitations


My brother bought Halo: The Master Chief Collection on Steam. He shared it with me and my sister via Steam Family Library Sharing, but it turns out that only one person can play at a time. I wanted to see if I could find a way around this restriction. Ideally, I'd like to play the game with my siblings.

Trying out the Game

The first thing to notice is that the developers likely intended for players to launch the game from Steam. Attempting to launch the binary (MCC-Win64-Shipping.exe) directly causes Steam.exe to execute and prompt the user to sign in.

If my siblings are not currently playing the game, then we will see a Play button in the Steam client.
Once the game is running, the Play button in the Steam client changes into a Stop button, which indicates that Steam knows that the game is running.

Anti-Cheat Enabled/Disabled Launch

Steam provides two launch options: regular and Anti-Cheat Disabled.
If we choose the first option, then Steam launches the game from the Easy Anti-Cheat (EAC) launcher (mcclauncher.exe). There will be an Easy Anti-Cheat logo when the game first starts.
Upon successfully launching the game, we will reach the main menu screen.
If we launch the game with Anti-Cheat disabled, then we will see this message when we click Multiplayer button in the menu.
Although the game has both a campaign and a multiplayer part, both parts require that the user be signed into Steam.

Progress

I spent a week reversing the game and learning about how the Unreal Engine uses the steamworks API. My goal here was to find a way to launch the game without Steam.

Usually, I will get a Fatal Error in the loading screen.
Or, if I successfully got through the loading screen, I would get the Purchase screen.

Solution: The 480 Trick

I ended up finding the idea for the solution in the steamworks documentation. It turns out that I do not need to find a way to launch the game without Steam in order to play the game with  my siblings. Instead, I launch the game with Steam but tell Steam that I am playing a different game.

Create this file: C:\Program Files (x86)\Steam\steamapps\common\Halo The Master Chief Collection\steam_appid.txt and write 480 in it.

To play with Anti-Cheat disable, launch the game from C:\Program Files (x86)\Steam\steamapps\common\Halo The Master Chief Collection\MCC\Binaries\Win64\MCC-Win64-Shipping.exe.

To play with Multiplayer enabled, launch the game from C:\Program Files (x86)\Steam\steamapps\common\Halo The Master Chief Collection\mcclauncher.exe

Why it Works

This works because 480 is the Steam app ID for Spacewar. Every user automatically has Spacewar. The Steamworks documentation suggest developers use 480 as a temporary app ID when working with the Steam API during the development and testing phase of creating the game.

When we launch the game with 480 as our app ID, the Steam client will report that we are playing Spacewar.

Since the lobby is provided by Steam, in order to join the same lobby, my siblings would also have to be detected as playing Spacewar. Therefore, they would also need to be using the 480 trick. Interestingly, when my brother did this trick, Steam reported that he was playing Spacewar but still reported that Halo was not currently available for family sharing. That means that while he is playing, Steam can still tell that he is using the license for Halo.

Future

Although I figured out how to bypass the Family Sharing restriction, I would still like to find a way to launch the game without Steam. I am also interested in figuring out how to launch the game with Multiplayer enabled but without having to use the Easy Anti-Cheat launcher.

The solution might seem kind of random. In a future post, I will explain the steps I took in reversing the game, which led me to realize that the solution had something to do with the steam_appid.txt.