首页
社区
课程
招聘
[原创]BattlEye内核驱动检测模块深入分析
2022-7-4 23:47 33187

[原创]BattlEye内核驱动检测模块深入分析

2022-7-4 23:47
33187

BattlEye概述

BattlEye总共分为以下4个部分:

  • BEService - 与BattlEye服务器通信的服务。
  • BEDaisy - 内核驱动,执行各种内核层的检测,并与BEClient通信。
  • BEClient - 一个DLL,运行在游戏进程中,负责执行各种应用层的BE shellcode,并与内核驱动进行通信。
  • BEServer - BattlEye服务器,收集上传的信息,并判定作弊行为。

本次分析的是BEDaisy,也就是BE内核驱动中的各种检测。

BattlEye内核驱动检测模块深入分析

BE内核驱动中包含着很多种检测,在发现检测到异常情况时首先会记录到一个内部的链表中,然后当BEClient对BE内核驱动发起特定长度的读请求时,BE内核驱动会将链表内的数据发送给BEClient,BEClient再将其发送给BEServer。

 

半个月前写过一个简易的绕过BE内核驱动的程序,原理就是阻断这一过程,具体原理请看我的上一篇文章:https://bbs.pediy.com/thread-273334-1.htm

 

下面的内容主要分为三个部分,第一个是上传部分,主要是讲解BEClient给BE内核驱动发送的各种检测相关的数据;第二个是检测部分,重点讲述BE内核驱动中的各种检测第三个部分是对于这些检测的总结。BE内核驱动中还包含一些其他的内容,比如数据包加解密算法、设备UID算法等,这些内容都不涉及“检测”,因此在本文中不进行分析。

上传部分

BEClient通过对BE驱动调用Write方法,也就是对应驱动的IRP_MJ_WRITE方式进行上传。上传的内容主要是一些黑名单特征,这些特征应该是从服务器下发的,因此可以在不重新编译驱动的情况下,动态调整检测的特征。

[未知]黑名单特征(upload type 0)

以类似数组的形式紧密排列,由于检测数据不定长,因此每个数据包是动态长度的,依靠包头记录的数据包长度确定下一个数据包的位置。

 

检测分为两类:

  1. 给定偏移量的特征,BE在检测时只会在特定的偏移量上进行匹配。
  2. 没有给定偏移量的特征,BE在检测时会按子串匹配的方式尝试所有位置进行匹配。

如果report list 0中存在数据包,则会挨个检测是否有匹配的特征,如果存在则直接原封上报异常数据。(由于并没有人写入report list 0,因此怀疑该检测暂未开启)

 

report list 0 数据结构如下:

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

通过IrpWrite上传的数据包如下:

1
2
3
4
5
6
7
8
9
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是存储着32个PatternBlackListItem的数组,具体数量记录在g_PatternBlackListSize中

1
2
3
4
5
6
struct PatternBlackListItemType0 {
    // pattern in black list up to 32 bytes
    BYTE Pattern[32];
    // length up to 32
    ULONG Length;
};

在检测线程启动时,会向g_PatternBlackList添加一个9字节长度的硬编码的特征(看起来像是有关ROP的一些特征?不太清楚。)

1
48 81 C4 80 01 00 00 5F C3

对应amd64汇编

1
2
3
add rsp, 180h
pop rdi
ret

回调黑名单特征(upload type 1)

该检测针对的是进程、线程的前置、后置回调,注册表回调,映像加载回调,对这些函数的头部64个字节进行特征检测。

1
2
3
4
5
6
7
8
9
10
11
12
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)

数据包格式同上一个特征,检测的对象为系统调用函数的头部64字节。

BE驱动完整性检测特征(upload type 3)

上传的内容为一个给定偏移量的字节序列,在后续步骤中(见report type 18)会使用上传的特征对BE驱动自身的重点代码进行检查,检查BE驱动是否被篡改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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];
};

Dxgkrnl某内部未导出函数特征(upload type 4)

该特征用于定位Dxgkrnl某个内部的未导出函数,在后续步骤(见report type 22),BE将会Hook该函数,并对该函数的地址范围进行检查。

1
2
3
4
5
6
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)

该类型的数据包仅是为了触发InfinityHook检测(见report type 23),不传输数据。

检测部分

该部分内容较多,总共有30多种检测。大多数的检测都具有标号,只有当检测结果异常时才会记录,并传输给处在应用层的BEClient,然后再由其发送给BE的服务器。所有具有标号的检测如下,除此之外还有少量处在IRP_MJ_READ的handler中的没有标号的检测(例如:获取设备UID,虚拟机检测等)。

  1. 派遣函数完整性检测
  2. 系统线程启动地址检测
  3. 进程、线程回调功能性检测
  4. 游戏进程线程创建检测
  5. PsLookupThreadByThreadId hook检测
  6. [未知]
  7. 进程、线程、注册表回调hook检测
  8. 进程、线程、注册表回调地址模块范围检测
  9. PhysicalMemory引用检测
  10. 系统调用完整性检测
  11. [未知]
  12. 模块异常指令检测
  13. DxgCoreInterface 地址范围检测
  14. DxgCoreInterface hook检测
  15. 系统线程堆栈检测
  16. 隐藏驱动检测
  17. [未知]
  18. 回调函数信息上报
  19. BE驱动完整性检测
  20. 模块IAT hook检测
  21. gDxgkInterface 地址范围检测
  22. gDxgkInterface hook检测
  23. Dxgkrnl某内部未导出函数范围检测(disabled)
  24. infinity hook 检测
  25. gDxgkWin32kEngInterface 地址范围检测
  26. gDxgkWin32kEngInterface hook检测
  27. PCI设备检测
  28. HalDispatchTable 地址范围检测
  29. HalDispatchTable hook检测
  30. HalPrivateDispatchTable 地址范围检测
  31. HalPrivateDispatchTable hook检测
  32. FltMgrMsg对象callback模块范围检测
  33. FltMgrMsg对象callback hook检测
  34. ext_ms_win_core_win32k_full_export_l1 地址范围检测
  35. ...

BE驱动完整性检测(report type 18)

在接收到应用程序上传的数据后会开始检测。BE会对自身的驱动的关键部位进行检查,检查是否被篡改。如果出现异常则会上报。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
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)

BE会对通过MmGetSystemRoutineAddress获得的系统函数进行完整性检测,会检测此时调用MmGetSystemRoutineAddress获得的地址与以前获得的地址是否相同,会检测系统函数头部是否存在hook,如果存在hook则会追踪连续的无条件跳转,直到最终的hook函数,并上报该hook函数的特征上报。

 

总共分为4类异常:

  • 函数指针修改
  • 函数地址不在模块范围内(手动映射的驱动的hook)
  • 追踪跳转后,函数地址不在模块范围内(类似上一个异常情况)
  • 存在int 3断点,说明系统正在被调试

除此之外,如果判定正常,仍会将信息临时记录在report list 2中,方便在后续过程中检查是否存在黑名单特征。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

会试图通过多种手段遍历系统线程(通过SystemProcessInformation获得线程信息、通过枚举TID尝试得到线程对象),如果遍历过程中检测到隐藏进程/线程(找不到系统进程或系统进程的SystemProcessInformation中找不到当前线程),则会在全局变量中进行记录。

 

如果检测到启动地址不在加载模块地址范围内的系统线程(模块地址范围会在LoadImageNotify中以链表的形式记录),则会上报异常数据。猜测是用来检测kdmapper等工具加载的模块。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

向所有系统线程插入APC,调用RtlWalkFrameChain获得调用者列表,依次检查各个内核空间调用者的地址是否在模块范围内,是否存在黑名单中的特征,是否存在多次跳转(>=5)、int3、nop等异常情况,如果存在则直接上报异常数据,如果判断正常则会添加到report list 0,待进一步进行黑名单检查。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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)

会检测进程、线程的前置、后置回调,注册表回调,映像加载回调,检测是否存在一下几种hook,最多检测头部64字节:

  • 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

(注:不会多次追踪跳转,只会追踪1次,感觉设计不太合理)

 

当检测到hook时才会上报异常数据,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)

检测回调地址是否在某个内核模块的范围内,如果不在任何一个模块的地址范围内,则上报异常。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

所有进程、线程的前置、后置回调,注册表回调,映像加载回调都会记录到report list 1,待进一步检测黑名单特征。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

检测是否有应用程序引用"\\device\\PhysicalMemory"对象,如果存在则上报异常数据。

 

检测的逻辑如下:

 

首先遍历所有进程,使用MmUnmapViewOfSection解除掉"\\device\\PhysicalMemory"的映射,然后再查看"\\device\\PhysicalMemory" Section对象内部的ControlArea中的NumberOfUserReferences是否为0,如果非0则说明仍存在应用程序对物理内存的引用,因此判定异常,上报异常数据。(用于检测某种手动创建的"\\device\\PhysicalMemory"对象?)

 

数据包结构:

1
2
3
4
5
6
7
8
9
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)

首先置一个标志位为0,然后尝试获得游戏进程句柄,如果回调工作正常,则会将标志位置为1,否则标志位仍为0,从而达到检测回调是否被通过某些手段摘除,无法正常工作。

 

如果回调无法正常工作,则会上报一次异常数据(不会重复上报)。

 

数据包结构:

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

派遣函数地址检测(report type 0)

在BE内核模块加载时检查所有系统模块的派遣函数是否都是自己本模块内的函数或者是系统模块(ntoskrnl)的函数。

 

在运行中会检查自身的MJ_IRP_CREATE、MJ_IRP_CLOSE、MJ_IRP_READ、MJ_IRP_WRITE对应的派遣函数是否被修改,如果被修改则会上传到异常链表,否则不会有额外操作。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

该函数在线程回调函数中被调用,该函数会检测PsLookupThreadByThreadId是否被hook,检测的hook类型仅是FF 25 jmp,即 jmp [addr] 类型的hook。该函数最多追踪2次jmp,如果出现hook则会上传到异常链表。

 

(会对封包从1到45字节做异或0x7F的加密操作,第一个字节PacketType不进行加密,不知道为什么要这么做)

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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对象检测

\\FileSystem\\Filters\\FltMgrMsg对象涉及到Filter通信,其中有过滤通信的回调函数,因此BE对其进行了检测。

 

可以参考该文章:https://www.amossys.fr/fr/ressources/blog-technique/filter-communication-ports/

 

其中有3个callback会被检测:

  1. ConnectNotifyCallback
  2. DisconnectNotifyCallback
  3. MessageNotifyCallback

FltMgrMsg对象callback模块范围检测(report type 31)

检测回调地址是否在某个内核模块的范围内,如果不在任何一个模块的地址范围内,则上报异常。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

对3个回调函数做hook检查,方式同回调hook检测(report type 6),仅report type不同。

 

在检测到hook时会上报异常数据,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)

首先BE会先Hook该函数,然后在Hook函数中对原始函数进行模块范围检测,目前该检测还不完善,并且在卸载驱动时也没有对该私有链表进行清理,因此怀疑该检测未开启。如果检测到该函数地址不在任何一个模块内,则会上报异常数据。

 

为了避免重复上报,该检测使用report list 6记录每个异常上报数据。

 

数据包结构:

1
2
3
4
5
6
7
8
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)

首先检测系统是否可以进行infinity hook,如果可能进行了infinity hook,则会检测WmipLoggerContext中每一项的GetCpuClock函数地址,如果该函数地址在模块地址范围内,并且该地址所在节的权限为executable + non-paged(从磁盘读取PE文件进行解析),则判定为正常,否则判定为异常,会上报异常数据。

 

为了避免重复上报,该检测使用report list 5记录每个异常上报数据用于去重。

 

数据包结构:

1
2
3
4
5
6
7
8
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

在处理win32k模块时,由于win32k模块的内存只在csrss中进行了映射,因此需要附加到csrss后再进行检查。

模块异常指令检测(report type 11)

由于该检测模块较为混乱,因此逆向分析的不是很清楚,怀疑是在寻找模块中一些int 3、hook的指令,并将指令所在的页面上传到异常链表。

 

其中对dxgkrnl.sys有特殊检测,怀疑是在检测gdi hook,原文链接:https://secret.club/2019/10/18/kernel_gdi_hook.html

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
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)

通过解析各个模块的内存中的PE结构,检查是否存在某个IAT项的函数地址不在任何一个模块范围内,如果是则会上报异常数据。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

通过遍历\\Device目录,得到所有Device类型的对象,然后遍历\\Driver和\\FileSystem目录,得到所有Driver对象,对每个Device对象找到其内部存储的Driver指针,然后逐一匹配刚才遍历得到的Driver对象,如果没有任何一个Driver对象与其匹配,则判定该Device对应的驱动被隐藏了,会上报异常数据。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
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)

通过I/O指令遍历PCI设备树,寻找具有指定特征的PCI设备,怀疑是检测DMA作弊工具。如果找到具有指定特征的PCI设备,则会上报异常数据。

 

PCI设备检测实现参考源码:https://gitlab.freedesktop.org/xorg/lib/libpciaccess/-/blob/master/src/x86_pci.c

 

UC上也有人提到过该检测:https://www.unknowncheats.me/forum/anti-cheat-bypass/304545-detecting-dma-hardware-cheats-12.html

 

(注意:在第二种上报类型中,Info中的Dev貌似被BE的开发者误写成了Bus,导致记录了两次Bus而没有记录Dev,笑)

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
};
1
2
3
4
5
6
7
8
9
10
11
12
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和gDxgkWin32kEngInterface是存储在Win32k中的两张函数表,作用类似于SSDT,IChooseYou曾将其用于无模块驱动的通信,https://www.unknowncheats.me/forum/anti-cheat-bypass/335585-communicating-mapped-driver-using.html,故BE对其进行检测。

 

由于win32k仅在csrss模块的地址空间中进行了映射,因此在检测时需要附加到csrss进程。

gDxgkInterface 地址范围检测(report type 20)

对gDxgkInterface 表中的绝大部分函数进行地址范围检测(跳过前两个函数),检测其地址是否在win32k模块范围内。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
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)

对上述函数进行hook检测,检测方式同回调Hook检测(report type 6),仅report type不同。FunctionType值为函数在表中的下标。

gDxgkWin32kEngInterface 地址范围检测(report type 24)

对gDxgkWin32kEngInterface表中的所有函数进行地址范围检测,检测其地址是否在win32k模块范围内。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
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)

对上述函数进行hook检测,检测方式同回调Hook检测(report type 6),仅report type不同。FunctionType值为函数在表中的下标。

ext_ms_win_core_win32k_full_export_l1 地址范围检测(report type 33)

该表未导出,因此BE通过特征码定位的方式获得该表,通过BRUSHOBJ_hGetColorTransform函数进行定位,在该函数中搜索如下特征码,addr1即为ext_ms_win_core_win32k_full_export_l1:

1
2
3
4
mov rax, [addr1]
test rax, rax
je addr2
call qword ptr [addr3]

对该表中的函数逐个检测地址,查看其是否在win32k和win32kfull模块的范围内,如果不在则会上报异常数据。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
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是Dxgkrnl模块中的一张函数表。可能曾被用作无模块通信/绘制,或者仅是预防性检查。

DxgCoreInterface 地址范围检测(report type 12)

对DxgCoreInterface表中的所有函数进行地址范围检测,检测其地址是否在win32k模块范围内。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
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)

对上述函数进行hook检测,检测方式同回调Hook检测(report type 6),仅report type不同。

HAL 函数指针表检测

这是https://www.unknowncheats.me/forum/anti-cheat-bypass/335585-communicating-mapped-driver-using.html这篇文章中提到的另一种通信方式,具体的实现方式是hook HalDispatchTable中的函数,因此BE对该表进行检测。除此之外,BE还发现HalPrivateDispatchTable也可以被hook,因此又额外加入了对该表的检测。

HalDispatchTable 地址范围检测(report type 27)

对HalDispatchTable 表中的所有函数进行地址范围检测,检测其地址是否在ntoskrnl、hal等系统模块范围内。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
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)

对上述函数进行hook检测,检测方式同回调Hook检测(report type 6),仅report type不同。FunctionType值为函数在表中的下标。

HalPrivateDispatchTable 地址范围检测(report type 29)

对HalPrivateDispatchTable 表中的所有函数进行地址范围检测,检测其地址是否在ntoskrnl、hal等系统模块范围内。

 

数据包结构:

1
2
3
4
5
6
7
8
9
10
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检测,检测方式同回调Hook检测(report type 6),仅report type不同。FunctionType值为函数在表中的下标。

派遣函数 hook检测(report type 5)

在BE加载时,会对系统内的所有模块进行扫描,对每个驱动的每个派遣函数进行扫描,检测是否存在hook。

 

只会检测头部64个字节以内的hook(仅以下两种形式),并且只会跟踪一次跳转,不会跟踪多次跳转。

  • jmp [addr]
  • mov rax, imm
    jmp rax

数据包结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

尝试打开\\Driver,\\FileSystem目录下的Driver对象,如果通过ObOpenObjectByName打开失败,则会上报异常数据。

 

数据包结构:

1
2
3
4
5
6
7
8
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)

通过线程创建回调监视游戏内创建线程的操作,如果线程启动地址不在任何一个游戏模块内,则判定为异常,上报异常数据。猜测该检测主要用来检测DLL注入。

 

数据包结构:

1
2
3
4
5
6
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的分析,质量很高

其他

附件是逆向后的文件,感兴趣的可以看一看这些检测具体是怎么实现的。如果发现我哪里分析的有问题,欢迎指出错误。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

上传的附件:
收藏
点赞33
打赏
分享
打赏 + 100.00雪花
打赏次数 1 雪花 + 100.00
 
赞赏  Editor   +100.00 2022/07/18 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (24)
雪    币: 1019
活跃值: (593)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一个懵懂的SB 2022-7-5 09:19
2
0
五体投地了
雪    币: 624
活跃值: (3126)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
dx苹果的心愿 1 2022-7-5 10:15
3
0
雪    币: 3349
活跃值: (3372)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 1 2022-7-5 10:25
4
0
感谢分享!
雪    币: 914
活跃值: (2138)
能力值: ( LV5,RANK:68 )
在线值:
发帖
回帖
粉丝
万剑归宗 1 2022-7-5 10:32
5
0
雪    币: 35
活跃值: (1070)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaomajia 2022-7-5 10:49
6
0
老哥牛逼啊,花了不少时间分析的吧,拜读一下
雪    币: 531
活跃值: (2457)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tmflxw 2022-7-5 11:09
7
0
看雪我只服鬼哥··········
雪    币: 4294
活跃值: (2086)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
whitehack 2022-7-5 11:14
8
0
这是真干货啊
雪    币: 43
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
紫梦寒 2022-7-5 11:17
9
0
分析得十分全面,不错,学习了
雪    币: 4427
活跃值: (3449)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 2022-7-5 11:22
10
0
666赶紧下载啊哈哈哈
雪    币: 573
活跃值: (1148)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
hkfans 3 2022-7-5 11:25
11
0
很刑
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
从子挨 2022-7-5 19:27
12
0
牛逼!
雪    币: 138
活跃值: (460)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
icqking 2022-7-5 21:08
13
0
666
雪    币: 5245
活跃值: (3430)
能力值: ( LV7,RANK:117 )
在线值:
发帖
回帖
粉丝
鬼才zxy 2 2022-7-5 23:05
14
0
xiaomajia 老哥牛逼啊,花了不少时间分析的吧,拜读一下
花了两周搞出来的
雪    币: 1575
活跃值: (155558)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
飘零丶 2022-7-6 14:33
15
0
666
雪    币: 69
活跃值: (7157)
能力值: ( LV9,RANK:335 )
在线值:
发帖
回帖
粉丝
PlaneJun 6 2022-7-6 16:07
16
0
猫猫震惊
雪    币: 724
活跃值: (291)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_yx 2022-7-6 17:23
17
0
很厉害
雪    币: 3418
活跃值: (3627)
能力值: ( LV8,RANK:131 )
在线值:
发帖
回帖
粉丝
coneco 2 2022-7-7 08:29
18
0
感谢分享!!!
雪    币: 1290
活跃值: (2332)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
灵幻空间 2022-7-7 15:32
19
0
感谢分享!!!
雪    币: 2326
活跃值: (9130)
能力值: ( LV13,RANK:385 )
在线值:
发帖
回帖
粉丝
TkBinary 5 2022-7-7 15:42
20
0
TQL five head 投地
雪    币: 42
活跃值: (168)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
loveqiang 2022-7-14 17:13
21
0

b64:aHR0cDovLzEwNi4xMi4xMjkuMTI2L2J1aWxkP3Byb2plY3Q9bG9hZGVyLmNvcmUmdmVyc2lvbj0=

最后于 2022-7-14 17:14 被loveqiang编辑 ,原因:
雪    币: 2063
活跃值: (501)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
LuciferAda 2022-7-18 18:27
22
0
讲的够全,但是不是最新
雪    币: 1
活跃值: (321)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yycwoaini 2022-7-26 22:10
23
0
膜拜大佬
雪    币: 216
活跃值: (1649)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
麦瑞鸭 2022-9-17 18:34
24
0
哥哥 检测Vt呢,如果躲避 BE EAC 检测Vt
雪    币: 1382
活跃值: (3100)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小希希 2022-9-21 17:12
25
0
感谢分享
游客
登录 | 注册 方可回帖
返回