BattlEye内核驱动检测模块深入分析
本文为看雪论坛精华文章
看雪论坛作者ID:鬼才zxy
一
BattlEye概述
BEService - 与BattlEye服务器通信的服务。
BEDaisy - 内核驱动,执行各种内核层的检测,并与BEClient通信。
BEClient - 一个DLL,运行在游戏进程中,负责执行各种应用层的BE shellcode,并与内核驱动进行通信。
BEServer - BattlEye服务器,收集上传的信息,并判定作弊行为。
二
BattlEye内核驱动检测模块深入分析
三
上传部分
[未知]黑名单特征(upload type 0)
struct AbnormalListItem {
// because nobody writes to report list 0, so some parts of the structure is unknown
BYTE Unknown[10];
BYTE Content[64];
};
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];
};
struct PatternBlackListItemType0 {
// pattern in black list up to 32 bytes
BYTE Pattern[32];
// length up to 32
ULONG Length;
};
48 81 C4 80 01 00 00 5F C3
add rsp, 180h
pop rdi
ret
回调黑名单特征(upload type 1)
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];
};
系统调用黑名单特征(upload type 2)
BE驱动完整性检测特征(upload type 3)
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];
};
struct UploadDxgkrnlInternalFunctionRangeCheck {
// length = upload packet length - 1
BYTE Pattern[0];
// how far is the function address from the pattern matching address
BYTE Offset;
}
InfinityHook检测(upload type 5)
四
检测部分
派遣函数完整性检测
系统线程启动地址检测
进程、线程回调功能性检测
游戏进程线程创建检测
PsLookupThreadByThreadId hook检测
[未知]
进程、线程、注册表回调hook检测
进程、线程、注册表回调地址模块范围检测
PhysicalMemory引用检测
系统调用完整性检测
[未知]
模块异常指令检测
DxgCoreInterface 地址范围检测
DxgCoreInterface hook检测
系统线程堆栈检测
隐藏驱动检测
[未知]
回调函数信息上报
BE驱动完整性检测
模块IAT hook检测
gDxgkInterface 地址范围检测
gDxgkInterface hook检测
Dxgkrnl某内部未导出函数范围检测(disabled)
infinity hook 检测
gDxgkWin32kEngInterface 地址范围检测
gDxgkWin32kEngInterface hook检测
PCI设备检测
HalDispatchTable 地址范围检测
HalDispatchTable hook检测
HalPrivateDispatchTable 地址范围检测
HalPrivateDispatchTable hook检测
FltMgrMsg对象callback模块范围检测
FltMgrMsg对象callback hook检测
ext_ms_win_core_win32k_full_export_l1 地址范围检测
...
BE驱动完整性检测(report type 18)
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];
};
系统调用完整性检测(report type 9)
函数指针修改
函数地址不在模块范围内(手动映射的驱动的hook)
追踪跳转后,函数地址不在模块范围内(类似上一个异常情况)
存在int 3断点,说明系统正在被调试
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];
};
系统线程启动地址检测(report type 1)
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;
// CountdonwId = 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;
};
系统线程堆栈检测(report type 14)
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;
// CountdonwId = 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];
};
进程、线程、注册表回调检测
回调Hook检测(report type 6)
FF 25 XX XX XX XX: jmp [addr]
48 B8 XX XX XX XX XX XX XX XX: mov rax, imm
FF E0: jmp rax
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
BTYE 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];
};
回调地址模块范围检测(report type 7)
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];
};
回调函数信息上报(report type 17)
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引用检测(report type 8)
struct PacketPhysicalMemoryReferenceCheck {
// 8 is physical memory reference check
BYTE PacketType;
// fields in struct _CONTROL_AREA
ULONG64 NumberOfSectionReferences;
ULONG64 NumberOfPfnReferences;
ULONG64 NumberOfMappedViews;
ULONG64 NumberOfUserReferences;
};
进程、线程回调功能性检测(report type 2)
struct PacketProcessThreadCallbackFunctionalityCheck {
// 2 is process thread callback functionality check
BYTE PacketType;
// probably always true
BOOLEAN Abnormal;
};
派遣函数地址检测(report type 0)
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检测(report type 4)
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对象检测
ConnectNotifyCallback
DisconnectNotifyCallback
MessageNotifyCallback
FltMgrMsg对象callback模块范围检测(report type 31)
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对象callback hook检测(report type 32)
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
BTYE 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某内部未导出函数范围检测(report type 22)(disabled)
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 检测(report type 23)
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];
};
系统模块检测
hal.dll
clipsp.sys
CI.dll
tpm.sys
ks.sys
cdd.dll
TSDDD.dll
spsys.sys
atikmpag.sys
模块异常指令检测(report type 11)
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];
};
模块IAT hook检测(report type 19)
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];
};
隐藏驱动检测(report type 15)
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设备检测(report type 26)
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函数指针表检测
gDxgkInterface 地址范围检测(report type 20)
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检测(report type 21)
gDxgkWin32kEngInterface 地址范围检测(report type 24)
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检测(report type 25)
ext_ms_win_core_win32k_full_export_l1 地址范围检测(report type 33)
mov rax, [addr1]
test rax, rax
je addr2
call qword ptr [addr3]
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 函数指针表检测
DxgCoreInterface 地址范围检测(report type 12)
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检测(report type 13)
HAL 函数指针表检测
HalDispatchTable 地址范围检测(report type 27)
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检测(report type 28)
HalPrivateDispatchTable 地址范围检测(report type 29)
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检测(report type 30)
派遣函数 hook检测(report type 5)
jmp [addr]
mov rax, imm
jmp rax
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];
};
驱动句柄打开失败(report type 10)
struct PacketOpenDriverObjectFailedCheck {
// 10 is open driver object failed check
BYTE PacketType;
// eg:\\Driver\\xxx or \\FileSystem\\xxx
CHAR DriverName[0];
// ObOpenObjectByName status
NTSTATUS Status;
};
游戏进程线程创建检测(report type 3)
struct PacketGameThreadCreateCheck {
// 3 is thread create check
BYTE PacketType;
// start address of the thread being created
PVOID StartAddress;
};
五
总结
1、在所有hook检测中只检测了头部的64字节,因此中部hook或者尾部hook通常可以更好的绕过检测,并且不要使用过于常规的hook无条件跳转(jmp [addr] / mov rax, imm jmp rax),请尽情发挥你的想象
2、BE内核驱动会维护内部的进程、驱动、模块等链表,因此如果使用简单的断链是没有用的,并且如果隐藏的不好,出现了数据的不一致性,“隐藏”这一行为也会被当做异常数据上报
3、由于win32k、dxgkrnl等驱动可以用于无模块通信、绘制等用途,并且不受Patch Guard管控,因此BE对其进行了额外的完整性检查
4、通过kdmapper等工具加载的驱动是重点关注对象,BE内核驱动会检查各种函数是否是无模块地址、并且系统线程的起始地址、堆栈也会被检查
六
相关工作
1、BattlEye去虚拟化内核模块
https://www.unknowncheats.me/forum/anti-cheat-bypass/489381-bedaisy-sys-devirtualized.html
这个帖子给出了一个使用VTIL脱掉VMP壳的BE内核模块,本次逆向工作就是在这个帖子的基础之上完成的。
2、NoVmp
https://github.com/can1357/NoVmp
使用VTIL作为内核,实现了给VMP3脱壳。(但是用在最新版的BE驱动上会崩溃)
3、BE内核驱动逆向
https://github.com/dllcrt0/bedaisy-reversal
这个人也做了个开源的BE内核驱动的逆向,但是细节稍有些粗糙,并且不全。
4、BE shellcode
https://github.com/weak1337/BE-Shellcode
这个人做了对BE应用层的一些shellcode的分析,质量很高。
七
其他
看雪ID:鬼才zxy
https://bbs.pediy.com/user-home-749612.htm
# 往期推荐
2.Frida inlineHook原理分析及简单设计一款AArch64 inlineHook工具
球分享
球点赞
球在看
点击“阅读原文”,了解更多!