BattlEye 通信协议分析

BattlEye(BEService.exe)是一个第三方保护引擎,为了保护电脑上的游戏,就一定会和游戏进程进行通信。
本文的分析是17年11月份的,可能部分协议已经改变,切勿直接使用

分析

Request.ID == 0

  1. 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];
};
  1. BEService -> TslGame_BE
    0x00,一个字节,没啥说的

Request.ID == 3

  1. TslGame_BE -> BEService
    包体长度5 (1+4),包体只有4字节,是当前游戏进程ID,
    这一步服务就可以监视这个游戏进程,游戏进程结束后服务也可以跟着退出了
struct BATTLE_REQUEST_3
{
    BYTE ID;
    DWORD pProcessId;
};

Request.ID == 6

  1. TslGame -> BEService
    这一步开始,和BEService通信的是游戏进程(里面的BEClient.dll)了
    包长度1 ,只有ID 0x06
struct BATTLE_REQUEST_6
{
    BYTE ID;
};
  1. 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

  1. TslGame -> BEService
    pArgument[4]这四个字节有点迷,发现应该是固定的,没搞懂什么意思
struct BATTLE_REQUEST_2
{
    BYTE ID;
    BYTE pArgument[4];
};
  1. BEService -> TslGame
    返回包也是这个样子,固定5字节

Request.ID == 4

  1. TslGame -> BEService
    1个字节,猜测应该是开始检测的通知
struct BATTLE_REQUEST_4
{
    BYTE ID;
};

特征码部分

  1. TslGame -> BEService
    ID4收到之后,会继续接受到几十个包,包内容是各种特征码,数据是 0x66 异或加密的

Request.ID == 4

  1. TslGame -> BEService
    1个字节,代表特征码发完了?
struct BATTLE_REQUEST_4
{
    BYTE ID;
};

循环部分

  1. 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

标签: battleye, pubg

仅有一条评论

  1. 橘

    感觉过hwid ban好麻烦...

添加新评论