BattlEye 通信协议分析
BattlEye(BEService.exe)是一个第三方保护引擎,为了保护电脑上的游戏,就一定会和游戏进程进行通信。
本文的分析是17年11月份的,可能部分协议已经改变,切勿直接使用
分析
Request.ID == 0
- TslGame_BE -> BEService
结构如下,大抵是通知BE: 游戏启动了
struct BATTLE_REQUEST_0
{
BYTE ID;
char nGameNameSize;
wchar_t GameName[20];
char pad[3];
DWORD BattlEye_Launcher_ProcessID;
byte nExecutableNameSize;
wchar_t ExecutableName[20];
wchar_t ServicePath[MAX_PATH];
};
- BEService -> TslGame_BE
0x00,一个字节,没啥说的
Request.ID == 3
- TslGame_BE -> BEService
包体长度5 (1+4),包体只有4字节,是当前游戏进程ID,
这一步服务就可以监视这个游戏进程,游戏进程结束后服务也可以跟着退出了
struct BATTLE_REQUEST_3
{
BYTE ID;
DWORD pProcessId;
};
Request.ID == 6
- TslGame -> BEService
这一步开始,和BEService通信的是游戏进程(里面的BEClient.dll)了
包长度1 ,只有ID0x06
struct BATTLE_REQUEST_6
{
BYTE ID;
};
- BEService -> TslGame
这一步的数据包至关重要!错误的话将直接踢出游戏然后Global Ban #XXXXX
....
包里面是 进程ID + 硬盘序列号(可怕)
数据包加密算法如下(前4字节是密钥,从第五字节开始加密)
auto XorAll_old = [](byte* InBuffer, std::int32_t Key, std::int32_t Size)->bool
{
//std::uint8_t* Temp = reinterpret_cast<std::uint8_t*>(VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
byte Temp[0x1000] = { 0 };
if (Temp == 0)
return false;
std::memcpy(Temp, InBuffer, Size);
int i = 0, d = 0, end = 0, dKey = Key;
while (i + 3 < Size)
{
//printf("%2X %X ", i + 5, *reinterpret_cast<DWORD*>(Temp + i));
*reinterpret_cast<DWORD*>(Temp + i) ^= dKey;
//printf(" -> %X Key: %08X\n", *reinterpret_cast<DWORD*>(Temp + i), dKey);
if (end) break;
d = dKey;
for (int f = 0; f < (i & 0x8000001F); f++)
d /= 2;
if ((static_cast<byte>(d) & 1) == 1)
{
dKey = ~Key; // dKey ^= 0xFFFFFFFF;
}
else
dKey = Key;
d = 0;
i += (reinterpret_cast<byte*>(&dKey)[i & 0x80000003] & 3);
i++;
while (i < Size && i + 3 >= Size) {
i--;
end = 1;
}
}
std::memcpy(InBuffer, Temp, Size);
for (int i = 0; i < Size; i++)
{
InBuffer[i] ^= 0xFF;
}
//VirtualFree(Temp, 0x1000, MEM_RELEASE);
return true;
};
Request.ID == 2
- TslGame -> BEService
pArgument[4]这四个字节有点迷,发现应该是固定的,没搞懂什么意思
struct BATTLE_REQUEST_2
{
BYTE ID;
BYTE pArgument[4];
};
- BEService -> TslGame
返回包也是这个样子,固定5字节
Request.ID == 4
- TslGame -> BEService
1个字节,猜测应该是开始检测的通知
struct BATTLE_REQUEST_4
{
BYTE ID;
};
特征码部分
- TslGame -> BEService
ID4收到之后,会继续接受到几十个包,包内容是各种特征码,数据是0x66
异或加密的
Request.ID == 4
- TslGame -> BEService
1个字节,代表特征码发完了?
struct BATTLE_REQUEST_4
{
BYTE ID;
};
循环部分
- BEService -> TslGame
如果循环发送以下数据,确保游戏正常
ID:5
0x05
ID:2
0x02 0xXX 0xXX 0xXX 0xXX
ID:2
0x02 + 进程ID
源码介绍
BE_Starter_x64.exe
启动器,用于判断 32
or 64
启动相应程序
BE_Proxy_x64.exe
模拟游戏 与 BESerivce.exe
进行通信,用于分析包
Client_BE.exe
创建服务,启动BEService.exe
(伪造的),并监视 TslGame_BE.exe
启动,并在启动后瞬间注入 BE_xBP.dll,
Client_Game_x64.exe
监视 TslGame.exe
启动,并在启动后瞬间注入 BE_xBP_x64.dll
BE_xBP.dll
对 TslGame_BE.exe
进行对用户
和内核
相关API的HOOK
1. 用户(BEClient.dll的一些操作):
a) 线程保护
NtGetContextThread
过硬断检测
NtOpenThread
修改 DesiredAccess 使之不能获取上一步的权限,更保险一些
b) DLL保护
NtQueryVirtualMemory
阻止BEClient.dll检测DLL注入
NtWow64QueryVirtualMemory64
同上
c) 进程保护
NtOpenProcess
阻止进程打开自身,(BE_xBP.dll)注入了,不能被发现
GetWindowThreadProcessId
把他检测的窗口都设置为是自身的进程ID
NtQuerySystemInformation
SystemInformationClass==NT_HANDLE_LIST(16)时,将所有线程的进程ID都设置为自身进程ID
NtReadVirtualMemory
直接返回 0 字节
d) 禁止BEClient.dll
NtReadFile
测试中,未完成,作用:替换BEClient.dll之后,拦截此函数防止发现被替换。
2. 内核(BEClient.dll和内核通信):
a) 替换通信命名管道
NtCreateFile
将 \\??\\pipe\\BattlEye
替换为 \\??\\pipe\\19060
消息在 BEService.exe
中处理
BE_xBP_x64.dll
64位,同 BE_xBP.dll
BEService.exe
这是个伪造的BESerivce,创建了一个 \\??\\pipe\\19060
命名管道用于劫持发往真正的 BEService.exe
的数据
感谢下面这位大佬的代码,参考了很多:
https://www.unknowncheats.me/forum/anti-cheat-bypass/214556-noeye-rootkit-bypass.html
感觉过hwid ban好麻烦...