Disclaimer

The information provided in this document is intended solely for educational and informational purposes. It is not meant to belittle EasyAntiCheat or any individuals involved in its development or implementation. Rather, it aims to shed light on the internal workings of EasyAntiCheat so that consumers can better understand what happens behind the scenes when playing their favorite games. Any opinions expressed herein do not necessarily reflect those of EasyAntiCheat or any other parties mentioned. This document is provided “as is” without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. I shall not be liable for any damages whatsoever arising out of or in connection with the use of this document.

Introduction

In a world where virtual battlegrounds have become the arena for fierce competition, cheaters threaten to undermine the very foundations of fair play. But what if the key to safeguarding the integrity of online gaming lies in an elusive strategy, cleverly concealed from prying eyes? Enter EasyAntiCheat, the silent sentinel dedicated to preserving the sanctity of gaming realms.

As we dive into the depths of this captivating tale, we uncover a hidden gem that unfolds like a thrilling detective novel. Beneath the surface of EasyAntiCheat’s armor, a remarkable methodology emerges—one that involves the cunning emulation of NtCreateUserProcess, subtly controlling the very essence of construction within the gaming universe.

Before We Dive In: Getting the Basics Right

To proceed with the remaining part of the article, I recommend first addressing these topics:

The Problem

In the past, it was possible to register a process creation callback and obtain the game’s real CR3 before EasyAntiCheat modified it. However, little did we know that a significant change was about to take place.

As I was going about my daily routine, a message from my friend popped up on Discord, stating that his cheat was no longer functional. The reason behind this sudden disruption? Registering a process creation callback now yielded an incorrect CR3.

Naturally, I couldn’t let this revelation pass without verifying it firsthand. Intrigued and determined, I embarked on a mission to uncover the truth.

Before diving deep, and to get a rough idea of what they were doing, I installed a hook on PspInsertProcess to see if the CR3 had been altered. To my surprise, it had been!

This was quite odd because there were no easy hook points in PspAllocateProcess without installing a hook that was local to the game’s launcher process, which required doing trickery with the PFN, and is rather a versatile approach.

But, more importantly, what does this change mean? Well, it simply means that either scanning for the CR3 or decrypting it is now required.

The Interrogation: Who Dares Touch the EProcess?

To initiate my reversing process, I needed to obtain access to the EProcess allocation before EasyAntiCheat made any modifications to it.

During my investigation of the PspAllocateProcess function in search of a convenient hook point, I stumbled upon the PspInitializeProcessLock function, which proved to be relatively simple to re-implement.

While I could’ve hooked another function, this function didn’t require using a trampoline.

If we delve deeper into the allocation function, we encounter a function named MmCreateProcessAddressSpace, which is responsible for writing to the process’ CR3.

So, by understanding where the CR3 is written, we can determine if it is intercepted by EasyAntiCheat, and not written by the original location.

To accomplish this, I decided to utilize my hypervisor and set an EPT breakpoint on the page that contained the EProcess.

Considering that the function was likely to be called from within the launcher’s context, I simply compared the name to the launcher’s name for verification.

Since the EProcess might not be allocated on a page boundary, I saved the address and checked if the violation occurred within the specified range.

{ "Rva":"0x58233A", "Rbp": "0x4000000853FFF000", ...Other Registers... }

Now, as you can clearly observe, this is precisely where EasyAntiCheat is writing their manipulated CR3 value to the EProcess.

Unfortunately, but not surprisingly, the routine responsible for writing the CR3 is virtualized.

Instead of investing time and effort into tracing and lifting the VM, an alternative approach would be to search for other sections of code that are not virtualized.

However, it’s crucial to remember and keep track of the mentioned piece of code, as it holds significance for future stages.

The Interrogation: The Search Party

As we are aware from the previous section, EasyAntiCheat is somehow intercepting the PspAllocateProcess function and modifying the CR3 very early on in the process.

Considering this information, it is not far-fetched to speculate that they may also be writing to the other components? To confirm this, I removed the offset check, and logged every write to the EProcess.

{ "Rva":"0x42106", "Offset":"0x5E8" }
{ "Rva":"0x42106", "Offset":"0x5E0" }
{ "Rva":"0x42106", "Offset":"0x8A8" }
{ "Rva":"0x42106", "Offset":"0x8A0" }
{ "Rva":"0x42106", "Offset":"0x998" }
{ "Rva":"0x42106", "Offset":"0x990" }

This log was quite a big bigger, but I stripped it down for clarity’s sake.

After following the Rva, I was introduced with a wonderful looking switch case.

Now, when examining this, what does it appear to be? Well, to me, it resembles some kind of function for writing to registers.

To further validate my suspicions, I logged the return address and proceeded to track its path.

Well, this turned out to be a highly successful endeavor, as upon further exploration of the function, it became apparent that it was in fact a CPU emulator!

Indeed, now everything falls into place. It appears that they were emulating the EProcess construction process and manipulating the CR3 write through their emulation engine. This explains how EasyAntiCheat was able to intercept and modify the CR3 value at such an early stage.

The Interrogation: The Operator

Driven by my curiosity, I persisted in tracing the return stack as I was eager to uncover the exact workings of this emulation engine, and who dared to operate it.

I understand that the code may appear obfuscated, but rest assured, it holds valuable significance for our investigation.

As you may have already deduced, this is, in fact, a handler within their VM, which I have identified as BRANCHCALL.

Let’s begin by tracing the branch that is executed when the emulation succeeds, which has the offset 0xFFFFFFFFF844BC37.

This is known as the dispatcher, which the handler uses to dispatch control flow.

It seems relatively straightforward. By adding both constants together, we obtain another branch, which leads to an EXIT handler.

In the other branch, which is executed when the emulation is not completed, the code continues the loop and proceeds with further emulation.

The Interrogation: Stealing the Flow

To determine the starting and ending points of the emulation process, I referred to my reliable hypervisor and hooked the BRANCHCALL handler.

These offsets were gathered by inspecting the assembly in the handler, and are relative to the VM.

{ "vRip":"0xFFFFF80681274376" }
{ "vRip":"0xFFFFF80681274379" }
{ "vRip":"0xFFFFF8068127437E" }
{ "vRip":"0xFFFFF80681274385" }
{ "vRip":"0xFFFFF80681274387" }
{ "vRip":"0xFFFFF80681274389" }
{ "vRip":"0xFFFFF8068127438B" } 
{ "vRip":"0xFFFFF8068127438D" }
{ "vRip":"0xFFFFF8068127438E" }
{ "vRip":"0xFFFFF8068127438F" }
{ "vRip":"0xFFFFF80681274390" }
{ "vRip":"0xFFFFF80681274391" }
{ "vRip":"0x0000000000000000" }

This log was quite a big bigger, but I stripped it down for clarity’s sake.

They were actually emulating even further back than I initially suspected. From analyzing my log, it became apparent that they were emulating NtCreateUserProcess!

To halt the emulation engine at the end of the function, they set the return address to NULL, effectively stopping the emulation process.

The Interrogation: The Backbone

Remember those random function pointers in the emulator’s context? Well, they’re not so random afterall, and are actually the backbone of this entire process.

Let’s start with analyzing this one, which lives at the top of the emulator’s function.

Well, isn’t that something! Now the puzzle is starting to come together, as that particular QWORD is actually the address of the instruction responsible for writing the CR3 value, which we found in MmCreateProcessAddressSpace.

To add the final touch, that function is even the virtualized function that performed the write operation on the CR3, which we traced in the previous sections.

Even after all of this, the question still remains: are they truly emulating every single call within NtCreateUserProcess?

After looking at the rest of the emulator’s code, I discovered the section that handles the execution or emulation of subroutines.

The callback itself is quite straightforward and simply emulates the code if the function leads to MmCreateProcessAddressSpace.

Conclusion

In conclusion, EasyAntiCheat has opened up a Pandora’s box that they cannot close. They have revealed their capabilities, and there is so much more they can unleash.

I eagerly am waiting to document any future developments that EasyAntiCheat introduces.

Until the next revelation!