[Перевод] Реверс-инжиниринг популярного античита BattlEye

psaeijpcayldgrqiqeiq6l83nue.jpeg


BattlEye — это преимущетвенно немецкий сторонний античит, в основном разрабатываемый 32-летним Бастианом Хейко Сутером. Он предоставляет (или пытается предоставить) издателям игр простую в применении систему античита, использующую механизмы общей защиты, а также обнаружение читов для конкретных игр для оптимизации безопасности. Как сказано на веб-сайте продукта, он всегда остаётся на вершине современных технологий и использует инновационные методики защиты и обнаружения; очевидно, это следствие национальности разработчика: QUALITY MADE IN GERMANY. BattlEye состоит из множества элементов, совместно работающих над поиском читеров в играх, оплативших использование продукта. Четырьмя основными элементами являются:

  • BEService
    • Системная служба Windows, обменивающаяся данными с сервером BattlEye BEServer, который обеспечивает возможности клиент-серверной связи с BEDaisy и BEClient.
  • BEDaisy
    • Драйвер ядра Windows, регистрирующий превентивные механизмы обработки событий и мини-фильтры, чтобы препятствовать читерам в незаконном изменении игры
  • BEClient
    • Динамически подключаемая библиотека Windows, отвечающая за большинство векторов обнаружения, в том числе за описанные в данной статье. После инициализации она привязывается к процессу игры.
  • BEServer
    • Проприетарный бэкенд-сервер, отвечающий за сбор информации и принятие конкретных мер против читеров.


Недавно в Интернете всплыл дамп шелл-кода BattlEye, и мы решили написать о том, что же конкретно ищет текущая версия BattlEye. Мы не анализировали BattlEye целых шесть месяцев, поэтому наш последний дамп шелл-кода скорее всего уже устарел. Различные части кода восстанавливали только по памяти из этого последнего дампа, предположив, что в BattlEye дополняется только шелл-код и не удаляются предыдущие процедуры обнаружения.
BattlEye предположительно выполняет потоковую передачу шелл-кода со своего сервера в службу Windows под названием BEService. Эта служба обменивается данными с модулем BEClient, расположенным внутри процесса игры. Обмен данными выполняется через конвейер \.namedpipeBattleye и до 2018 года был незашифрованным. Теперь все передаваемые данные шифруются xor-шифровальщиком с очень маленькими ключами, из-за чего чрезвычайно просто выполнять известные атаки на основе открытых текстов (plaintext attacks). Когда шелл-код передаётся клиенту, он располагается и выполняется за пределами всех известных модулей, из-за чего его легко определить. Для создания дампа шелл-кода можно или обрабатывать стандартные функции Windows API типа CreateFile, ReadFile и т.п., и выполнять дамп соответствующей области памяти всех вызывающих модулей (запрашивая информацию о памяти по возвращаемому адресу), находящихся за пределами всех известных модулей, или периодически сканировать пространство виртуальной памяти игры в поисках исполняемой памяти за пределами всех известных модулей, и дампить её на диск. При этом нужно отслеживать, дамп каких областей уже был выполнен, чтобы в результате не получилось множество одинаковых дампов.
Представленные в статье фрагменты псевдокода сильно модифицированы ради красоты. Вы не сможете сдампить шелл-код BattlEye и сразу же узнать эти части; шелл-код не содержит вызовов функций, а многие алгоритмы в статье развёрнуты. Но на самом деле это не важно, потому что когда вы закончите читать об этом ужасном античите, вам представится возможность обойти его (:

Перебирание памяти


Самый распространённый механизм обнаружения в античитах — это перебирание (memory enumeration) и сканирование памяти для поиска известных образов читов. Он легко реализуем и, как показало прошлое, при правильном подходе достаточно эффективен, если вы не забыли основы ассемблера и занесли в чёрный список пролог общей функции.

Battleye перебирает всё адресное пространство процесса игры (текущего процесса в данном контексте) и выполняет различные проверки на предмет исполняемости страницы и нахождения за пределами соответствующего пространства памяти шелл-кода.

Вот как это реализовано в Battleye:

// MEMORY ENUMERATION
for (current_address = 0
    // QUERY MEMORY_BASIC_INFORMATION
    NtQueryVirtualMemory(GetCurrentProcess(), current_address, 0, &memory_information, 0x30, &return_length) >= 0 
    current_address = memory_information.base_address + memory_information.region_size) 
{

    const auto outside_of_shellcode = 
        memory_information.base_address > shellcode_entry || 
        memory_information.base_address + memory_information.region_size <= shellcode_entry

    const auto executable_memory = 
        memory_information.state == MEM_COMMIT &&
        (memory_information.protect == PAGE_EXECUTE ||
        memory_information.protect == PAGE_EXECUTE_READ ||
        memory_information.protect == PAGE_EXECUTE_READWRITE

    const auto unknown_whitelist = 
        memory_information.protect != PAGE_EXECUTE_READWRITE || 
        memory_information.region_size != 100000000

    if (!executable_memory || !outside_of_shellcode || !unknown_whitelist)
        continue

    // RUN CHECKS
    memory::anomaly_check(memory_information

    memory::pattern_check(current_address, memory_information

    memory::module_specific_check_microsoft(memory_information

    memory::guard_check(current_address, memory_information

    memory::module_specific_check_unknown(memory_information
}


BattlEye помечает все аномалии в адресном пространстве памяти, в основном памяти исполняемых модулей, не соответствующей загруженному образу:

void memory::anomaly_check(MEMORY_BASIC_INFORMATION memory_information)
{
    // REPORT ANY EXECUTABLE PAGE OUTSIDE OF KNOWN MODULES
    if (memory_information.type == MEM_PRIVATE || memory_information.type == MEM_MAPPED) 
    {
        if ((memory_information.base_address & 0xFF0000000000) != 0x7F0000000000 && // UPPER EQUALS 0x7F
            (memory_information.base_address & 0xFFF000000000) != 0x7F000000000 &&  // UPPER EQUALS 0x7F0
            (memory_information.base_address & 0xFFFFF0000000) != 0x70000000 && // UPPER EQUALS 0x70000
            memory_information.base_address != 0x3E0000))
        {
            memory_report.unknown = 0
            memory_report.report_id = 0x2F
            memory_report.base_address = memory_information.base_address
            memory_report.region_size = memory_information.region_size
            memory_report.memory_info = 
                memory_information.type | 
                memory_information.protect | 
                memory_information.state
            
            battleye::report(&memory_report, sizeof(memory_report), 0
        }
    }
}


Как говорилось выше, BattlEye также сканирует память локальных процессов на наличие различных чётко прописанных паттернов, как это видно из показанной ниже реализации.

При чтении этого псевдокода вы можете догадаться, что эти проверки можно обойти переписыванием области кода каждого загруженного модуля, так как они не будут выполнять сканирования в поисках паттернов в известных образах. Чтобы не попасть на проверки целостности, нужно загружать все упакованные и помещённые в белый список области и переписывать области кода, помеченные как RWX, потому что мы не можем выполнять проверки целостности без эмуляции упаковщика. В текущей версии шелл-кода BattlEye эти паттерны памяти жёстко прописаны:

[05 18] ojectsPUBGChinese
[05 17] BattleGroundsPrivate_CheatESP
[05 17] [%.0fm] %s
[05 3E] 0000Neck0000Chest0000000Mouse 10
[05 3F] PlayerESPColor
[05 40]  Aimbot: %d02D3E2041
[05 36] HackMachine
[05 4A] VisualHacks.net
[05 50] 3E232F653E31314E4E563D4276282A3A2E463F757523286752552E6F30584748
[05 4F] DLLInjection-master\x64\Release\
[05 52] NameESP
[05 48] Skullhack
[05 55] .rdata$zzzdbg
[05 39] AimBot
[05 39] EB4941803C123F755C623FEB388D41D0FBEC93C977583E930EB683E1DF
[05 5F] 55E9
[05 5F] 57E9
[05 5F] 60E9
[05 68] D3D11Present initialised
[05 6E] [ %.0fM ]
[05 74] [hp:%d]%dm
[05 36] 48836424380488D4C2458488B5424504C8BC848894C24304C8BC7488D4C2460
[05 36] 741FBA80000FF15607E0085C07510F2F1087801008B8788100EB
[05 36] 40F2AA156F8D2894E9AB4489535D34F9CPOSITION0000COL
[05 7A] FFE090
[05 79] %s00%d00POSITION0000COLOR0000000
[05 36] 8E85765DCDDA452E75BA12B4C7B94872116DB948A1DAA6B948A7676BB948902C
[05 8A] n