In-Depth Analysis of the BattlEye Kernel Driver Detection Module

In-Depth Analysis of the BattlEye Kernel Driver Detection Module

This article is a featured post from the KX Forum, authored by KX Forum ID: Ghost Talent zxy

1

Overview of BattlEye

BattlEye is divided into the following four components:

BEService – A service that communicates with the BattlEye server.

BEDaisy – A kernel driver that performs various kernel-level detections and communicates with BEClient.

BEClient – A DLL that runs within the game process, responsible for executing various application-level BE shellcode and communicating with the kernel driver.

BEServer – The BattlEye server that collects uploaded information and determines cheating behavior.

This analysis focuses on BEDaisy, which encompasses various detections within the BE kernel driver.

2

In-Depth Analysis of the BattlEye Kernel Driver Detection Module

The BE kernel driver contains many types of detections. When an anomaly is detected, it is first recorded in an internal linked list. Then, when BEClient issues a read request of a specific length to the BE kernel driver, the driver sends the data from the linked list to BEClient, which then forwards it to BEServer. About a month ago, I wrote a simple program to bypass the BE kernel driver, which works by blocking this process. For the specific principle, please refer to my previous article:https://bbs.pediy.com/thread-273334-1.htm The following content is divided into three parts: the first part discusses the upload section, explaining the various detection-related data sent by BEClient to the BE kernel driver; the second part focuses on the detection section, detailing the various detections within the BE kernel driver; and the third part summarizes these detections. The BE kernel driver also includes other content, such as packet encryption/decryption algorithms and device UID algorithms, which are not related to “detection” and will not be analyzed in this article.

3

Upload Section

BEClient uploads data by calling the Write method of the BE driver, corresponding to the IRP_MJ_WRITE method of the driver. The uploaded content mainly consists of blacklist features, which should be issued by the server, allowing for dynamic adjustment of detection features without recompiling the driver.

[Unknown] Blacklist Features (upload type 0)

Arranged closely in an array-like format, since the detection data is of variable length, each packet has a dynamic length, relying on the header to record the packet length to determine the position of the next packet. Detections are divided into two categories:Features with a specified offset, where BE will only match at specific offsets during detection.Features without a specified offset, where BE will attempt to match at all positions using substring matching.If there are packets in report list 0, each will be checked for matching features, and if found, the abnormal data will be reported as is. (Since no one writes to report list 0, it is suspected that this detection is not yet enabled.) The data structure of report list 0 is as follows:

struct AbnormalListItem {    // because nobody writes to report list 0, so some parts of the structure is unknown    BYTE Unknown[10];    BYTE Content[64];};

The packet uploaded through IrpWrite is as follows:

struct UploadPatternBlackListItemType0 {    // -1 means no specified match offset, it will try every possible offset    // not -1 means a specified offset, it will just try the offset    BYTE MatchOffset;    // if the length <= 32, it will be copied to the g_PatternBlackList    BYTE PatternLength;    // length depends on PatternLength    BYTE Content[0];};

g_PatternBlackList is an array storing 32 PatternBlackListItem, with the specific quantity recorded in g_PatternBlackListSize.

struct PatternBlackListItemType0 {    // pattern in black list up to 32 bytes    BYTE Pattern[32];    // length up to 32    ULONG Length;};

When the detection thread starts, a hardcoded feature of 9 bytes length will be added to g_PatternBlackList (it appears to be related to some ROP features? Not quite sure).

48 81 C4 80 01 00 00 5F C3

Corresponding to amd64 assembly

add rsp, 180hpop rdiret

Callback Blacklist Features (upload type 1)

This detection targets the pre- and post-callbacks of processes and threads, registry callbacks, and image loading callbacks, detecting features in the first 64 bytes of these functions.

struct UploadPatternBlackListItemType1or2 {    // -1 means universal pattern, this check will be applied to each callback    // not -1 means this check only works on a specific callback    BYTE FunctionType;    // -1 means no specified match offset, it will try every possible offset    // not -1 means a specified offset, it will just try the offset    BYTE MatchOffset;    // length of the pattern    BYTE PatternLength;    // length depends on PatternLength    BYTE Content[0];};

System Call Blacklist Features (upload type 2)

The packet format is the same as the previous feature, with the detection target being the first 64 bytes of system call functions.

BE Driver Integrity Check Features (upload type 3)

The uploaded content is a byte sequence at a given offset, which will be used in subsequent steps (see report type 18) to check key code sections of the BE driver to determine if it has been tampered with.

struct UploadSelfIntegrityCheck {    // if it is true, it means use stored driver memory range    // if it is false, it means use driver memory range read from driver object    BOOLEAN UseStoredDriverInfo;    // offset to the driver module    ULONG Offset;    // unknown, has an impact on the reporting policy    // if the flag is true, then normal means upload, abnormal means don't upload    // maybe use to detect some kind of attack?    BOOLEAN FlipReportPolicy;    // compare size, up to 64 bytes    ULONG CompareSize;    // content of normal data, length depends on CompareSize    BYTE Content[0];};

Internal Unexported Function Features of Dxgkrnl (upload type 4)This feature is used to locate a certain internal unexported function of Dxgkrnl. In subsequent steps (see report type 22), BE will hook this function and check the address range of this function.

struct UploadDxgkrnlInternalFunctionRangeCheck {    // length = upload packet length - 1    BYTE Pattern[0];    // how far is the function address from the pattern matching address    BYTE Offset;}

InfinityHook Detection (upload type 5)

This type of packet is solely to trigger InfinityHook detection (see report type 23) and does not transmit data.

4

Detection Section

This section contains a lot of content, with over 30 types of detections. Most detections are numbered, and only when the detection result is abnormal will it be recorded and transmitted to the application-layer BEClient, which then sends it to the BE server. All numbered detections are as follows, in addition to a few unnumbered detections in the IRP_MJ_READ handler (e.g., obtaining device UID, virtual machine detection, etc.).

  1. Dispatch Function Integrity Check

  2. System Thread Start Address Check

  3. Process and Thread Callback Functionality Check

  4. Game Process Thread Creation Check

  5. PsLookupThreadByThreadId Hook Check

  6. [Unknown]

  7. Process, Thread, Registry Callback Hook Check

  8. Process, Thread, Registry Callback Address Module Range Check

  9. PhysicalMemory Reference Check

  10. System Call Integrity Check

  11. [Unknown]

  12. Module Abnormal Instruction Check

  13. DxgCoreInterface Address Range Check

  14. DxgCoreInterface Hook Check

  15. System Thread Stack Check

  16. Hidden Driver Check

  17. [Unknown]

  18. Callback Function Information Reporting

  19. BE Driver Integrity Check

  20. Module IAT Hook Check

  21. gDxgkInterface Address Range Check

  22. gDxgkInterface Hook Check

  23. Dxgkrnl Internal Unexported Function Range Check (disabled)

  24. Infinity Hook Check

  25. gDxgkWin32kEngInterface Address Range Check

  26. gDxgkWin32kEngInterface Hook Check

  27. PCI Device Check

  28. HalDispatchTable Address Range Check

  29. HalDispatchTable Hook Check

  30. HalPrivateDispatchTable Address Range Check

  31. HalPrivateDispatchTable Hook Check

  32. FltMgrMsg Object Callback Module Range Check

  33. FltMgrMsg Object Callback Hook Check

  34. ext_ms_win_core_win32k_full_export_l1 Address Range Check

BE Driver Integrity Check (report type 18)

Upon receiving the data uploaded by the application, detection will begin. BE will check key parts of its own driver to see if it has been tampered with. If an anomaly is detected, it will be reported. Packet structure:

struct PacketSelfIntegrityCheck {    // 18 is self integrity check    BYTE PacketType;    // if it is true, it means use stored driver memory range    // if it is false, it means use driver memory range read from driver object    BOOLEAN UseStoredDriverInfo;    // offset to the driver module    ULONG Offset;    // content of checked address, 64 bytes    BYTE Content[64];};

System Call Integrity Check (report type 9)

BE will perform integrity checks on system functions obtained through MmGetSystemRoutineAddress, checking whether the address obtained at that time matches the previously obtained address, and whether the system function header has been hooked. If a hook is found, it will trace unconditional jumps until the final hook function and report the characteristics of that hook function. There are a total of four types of anomalies:

  • Function Pointer Modification

  • Function Address Not Within Module Range (hook of manually mapped drivers)

  • After Jump, Function Address Not Within Module Range (similar to the previous anomaly)

  • Presence of int 3 Breakpoint, indicating that the system is being debugged

Additionally, if deemed normal, information will still be temporarily recorded in report list 2 for subsequent checks for the presence of blacklist features. Packet structure:

struct PacketSyscallIntegrityCheck {    // 9 is syscall integrity check    BYTE PacketType;    // each syscall function has an index    BYTE FuncIndex;    // -1: fine    // 0: function pointer modification    // 1: address out of module range    // 2: after jump, address out of range    // 3: int3 trap, may be under debugging    BYTE ErrorType;    // after useless jump instructions, the function body's address    PVOID Address;    // dump 64 bytes    BYTE Content[64];};

System Thread Start Address Check (report type 1)

This will attempt to traverse system threads through various means (obtaining thread information via SystemProcessInformation, enumerating TIDs to try to get thread objects). If hidden processes/threads are detected during traversal (i.e., the system process cannot be found or the current thread cannot be found in the SystemProcessInformation of the system process), it will be recorded in a global variable. If a system thread is detected with a start address not within the loaded module address range (the module address range will be recorded in a linked list in LoadImageNotify), abnormal data will be reported. It is suspected that this is used to detect tools like kdmapper. Packet structure:

struct PacketSystemThreadStartAddressCheck {    // 1 is system thread start address check    BYTE PacketType;    // start address read from SYSTEM_PROCESS_INFORMATION structure    PVOID StartAddress;    // dump 64 bytes from start address    BYTE Content[64];    // thread running time    // from thread creation to now    LARGE_INTEGER RunningTime;    // CountdownId = SystemProcessInformation->NumberOfThreads - AbnormalThreadIndex - 1    // counting thread indexes from back to front    // making the ID generic    USHORT CountdownId;    // thread create time    // between process creation and thread creation    LARGE_INTEGER CreateTime;};

System Thread Stack Check (report type 14)

Inserts APC into all system threads, calling RtlWalkFrameChain to obtain the caller list, checking whether the addresses of each kernel space caller are within the module range, whether there are features in the blacklist, and whether there are multiple jumps (>=5), int3, nop, and other anomalies. If any are found, abnormal data will be reported directly. If deemed normal, it will be added to report list 0 for further blacklist checks. Packet structure:

struct PacketSystemThreadStartAddressCheck {    // 14 is system thread stack check    BYTE PacketType;    // bad caller index in the RtlWalkFrameChain result    BYTE CallerIndex;    // bad caller's return address    PVOID Address;    // 64 bytes of caller's content    BYTE Content[64];    // notice: only 32 bits    // which thread has the bad caller    ULONG ThreadId;    // image name length    BYTE ImageNameLength;    // image name buffer    // length depends on the ImageNameLength    BYTE ImageName[0];    // low 32 bits of StartAddress, always upload    ULONG LowStartAddress;    // may be null if the StartAddress is invalid    PVOID StartAddress;    // may be null if the StartAddress is invalid    HANDLE ProcessId;    // thread running time    // from thread creation to now    LARGE_INTEGER RunningTime;    // CountdownId = SystemProcessInformation->NumberOfThreads - AbnormalThreadIndex - 1    // counting thread indexes from back to front    // making the ID generic    USHORT CountdownId;    // thread create time    // between process creation and thread creation    LARGE_INTEGER CreateTime;    // track the E9 jumps after the return address up to 60 bytes,    // record up to 10 addresses    BYTE FollowAddressCount;    // size depends on the FollowAddressCount    PVOID FollowAddressArr[0];};

Process, Thread, Registry Callback Detection

Callback Hook Check (report type 6)

This will check the pre- and post-callbacks of processes and threads, registry callbacks, and image loading callbacks, detecting whether the following types of hooks exist, checking the first 64 bytes at most:

  • FF 25 XX XX XX XX: jmp [addr]

  • 48 B8 XX XX XX XX XX XX XX XX: mov rax, immFF E0: jmp rax

(Note: It will not trace multiple jumps, only one, which seems to be an unreasonable design) Abnormal data will only be reported when a hook is detected, with the structure as follows:

struct PacketCallbackHookCheck {    // 6 is callback hook check    BYTE PacketType;    // function type:    // 0: process callback    // 1: thread callback    // 2: register callback    // 3: image notify callback    BYTE FunctionType;    // hooked offset to the callback function begin    BYTE HookOffset;    // absolute hooked address    PVOID HookAddress;    // dump 16 bytes of callback head    BYTE CallbackHeadContent[16];    // where to jump    PVOID JumpAddress;    // content of address after the jump    BYTE HookContent[64];    // up to 260 bytes, no terminator    CHAR ModulePath[0];};

Callback Address Module Range Check (report type 7)

This checks whether the callback address is within the range of a certain kernel module. If it is not within any module’s address range, abnormal data will be reported. Packet structure:

struct PacketCallbackRangeCheck {    // 7 is callback range check    BYTE PacketType;    // function type:    // 0: process callback    // 1: thread callback    // 2: register callback    // 3: image notify callback    BYTE FunctionType;    // address of the function    PVOID Address;    // 64 bytes content of the callback    BYTE Content[64];};

Callback Function Information Reporting (report type 17)

All pre- and post-callbacks of processes and threads, registry callbacks, and image loading callbacks will be recorded in report list 1 for further detection of blacklist features. Packet structure:

struct PacketCallbackCheck {    // 17 is callback check    BYTE PacketType;    // function type:    // 0: process callback    // 1: thread callback    // 2: register callback    // 3: image notify callback    BYTE FunctionType;    // address of the callback    PVOID Address;    // 64 bytes content of the callback    BYTE Content[64];    // module path if exists, no terminator    CHAR ModulePath[0];};

PhysicalMemory Reference Check (report type 8)

This checks whether any application references the “\\device\\PhysicalMemory” object. If it does, abnormal data will be reported. The logic of the detection is as follows: First, traverse all processes, using MmUnmapViewOfSection to unmap the “\\device\\PhysicalMemory” mapping, then check whether the NumberOfUserReferences in the ControlArea of the “\\device\\PhysicalMemory” Section object is 0. If it is not 0, it indicates that there is still an application referencing physical memory, thus deemed abnormal, and abnormal data will be reported. (This is used to detect some manually created “\\device\\PhysicalMemory” objects?) Packet structure:

struct PacketPhysicalMemoryReferenceCheck {    // 8 is physical memory reference check    BYTE PacketType;    // fields in struct _CONTROL_AREA    ULONG64 NumberOfSectionReferences;    ULONG64 NumberOfPfnReferences;    ULONG64 NumberOfMappedViews;    ULONG64 NumberOfUserReferences;};

Process, Thread Callback Functionality Check (report type 2)

First, a flag is set to 0, then an attempt is made to obtain the game process handle. If the callback works normally, the flag will be set to 1; otherwise, it remains 0, thus detecting whether the callback has been removed by some means and cannot function normally. If the callback cannot function normally, abnormal data will be reported once (no repeated reporting). Packet structure:

struct PacketProcessThreadCallbackFunctionalityCheck {    // 2 is process thread callback functionality check    BYTE PacketType;    // probably always true    BOOLEAN Abnormal;};

Dispatch Function Address Check (report type 0)

When the BE kernel module loads, it checks whether all dispatch functions of all system modules are either functions within its own module or functions of system modules (ntoskrnl). During runtime, it checks whether its own MJ_IRP_CREATE, MJ_IRP_CLOSE, MJ_IRP_READ, MJ_IRP_WRITE corresponding dispatch functions have been modified. If modified, it will upload to the abnormal list; otherwise, no additional operations will be performed. Packet structure:

struct PacketDispatchFunctionIntegrityCheck {    // 0 is dispatch function integrity check    BYTE PacketType;    // driver name    // length = PacketLength - OtherFieldsLength    CHAR DriverName[0];    // major number    BYTE MajorNumber;    // hook function address    PVOID Address;    // 64 bytes of hook function    BYTE Content[64];};

PsLookupThreadByThreadId Hook Check (report type 4)

This function is called within the thread callback function and checks whether PsLookupThreadByThreadId has been hooked. The only hook type checked is FF 25 jmp, i.e., jmp [addr] type hooks. This function will trace up to 2 jumps. If a hook is found, it will be uploaded to the abnormal list. (It will perform an XOR 0x7F encryption operation on the packet from 1 to 45 bytes, with the first byte PacketType not being encrypted; the reason for this is unclear) Packet structure:

struct PacketPsLookupThreadByThreadIdHookCheck {    // 4 is PsLookupThreadByThreadId hook check    BYTE PacketType;    // PsLookupThreadByThreadId address    PVOID FunctionAddress;    // FF 25 (4 bytes offset)    ULONG JumpOffset1;    // address after the first jump    PVOID HookFunction1;    // whether there is another jump    BOOLEAN TwoJump;    union {        // no another jump        // dump 16 bytes of the first hook function        BYTE Content1[16];        // have another jump        struct {            // record the second hook function            PVOID HookFunction2;            // dump 16 bytes of the second hook function            BYTE Content2[16];        };    };};

\\FileSystem\\Filters\\FltMgrMsg Object Detection

The \\FileSystem\\Filters\\FltMgrMsg object involves Filter communication, which has callback functions for filtering communication, thus BE performs checks on it. Refer to this article:https://www.amossys.fr/fr/ressources/blog-technique/filter-communication-ports/ Three callbacks will be checked:

  1. ConnectNotifyCallback

  2. DisconnectNotifyCallback

  3. MessageNotifyCallback

FltMgrMsg Object Callback Module Range Check (report type 31)

This checks whether the callback address is within the range of a certain kernel module. If it is not within any module’s address range, abnormal data will be reported. Packet structure:

struct PacketFltMgrMsgCallbackRangeCheck {    // 31 is FltMgrMsg callback range check    BYTE PacketType;    // function type:    // 0: ConnectNotifyCallback    // 1: DisconnectNotifyCallback    // 2: MessageNotifyCallback    BYTE FunctionType;    // address of the function    PVOID Address;    // 64 bytes content of the callback    BYTE Content[64];};

FltMgrMsg Object Callback Hook Check (report type 32)

Checks for hooks on the three callback functions, similar to the callback hook check (report type 6), with only the report type differing. Abnormal data will be reported when a hook is detected, with the structure as follows:

struct PacketFltMgrMsgCallbackHookCheck {    // 32 is FltMgrMsg callback hook check    BYTE PacketType;    // function type:    // 0: ConnectNotifyCallback    // 1: DisconnectNotifyCallback    // 2: MessageNotifyCallback    BYTE FunctionType;    // hooked offset to the callback function begin    BYTE HookOffset;    // absolute hooked address    PVOID HookAddress;    // dump 16 bytes of callback head    BYTE CallbackHeadContent[16];    // where to jump    PVOID JumpAddress;    // content of address after the jump    BYTE HookContent[64];    // up to 260 bytes, no terminator    CHAR ModulePath[0];};

Dxgkrnl Internal Unexported Function Range Check (report type 22) (disabled)

First, BE will hook this function, then in the hook function, perform a module range check on the original function. Currently, this detection is not complete, and the private linked list is not cleaned up when the driver is unloaded, leading to suspicion that this detection is not enabled. If the function address is detected to be outside any module, abnormal data will be reported. To avoid duplicate reporting, this detection uses report list 6 to record each abnormal report data. Packet structure:

struct PacketDxgkrnlInternalFunctionRangeCheck {    // 22 is unknown function range check    BYTE PacketType;    // address of the function    PVOID Address;    // 64 bytes content of the function    BYTE Content[64];};

Infinity Hook Detection (report type 23)

This first checks whether the system can perform an infinity hook. If it is possible, it will check the address of the GetCpuClock function in each item of WmipLoggerContext. If the function address is within the module address range and the permissions of the section where the address resides are executable + non-paged (analyzing the PE file read from disk), it is deemed normal; otherwise, it is deemed abnormal, and abnormal data will be reported. To avoid duplicate reporting, this detection uses report list 5 to record each abnormal report data for deduplication. Packet structure:

struct PacketInfinityHookRangeCheck {    // 23 is infinity hook range check    BYTE PacketType;    // address of the function    PVOID Address;    // 64 bytes content of the function    BYTE Content[64];};

System Module Detection

This traverses all modules loaded in the kernel for detection but skips the following modules.

  • hal.dll

  • clipsp.sys

  • CI.dll

  • tpm.sys

  • ks.sys

  • cdd.dll

  • TSDDD.dll

  • spsys.sys

  • atikmpag.sys

When processing the win32k module, since the memory of the win32k module is only mapped in csrss, it needs to be attached to csrss for inspection.

Module Abnormal Instruction Check (report type 11)

Due to the chaotic nature of this detection module, reverse analysis is not very clear. It is suspected to be looking for some int 3, hook instructions in the module and uploading the pages where the instructions are located to the abnormal list. There is special detection for dxgkrnl.sys, suspected to be detecting gdi hooks, original link:https://secret.club/2019/10/18/kernel_gdi_hook.html Packet structure:

struct PacketModuleAbnormalInstructionCheck {    // 11 is module abnormal instruction check    BYTE PacketType;    // length of the module name, up to 64    BYTE ModuleNameLength;    // length depends on ModuleNameLength    CHAR ModuleName[0];    // offset in page    ULONG OffsetInPage;    // content of the page which contains the abnormal instruction, up to 0x1000 bytes    BYTE Content[0];};

Module IAT Hook Check (report type 19)

This checks whether any function address of a certain IAT item is not within any module range by parsing the PE structure in the memory of each module. If so, abnormal data will be reported. Packet structure:

struct PacketModuleIATHookCheck {    // 19 is module IAT hook check    BYTE PacketType;    // module name    // no length is recorded yet !    CHAR ModuleName[0];    // function index in the IAT    ULONG FunctionIndex;    // offset of the function IAT entry to the module base    ULONG EntryOffset;    // function in the IAT entry    PVOID Function;    // content of the function    BYTE Content[64];};

Hidden Driver Check (report type 15)

This traverses the \\Device directory to obtain all Device type objects, then traverses the \\Driver and \\FileSystem directories to obtain all Driver objects. For each Device object, it finds its internally stored Driver pointer and matches it with the previously obtained Driver objects. If no Driver object matches, it is deemed that the driver corresponding to that Device is hidden, and abnormal data will be reported. Packet structure:

struct PacketHiddenDriverCheck {    // 15 is hidden driver check    BYTE PacketType;    // length of the device name    BYTE DeviceNameLength;    // name of the device whose driver is hidden    CHAR DeviceName[0];    // driver name of the hidden driver    // length = PacketLength - OtherFieldsLength    CHAR DriverName[0];};

PCI Device Check (report type 26)

This traverses the PCI device tree using I/O instructions to find PCI devices with specified features, suspected to be detecting DMA cheating tools. If a PCI device with specified features is found, abnormal data will be reported. Reference source code for PCI device detection:https://gitlab.freedesktop.org/xorg/lib/libpciaccess/-/blob/master/src/x86_pci.c It has also been mentioned in UC:https://www.unknowncheats.me/forum/anti-cheat-bypass/304545-detecting-dma-hardware-cheats-12.html (Note: In the second reporting type, the Info in the report seems to have been mistakenly written as Bus by the BE developers, resulting in two Bus records without recording Dev, which is amusing) Packet structure:

struct PacketHiddenDriverCheckType1 {    // 26 is pci device check    BYTE PacketType;    // PCI enumeration info    struct {        BYTE Bus;        BYTE Dev;        BYTE Func;    } Info;    // 4 bytes read from reg VENDOR_ID (0x0)    ULONG VendorId;    // 4 bytes read from reg PCI_CLASS (0x08)    ULONG PciClass;    // 1 byte read from reg HDRTYPE (0x0E)    BYTE HdrType;    // 4 bytes read from reg PCI_SUB_VENDOR_ID (0x2C)    ULONG SubVendorId;};
struct PacketHiddenDriverCheckType2 {    // 26 is pci device check    BYTE PacketType;    // PCI enumeration info    struct {        BYTE Bus;        BYTE Dev;        BYTE Func;    } Info;    // 256 bytes read from reg VENDOR_ID (0x0)    BYTE VendorId[256];};

Win32k Function Pointer Table Detection

gDxgkInterface and gDxgkWin32kEngInterface are two function tables stored in Win32k, serving a purpose similar to SSDT. IChooseYou has used them for communication without modules,https://www.unknowncheats.me/forum/anti-cheat-bypass/335585-communicating-mapped-driver-using.html, thus BE performs checks on them. Since win32k is only mapped in the address space of the csrss module, it needs to be attached to the csrss process for detection.

gDxgkInterface Address Range Check (report type 20)

This performs address range checks on most functions in the gDxgkInterface table (skipping the first two functions), checking whether their addresses are within the win32k module range. Packet structure:

struct PacketWin32kRangeCheckType1 {    // 20 is win32k gDxgkInterface range check    BYTE PacketType;    // function index in the gDxgkInterface table    ULONG Index;    // function address    PVOID Function;    // 64 bytes of the function    BYTE Content[64];};

gDxgkInterface Hook Check (report type 21)

This performs hook checks on the aforementioned functions, similar to the callback Hook check (report type 6), with only the report type differing. FunctionType value is the index of the function in the table.

gDxgkWin32kEngInterface Address Range Check (report type 24)

This performs address range checks on all functions in the gDxgkWin32kEngInterface table, checking whether their addresses are within the win32k module range. Packet structure:

struct PacketWin32kRangeCheckType2 {    // 20 is win32k gDxgkWin32kEngInterface range check    BYTE PacketType;    // function index in the gDxgkWin32kEngInterface table    ULONG Index;    // function address    PVOID Function;    // 64 bytes of the function    BYTE Content[64];};

gDxgkWin32kEngInterface Hook Check (report type 25)

This performs hook checks on the aforementioned functions, similar to the callback Hook check (report type 6), with only the report type differing. FunctionType value is the index of the function in the table.

ext_ms_win_core_win32k_full_export_l1 Address Range Check (report type 33)

This table is unexported, so BE locates it through signature matching, using the BRUSHOBJ_hGetColorTransform function for locating. In this function, the following signature is searched, where addr1 is ext_ms_win_core_win32k_full_export_l1:

mov rax, [addr1]test rax, raxje addr2call qword ptr [addr3]

It checks each function in this table to see if their addresses are within the ranges of win32k and win32kfull modules. If not, abnormal data will be reported. Packet structure:

struct PacketWin32kRangeCheckType3 {    // 33 is win32k ext_ms_win_core_win32k_full_export_l1 range check    BYTE PacketType;    // function index in the ext_ms_win_core_win32k_full_export_l1 table    ULONG Index;    // function address    PVOID Function;    // 64 bytes of the function    BYTE Content[64];};

Dxgkrnl Function Pointer Table Detection

DxgCoreInterface is a function table within the Dxgkrnl module. It may have been used for communication/rendering without modules, or merely as a preventive check.

DxgCoreInterface Address Range Check (report type 12)

This performs address range checks on all functions in the DxgCoreInterface table, checking whether their addresses are within the win32k module range. Packet structure:

struct PacketDxgkrnlRangeCheck {    // 12 is Dxgkrnl DxgCoreInterface range check    BYTE PacketType;    // function index in the DxgCoreInterface table    ULONG Index;    // function address    PVOID Function;    // 64 bytes of the function    BYTE Content[64];};

DxgCoreInterface Hook Check (report type 13)

This performs hook checks on the aforementioned functions, similar to the callback Hook check (report type 6), with only the report type differing.

HAL Function Pointer Table Detection

This ishttps://www.unknowncheats.me/forum/anti-cheat-bypass/335585-communicating-mapped-driver-using.htmlanother communication method mentioned in this article, specifically implementing hooks on functions in the HalDispatchTable, thus BE checks this table. Additionally, BE has found that the HalPrivateDispatchTable can also be hooked, so it has added checks for this table as well.

HalDispatchTable Address Range Check (report type 27)

This performs address range checks on all functions in the HalDispatchTable, checking whether their addresses are within the ranges of ntoskrnl, hal, and other system modules. Packet structure:

struct PacketHalDispatchTableRangeCheck {    // 27 is HalDispatchTable range check    BYTE PacketType;    // function index in the HalDispatchTable table    ULONG Index;    // function address    PVOID Function;    // 64 bytes of the function    BYTE Content[64];};

HalDispatchTable Hook Check (report type 28)

This performs hook checks on the aforementioned functions, similar to the callback Hook check (report type 6), with only the report type differing. FunctionType value is the index of the function in the table.

HalPrivateDispatchTable Address Range Check (report type 29)

This performs address range checks on all functions in the HalPrivateDispatchTable, checking whether their addresses are within the ranges of ntoskrnl, hal, and other system modules. Packet structure:

struct PacketHalPrivateDispatchTableRangeCheck {    // 29 is HalPrivateDispatchTable range check    BYTE PacketType;    // function index in the HalPrivateDispatchTable table    ULONG Index;    // function address    PVOID Function;    // 64 bytes of the function    BYTE Content[64];};

HalPrivateDispatchTable Hook Check (report type 30)

This performs hook checks on the aforementioned functions, similar to the callback Hook check (report type 6), with only the report type differing. FunctionType value is the index of the function in the table.

Dispatch Function Hook Check (report type 5)

When BE loads, it scans all modules in the system, checking each dispatch function of each driver for hooks. It will only check for hooks within the first 64 bytes (only the following two forms) and will only track one jump, not multiple jumps.

  • jmp [addr]

  • mov rax, immjmp rax

Packet structure:

struct PacketDispatchFunctionHookCheck {    // 5 is dispatch function hook check    BYTE PacketType;    // major number    BYTE MajorNumber;    // offset of the hook instructions to the function begin    BYTE HookOffset;    // address of the hook instructions    PVOID HookAddress;    // 16 bytes of the hook instructions    BYTE HookInstructions[16];    // hook function    PVOID HookFunction;    // 64 bytes of the hook function    BYTE Content[64];    // driver name read from the driver object (DriverObject->DriverName)    CHAR DriverName[0];};

Driver Handle Open Failure (report type 10)

This attempts to open Driver objects in the \\Driver and \\FileSystem directories. If it fails to open via ObOpenObjectByName, abnormal data will be reported. Packet structure:

struct PacketOpenDriverObjectFailedCheck {    // 10 is open driver object failed check    BYTE PacketType;    // e.g.: \\Driver\\xxx or \\FileSystem\\xxx    CHAR DriverName[0];    // ObOpenObjectByName status    NTSTATUS Status;};

Game Process Thread Creation Check (report type 3)

This monitors the creation of threads within the game by using thread creation callbacks. If the start address of the thread is not within any game module, it is deemed abnormal, and abnormal data will be reported. It is suspected that this detection is primarily used to detect DLL injection. Packet structure:

struct PacketGameThreadCreateCheck {    // 3 is thread create check    BYTE PacketType;    // start address of the thread being created    PVOID StartAddress;};

5

Summary

1. In all hook checks, only the first 64 bytes are checked, so middle or tail hooks can usually bypass detection better. Additionally, avoid using overly conventional hook unconditional jumps (jmp [addr] / mov rax, imm jmp rax); be creative.

2. The BE kernel driver maintains internal linked lists of processes, drivers, modules, etc., so simple breakage is ineffective. If hidden poorly, inconsistencies in data will also be reported as abnormal data.

3. Since drivers like win32k and dxgkrnl can be used for communication, rendering, etc., and are not controlled by Patch Guard, BE performs additional integrity checks on them.

4. Drivers loaded using tools like kdmapper are a key focus. The BE kernel driver checks whether various functions are at non-module addresses, and the start addresses and stacks of system threads will also be checked.

6

Related Work

1. BattlEye Devirtualization of Kernel Modules

https://www.unknowncheats.me/forum/anti-cheat-bypass/489381-bedaisy-sys-devirtualized.html

This post provides a BE kernel module that has been devirtualized using VTIL, and this reverse engineering work is based on that post.

2. NoVmp

https://github.com/can1357/NoVmp

Using VTIL as a kernel, it implements devirtualization for VMP3. (However, it crashes when used on the latest BE driver.)

3. BE Kernel Driver Reverse Engineering

https://github.com/dllcrt0/bedaisy-reversal

This person has also done an open-source reverse engineering of the BE kernel driver, but the details are somewhat rough and incomplete.

4. BE Shellcode

https://github.com/weak1337/BE-Shellcode

This person has done some high-quality analysis of BE application layer shellcode.

7

Others

The attached file is the reverse-engineered document. Interested parties can take a look at how these detections are specifically implemented. If you find any errors in my analysis, please feel free to point them out.

In-Depth Analysis of the BattlEye Kernel Driver Detection Module

KX ID: Ghost Talent zxy

https://bbs.pediy.com/user-home-749612.htm

*This article is original by KX Forum Ghost Talent zxy, please indicate the source when reprinting from the KX community.

In-Depth Analysis of the BattlEye Kernel Driver Detection Module

# Previous Recommendations

1. PWN from Experiment to Principle of Heap and UAF

2. Analysis of Frida inlineHook Principle and Simple Design of AArch64 inlineHook Tool

3. PWN Learning Notes [Format String Vulnerability Practice]

4. Analysis of Il2Cpp Symbol Recovery Process

5. A Record of Vulnerability Mining in Security Products

6. CVE-2016-3309 Privilege Escalation Vulnerability Learning Notes

In-Depth Analysis of the BattlEye Kernel Driver Detection ModuleIn-Depth Analysis of the BattlEye Kernel Driver Detection Module

Share

In-Depth Analysis of the BattlEye Kernel Driver Detection Module

Like

In-Depth Analysis of the BattlEye Kernel Driver Detection Module

Watch

In-Depth Analysis of the BattlEye Kernel Driver Detection Module

Click “Read the original text” to learn more!

Leave a Comment