BattlEye and Windows Local Kernel-Mode Debugging (Part 1)

Windows

Nowadays, every suitable online multiplayer game needs a decent anti-cheat, thanks to many communities of cheaters (eg. MPGH and UnKnoWnCheaTs). As a game server owner, I noticed that the number of cheaters decreased drastically.

BattlEye, an anti-cheat software developed by Bastian Suter in 2004, protects many popular games including ARMA III, Fortnite, PUBG and DayZ. Let’s summarize how it works.

949fb612deaaf3040c4dea0cb4c9145c

BattlEye logo

How does BattlEye works

BattlEye is a client-side and server-side based anti-cheat. The client communicates regularly with the server, and is kicked when abnormal activity is detected (e.g. no response: BattlEye client is probably not running anymore).

BattlEye is backed by a kernel driver, BEDaisy.sys. This is what is all about: this is very complicated to monitor the driver.

It is complicated to describe how BattlEye works precisely because most anti-cheats are based on security through obscurity. However, we can describe most noticeable mechanisms.

Prevent game running under Windows Test Mode

Windows Test Mode is used to allow all drivers signatures, not only trusted ones: anyone can sign it’s own driver. This mode is mostly used by driver developers to test their driver without paying an expensive certificate.

It can be enabled using the following command: bcdedit.exe -set testsigning on. More information can be found on Microsoft’s website.

No need to say that you could easily develop your own cheat driver if BattlEye did not forbid Test Mode while running a BattlEye-protected game.

remove-test-mode-watermark-from-windows-10-desktop_thumb

Test Mode watermark

ObRegisterCallback: register callbacks on hooks

ObRegisterCallback routine can register callbacks on WinAPI calls. It was introduced in Windows Vista following PatchGuard: antivirus and other software which needed to be aware when a new process opened were not able to patch the kernel anymore.

BattlEye registers a callback on many WinAPI calls, including RPM (ReadProcessMemory) and WPM (WriteProcessMemory). That means if we try to read and/or write in a BattlEye-protected game memory, BattlEye will be notified and block the request.

DLLs signatures comparison

Thanks to ObRegisterCallback, BattlEye is aware when someone tries to inject a DLL into the process. It checks if signatures of DLLs injected to the game are in its whitelist. If it is not, it blocks its injection.

DLL signatures are checked on BattlEye master server. Connections to BE master server are obviously encrypted.

Introducing Local Kernel-Mode Debugging

Local Kernel-Mode Debugging allows debugging Windows kernel from userland. It has several restrictions because of the local nature of this kind of debugging. The user cannot perform:

  • Any action that would lead to a stop of the whole OS
  • Any kind of registers content dumping

The reason why is due to it’s implementation: the local kernel debugger is backed by a kernel driver which will execute requested actions. If the drivers dumps registers content, they may (and they will) change during the lapse of time required to print them. Therefore, register dumping is a non-sense.

Local Kernel-Mode Debugging can be enabled using the following command: bcdedit.exe -set debug on. A computer restart is needed to commit changes. After that, you can run a local kernel debugger using the following command: windbg -kl.

02c3e8cdd2966146701012dba5534133

Local Kernel-Mode Debugging using WinDbg

Entering Local Kernel-Mode Debugging will not disable PatchGuard, but:

  • PatchGuard is not started on boot if a kernel debugger is attached
  • PatchGuard has known flaws, including GhostHook

The magic solution to all our problems?

Thanks to lkd, we have now access to the whole memory, and even more. If we succeed to run a BattlEye-protected game with local kernel debugging enabled, we will be able to do whatever we want into the game.

The local debugging driver, kldbgdrv.sys, is signed by Microsoft (which is obviously a trusted authority): this means we can do almost anything whenever we want. Isn’t this great?

The fact is our favorite anti-cheat is using a driver blacklist, which obviously includes kldbgdrv.sys. So sad. If we succeed to bypass the check, we’ll be able to do whatever we want everywhere.

0f0975a981a39585d164533ea2d5e24a

Running Argo with local kernel debugger running

Futhermore, Windows uses 2 global variables to update status: KD_DEBUGGER_ENABLED (macro for KdDebuggerEnabled) and KD_DEBUGGER_NOT_PRESENT (macro for KdDebuggerNotPresent). BattlEye did not seem to check them, but if it did, the check would be easily bypassed.

Part 2: Exploiting Local Kernel-Mode Debugging using WinDbg API

In part 2, we’ll see how we can exploit lkd to create a cheat for education purposes: let’s grab a way to read and write memory.

Bypass BattlEye check

BattlEye is detecting lkd driver, so we need to bypass the check. An idea could be to remove the driver from drivers list. To accomplish that, we need to bypass PatchGuard and do some DKOM on the user-loaded modules list ($peb->Ldr) and the kernel-loaded modules list (nt!PsLoadedModuleList).

Custom RPM and WPM

lkd allows us to read and write in the whole physical address space, but we need to be able to write in the game virtual address space. To do that, let’s convert virtual addresses to physical addresses. Thanks to EngExtCpp extension framework, we don’t have to worry about the very complicated WinDbg API.

Part 2 coming soon.

Leave a Reply

Your email address will not be published. Required fields are marked *