
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.).
-
Dispatch Function Integrity Check
-
System Thread Start Address Check
-
Process and Thread Callback Functionality Check
-
Game Process Thread Creation Check
-
PsLookupThreadByThreadId Hook Check
-
[Unknown]
-
Process, Thread, Registry Callback Hook Check
-
Process, Thread, Registry Callback Address Module Range Check
-
PhysicalMemory Reference Check
-
System Call Integrity Check
-
[Unknown]
-
Module Abnormal Instruction Check
-
DxgCoreInterface Address Range Check
-
DxgCoreInterface Hook Check
-
System Thread Stack Check
-
Hidden Driver Check
-
[Unknown]
-
Callback Function Information Reporting
-
BE Driver Integrity Check
-
Module IAT Hook Check
-
gDxgkInterface Address Range Check
-
gDxgkInterface Hook Check
-
Dxgkrnl Internal Unexported Function Range Check (disabled)
-
Infinity Hook Check
-
gDxgkWin32kEngInterface Address Range Check
-
gDxgkWin32kEngInterface Hook Check
-
PCI Device Check
-
HalDispatchTable Address Range Check
-
HalDispatchTable Hook Check
-
HalPrivateDispatchTable Address Range Check
-
HalPrivateDispatchTable Hook Check
-
FltMgrMsg Object Callback Module Range Check
-
FltMgrMsg Object Callback Hook Check
-
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:
-
ConnectNotifyCallback
-
DisconnectNotifyCallback
-
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.

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.

# 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


Share

Like

Watch

Click “Read the original text” to learn more!