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

Original author: Carl Schou
  • Translation

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<assembly xmlsn='urn:schemas-mi

Эти паттерны памяти также содержат двухбайтный заголовок, а именно неизвестное статическое значение 05 и уникальный идентификатор.

Чего мы не увидим, так это того, что BattlEye также динамически выполняет потоковую передачу паттернов из BEServer и отправляет их в BEClient, но в статье мы рассматривать это не будем.

Они итеративно сканируются следующим алгоритмом:

void memory::pattern_check(void* current_address, MEMORY_BASIC_INFORMATION memory_information)
{
    const auto is_user32 = memory_information.allocation_base == GetModuleHandleA("user32.dll"
    
    // ONLY SCAN PRIVATE MEMORY AND USER32 CODE SECTION
    if (memory_information.type != MEM_PRIVATE && !is_user32) 
        continue

    for (address = current_address 
         address != memory_information.base_address + memory_information.region_size 
         address += PAGE_SIZE) // PAGE_SIZE
    {
        // READ ENTIRE PAGE FROM LOCAL PROCESS INTO BUFFER
        if (NtReadVirtualMemory(GetCurrentProcess(), address, buffer, PAGE_SIZE, 0) < 0)
            continue

        for (pattern_index = 0 pattern_index < 0x1C/*PATTERN COUNT*/ ++pattern_index)
        {
            if (pattern[pattern_index].header == 0x57A && !is_user32) // ONLY DO FFE090 SEARCHES WHEN IN USER32
                continue

            for (offset = 0 pattern[pattern_index].length + offset <= PAGE_SIZE ++offset) 
            {
                const auto pattern_matches = 
                    memory::pattern_match(&address[offset], pattern[pattern_index //    BASIC PATTERN MATCH

                if (pattern_matches) 
                {
                    // PATTERN FOUND IN MEMORY
                    pattern_report.unknown = 0
                    pattern_report.report_id = 0x35
                    pattern_report.type = pattern[index].header
                    pattern_report.data = &address[offset
                    pattern_report.base_address = memory_information.base_address
                    pattern_report.region_size = memory_information.region_size
                    pattern_report.memory_info = 
                        memory_information.type | 
                        memory_information.protect | 
                        memory_information.state

                    battleye::report(&pattern_report, sizeof(pattern_report), 0
                }
            }
        }
    }
}

Проверка конкретных модулей (Microsoft)


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

void memory::module_specific_check_microsoft(MEMORY_BASIC_INFORMATION memory_information)
{
    auto executable = 
        memory_information.protect == PAGE_EXECUTE || 
        memory_information.protect == PAGE_EXECUTE_READ || 
        memory_information.protect == PAGE_EXECUTE_READWRITE

    auto allocated = 
        memory_information.state == MEM_COMMIT

    if (!allocated || !executable)
        continue

    auto mmres_handle = GetModuleHandleA("mmres.dll"
    auto mshtml_handle = GetModuleHandleA("mshtml.dll"

    if (mmres_handle && mmres_handle == memory_information.allocation_base)
    {
        battleye_module_anomaly_report module_anomaly_report
        module_anomaly_report.unknown = 0
        module_anomaly_report.report_id = 0x5B
        module_anomaly_report.identifier = 0x3480
        module_anomaly_report.region_size = memory_information.region_size
        battleye::report(&module_anomaly_report, sizeof(module_anomaly_report), 0   
    }
    else if (mshtml_handle && mshtml_handle == memory_information.allocation_base)
    {
        battleye_module_anomaly_report module_anomaly_report
        module_anomaly_report.unknown = 0
        module_anomaly_report.report_id = 0x5B
        module_anomaly_report.identifier = 0xB480
        module_anomaly_report.region_size = memory_information.region_size
        battleye::report(&module_anomaly_report, sizeof(module_anomaly_report), 0  
    }
}

Проверка конкретных модулей (неизвестных)


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

void memory::module_specific_check_unknown(MEMORY_BASIC_INFORMATION memory_information)
{
    const auto dos_header = (DOS_HEADER*)module_handle
    const auto pe_header = (PE_HEADER*)(module_handle + dos_header->e_lfanew

    const auto is_image = memory_information.state == MEM_COMMIT && memory_information.type == MEM_IMAGE
    if (!is_image)
        return

    const auto is_base = memory_information.base_address == memory_information.allocation_base
    if (!is_base)
        return

    const auto match_1 = 
        time_date_stamp == 0x5B12C900 && 
        *(__int8*)(memory_information.base_address + 0x1000) == 0x00 &&
        *(__int32*)(memory_information.base_address + 0x501000) != 0x353E900

    const auto match_2 = 
        time_date_stamp == 0x5A180C35 && 
        *(__int8*)(memory_information.base_address + 0x1000) != 0x00

    const auto match_2 = 
        time_date_stamp == 0xFC9B9325 && 
        *(__int8*)(memory_information.base_address + 0x6D3000) != 0x00

    if (!match_1 && !match_2 && !match_3)
        return
    
    const auto buffer_offset = 0x00 // OFFSET DEPENDS ON WHICH MODULE MATCHES, RESPECTIVELY 0x501000, 0x1000 AND 0x6D3000

    unknown_module_report.unknown1 = 0
    unknown_module_report.report_id = 0x46
    unknown_module_report.unknown2 = 1
    unknown_module_report.data = *(__int128*)(memory_information.base_address + buffer_offset 
    battleye::report(&unknown_module_report, sizeof(unknown_module_report), 0
}

Мы не знаем, какие модули удовлетворяют этим критериям, но подозреваем, что это попытка обнаружения очень ограниченного набора конкретных чит-модулей.

Дополнение: @how02 сообщил нам, что модуль action_x64.dll имеет метку времени 0x5B12C900 и содержит область кода, в которую можно выполнять запись; как и говорилось ранее, это можно использовать для эксплойта.

Защита памяти


В BattlEye также внедрена очень сомнительная процедура обнаружения, которая, по нашему мнению, ищет память с установленным флагом PAGE_GUARD, не проверяя на самом деле, установлен ли флаг PAGE_GUARD:

void memory::guard_check(void* current_address, MEMORY_BASIC_INFORMATION memory_information)
{
    if (memory_information.protect != PAGE_NOACCESS)
    {
        auto bad_ptr = IsBadReadPtr(current_address, sizeof(temporary_buffer
        auto read = NtReadVirtualMemory(
            GetCurrentProcess(), 
            current_address, 
            temporary_buffer, sizeof(temporary_buffer), 
            0

        if (read < 0 || bad_ptr)
        {
            auto query = NtQueryVirtualMemory(
                GetCurrentProcess(), 
                current_address, 
                0, 
                &new_memory_information, sizeof(new_memory_information), 
                &return_length

            memory_guard_report.guard = 
                    query < 0 || 
                    new_memory_information.state != memory_information.state || 
                    new_memory_information.protect != memory_information.protect

            if (memory_guard_report.guard)
            {
                memory_guard_report.unknown = 0
                memory_guard_report.report_id = 0x21
                memory_guard_report.base_address = memory_information.base_address
                memory_guard_report.region_size = (int)memory_information.region_size
                memory_guard_report.memory_info = 
                    memory_information.type | 
                    memory_information.protect | 
                    memory_information.state

                battleye::report(&memory_guard_report, sizeof(memory_guard_report), 0
            }
        }
    }
}

Перебирание окон


Шелл-код BattlEye перебирает каждое из текущих видимых во время работы игры окон, обходя окна сверху вниз (по z-значению). Дескрипторы окон, находящиеся внутри процесса игры, исключаются из этого перебора, и это определяется вызовом GetWindowThreadProcessId. Следовательно, можно привязать соответствующую функцию к ложному владельцу окна, чтобы BattlEye не проверял ваше окно.

void window_handler::enumerate()
{
    for (auto window_handle = GetTopWindow
         window_handle 
         window_handle = GetWindow(window_handle, GW_HWNDNEXT), // GET WINDOW BELOW
         ++window_handler::windows_enumerated)                  // INCREMENT GLOBAL COUNT FOR LATER USAGE
    {
        auto window_process_pid = 0
        GetWindowThreadProcessId(window_handle, &window_process_pid

        if (window_process_pid == GetCurrentProcessId())
            continue

        // APPEND INFORMATION TO THE MISC. REPORT, THIS IS EXPLAINED LATER IN THE ARTICLE
        window_handler::handle_summary(window_handle

        constexpr auto max_character_count = 0x80
        const auto length = GetWindowTextA(window_handle, window_title_report.window_title, max_character_count

        // DOES WINDOW TITLE MATCH ANY OF THE BLACKLISTED TITLES?
        if (!contains(window_title_report.window_title, "CheatAut") &&
            !contains(window_title_report.window_title, "pubg_kh") &&
            !contains(window_title_report.window_title, "conl -") &&
            !contains(window_title_report.window_title, "PerfectA") &&
            !contains(window_title_report.window_title, "AIMWA") &&
            !contains(window_title_report.window_title, "PUBG AIM") &&
            !contains(window_title_report.window_title, "HyperChe"))
            continue

        // REPORT WINDOW
        window_title_report.unknown_1 = 0
        window_title_report.report_id = 0x33
        battleye::report(&window_title_report, sizeof(window_title_report) + length, 0
    }
}

Аномалия при перебирании


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

void window_handler::check_count()
{
    if (window_handler::windows_enumerated > 1)
        return
    
    // WINDOW ENUMERATION FAILED, MOST LIKELY DUE TO HOOK
    window_anomaly_report.unknown_1 = 0
    window_anomaly_report.report_id = 0x44
    window_anomaly_report.enumerated_windows = windows_enumerated
    battleye::report(&window_anomaly_report, sizeof(window_anomaly_report), 0
    
}

Перебирание процессов


При помощи вызова CreateToolhelp32Snapshot BattlEye перебирает все запущенные процессы, но не обрабатывает никакие ошибки, благодаря чему очень легко пропатчить и избежать выполнения следующих процедур обнаружения:

Проверка пути


Если образ находится внутри как минимум двух подкаталогов (считая от корня диска), то система пометит процессы флагом, если путь к соответствующему образу содержит хотя бы одну из этих строк:

Desktop
Temp
FileRec
Documents
Downloads
Roaming
tmp.ex
notepad.
...\.
cmd.ex

Если путь к исполняемому файлу соответствует одной из этих строк, то сервер получает уведомление о пути к исполняемому файлу, а также информацию о том, является ли родительский процесс одним из следующих (содержит соответствующий бит флага, отправляемый серверу):

steam.exe       [0x01]
explorer.exe    [0x02]
lsass.exe       [0x08]
cmd.exe         [0x10]

Если клиент не может открыть дескриптор с соответствующими правами QueryLimitedInformation, то он установит бит флага 0x04, если причина ошибки при сбое вызова OpenProcess не равна ERROR_ACCESS_DENIED, что даёт нам последний контейнер перечисления для соответствующего значения флага:

enum BATTLEYE_PROCESS_FLAG
{
  STEAM     = 0x1,
  EXPLORER  = 0x2,
  ERROR     = 0x4,
  LSASS     = 0x8,
  CMD       = 0x10
}

Если родительским процессом является steam, то пользователю мгновенно устанавливается флаг и об этом сообщается серверу с id уведомления 0x40

Имя образа


Если процесс соответствует любому из множества представленных ниже критериев, то вам мгновенно устанавливается флаг и об этом сообщается серверу с id уведомления 0x38

Имя образа содержит "Loadlibr"
Имя образа содержит "Rng "
Имя образа содержит "A0E7FFFFFF81"
Имя образа содержит "RNG "
Имя образа содержит "90E54355"
Имя образа содержит "2.6.ex"
Имя образа содержит "TempFile.exe"

Оверлей игр Steam


BattlEye отслеживает процесс оверлея игр Steam, отвечающий за внутриигровой оверлей, известный большинству пользователей Steam. Полное имя хоста оверлея игр Steam — gameoverlayui.exe; известно, что его часто используют для эксплойтов рендеринга, потому что довольно легко взломать и выполнять незаконную отрисовку в окне игры. Проверка имеет следующее условие:

file size != 0 && image name contains (case insensitive) gameoverlayu

Дальнейшие проверки, специфичные для оверлея игр Steam, практически аналогичны процедурам, выполняемым для самого процесса игры, поэтому в псевдокоде пропущены.

Сканирование памяти оверлея игр Steam


Процесс оверлея игр Steam сканируется на наличие паттернов и аномалий. Нам не удалось пробраться глубже в кроличью нору, и узнать, для чего эти паттерны, потому что они очень обобщённые и скорее всего связаны с модулями читов.

void gameoverlay::pattern_scan(MEMORY_BASIC_INFORMATION memory_information)
{
    // PATTERNS:
    // Home
    // F1
    // FFFF83C48C30000000000
    // \.pipe%s
    // C760000C64730
    // 60C01810033D2

    // ... 
    // PATTERN SCAN, ALMOST IDENTICAL CODE TO THE AFOREMENTIONED PATTERN SCANNING ROUTINE

    gameoverlay_memory_report.unknown_1 = 0
    gameoverlay_memory_report.report_id = 0x35
    gameoverlay_memory_report.identifier = 0x56C
    gameoverlay_memory_report.data = &buffer[offset
    gameoverlay_memory_report.base_address = memory_information.base_address
    gameoverlay_memory_report.region_size = (int)memory_information.region_size
    gameoverlay_memory_report.memory_info = 
        memory_information.type | 
        memory_information.protect | 
        memory_information.state

    battleye::report(&gameoverlay_memory_report, sizeof(gameoverlay_memory_report), 0
}

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

void gameoverlay::memory_anomaly_scan(MEMORY_BASIC_INFORMATION memory_information)
{  
    // ...
    // ALMOST IDENTICAL ANOMALY SCAN COMPARED TO MEMORY ENUMERATION ROUTINE OF GAME PROCESS

    gameoverlay_report.unknown = 0
    gameoverlay_report.report_id = 0x3B
    gameoverlay_report.base_address = memory_information.base_address
    gameoverlay_report.region_size = memory_information.region_size
    gameoverlay_report.memory_info = memory_information.type | memory_information.protect | memory_information.state
    battleye::report(&gameoverlay_report, sizeof(gameoverlay_report), 0
}

Защита процесса оверлея игр Steam


Если процесс оверлея игр Steam защищён какой-нибудь защитой процессов Windows наподобие Light (WinTcb), то сервер получит об этом уведомление.

void gameoverlay::protection_check(HANDLE process_handle)
{
    auto process_protection = 0

    NtQueryInformationProcess(
        process_handle, ProcessProtectionInformation, 
        &process_protection, sizeof(process_protection), nullptr

    if (process_protection == 0) // NO PROTECTION
        return

    gameoverlay_protected_report.unknown = 0
    gameoverlay_protected_report.report_id = 0x35
    gameoverlay_protected_report.identifier = 0x5B1
    gameoverlay_protected_report.data = process_protection
    battleye::report(&gameoverlay_protected_report, sizeof(gameoverlay_protected_report), 0
}

Кроме того, если соответствующий вызов OpenProcess к процессу оверлея возвращает ERROR_ACCESS_DENIED, то о пользователе высылается уведомление с id 3B.

Перебирание модулей


Модули процесса оверлея игр Steam тоже перебираются, в частности, ищутся vgui2_s.dll и gameoverlayui.dll. Для этих модулей выполняется несколько проверок, начиная с gameoverlayui.dll.

Если выполняется это условие: [gameoverlayui.dll+6C779] == 08BE55DC3CCCCB8????????C3CCCCCC, то шелл-код сканирует vtable по адресу, хранящемуся в байтах ????????. Если любой из этих элементов vtable находится за пределами исходного модуля gameoverlayui.dll или указывает на инструкцию int 3, то о пользователе сообщается на сервер с id уведомления 3B.

void gameoverlay::scan_vtable(HANDLE process_handle, char* buffer, MODULEENTRY32 module_entry)
{
    char function_buffer[16

    for (vtable_index = 0 vtable_index < 20 vtable_index += 4)
    {
        NtReadVirtualMemory(
          process_handle,
          *(int*)&buffer[vtable_index],
          &function_buffer,
          sizeof(function_buffer),
          0

        if (*(int*)&buffer[vtable_index] < module_entry.modBaseAddr ||
            *(int*)&buffer[vtable_index] >= module_entry.modBaseAddr + module_entry.modBaseSize ||
            function_buffer[0] == 0xCC )    // FUNCTION PADDING
        {
            gameoverlay_vtable_report.report_id = 0x3B
            gameoverlay_vtable_report.vtable_index = vtable_index
            gameoverlay_vtable_report.address = buffer[vtable_index
            battleye::report(&gameoverlay_vtable_report, sizeof(gameoverlay_vtable_report), 0
        }
    }
}

Для модуля vgui2_s.dll тоже выполняется специфическая процедура проверки:

void vgui::scan()
{
    if (!equals(vgui_buffer, "6A08B31FF561C8BD??????????FF96????????8BD????????8B1FF90"))
    {
        auto could_read = NtReadVirtualMemory(
            process_handle, module_entry.modBaseAddr + 0x48338, vgui_buffer, 8, 0) >= 0

        constexpr auto pattern_offset = 0x48378

        // IF READ DID NOT FAIL AND PATTERN IS FOUND
        if (could_read && equals(vgui_buffer, "6A46A06A26A"))
        {
            vgui_report.unknown_1 = 0
            vgui_report.report_id = 0x3B
            vgui_report.unknown_2 = 0
            vgui_report.address = LODWORD(module_entry.modBaseAddr) + pattern_offset

            // READ TARGET BUFFER INTO REPORT
            NtReadVirtualMemory(
              process_handle,
              module_entry.modBaseAddr + pattern_offset,
              vgui_report.buffer,
              sizeof(vgui_report.buffer),
              0

            battleye::report(&vgui_report, sizeof(vgui_report), 0
        }
    }
    else if (
            // READ ADDRESS FROM CODE
            NtReadVirtualMemory(process_handle, *(int*)&vgui_buffer[9], vgui_buffer, 4, 0) >= 0 &&
            // READ POINTER TO CLASS
            NtReadVirtualMemory(process_handle, *(int*)vgui_buffer, vgui_buffer, 4, 0) >= 0 && 
            // READ POINTER TO VIRTUAL TABLE
            NtReadVirtualMemory(process_handle, *(int*)vgui_buffer, vgui_buffer, sizeof(vgui_buffer), 0) >= 0)
    {
        for (vtable_index = 0 vtable_index < 984 vtable_index += 4 )      // 984/4 VTABLE ENTRY COUNT
        {
            NtReadVirtualMemory(process_handle, *(int*)&vgui_buffer[vtable_index], &vtable_entry, sizeof(vtable_entry), 0

            if (*(int*)&vgui_buffer[vtable_index] < module_entry.modBaseAddr ||
                *(int*)&vgui_buffer[vtable_index] >= module_entry.modBaseAddr + module_entry.modBaseSize ||
                vtable_entry == 0xCC )
            {
                vgui_vtable_report.unknown = 0
                vgui_vtable_report.report_id = 0x3B
                vgui_vtable_report.vtable_index = vtable_index
                vgui_vtable_report.address = *(int*)&vgui_buffer[vtable_index
                battleye::report(&vgui_vtable_report, sizeof(vgui_vtable_report), 0
            }
        }
    }

Данная процедура проверяет наличие изменений по смещению 48378, которое является расположением области кода:

push    04
push    offset aCBuildslaveSte_4 ; "c:\buildslave\steam_rel_client_win32"...
push    offset aAssertionFaile_7 ; "Assertion Failed: IsValidIndex(elem)"

Затем процедура проверяет наличие очень специфического и кажущегося мусорным изменения:

push    04
push    00
push    02
push    ??

Нам не удалось найти копию vgui2_s.dll, не соответствующую первой из двух вышеприведённых проверок, поэтому не можем узнать, какую vtable она проверяет.

Потоки оверлея игр Steam


Потоки в процессе оверлея игр Steam тоже перебираются:

void gameoverlay::check_thread(THREADENTRY32 thread_entry)
{
    const auto tread_handle = OpenThread(THREAD_SUSPEND_RESUME|THREAD_GET_CONTEXT, 0, thread_entry.th32ThreadID
    if (thread_handle)
    {
        suspend_count = ResumeThread(thread_handle
        if (suspend_count > 0)
        {
            SuspendThread(thread_handle
            gameoverlay_thread_report.unknown = 0
            gameoverlay_thread_report.report_id = 0x3B
            gameoverlay_thread_report.suspend_count = suspend_count
            battleye::report(&gameoverlay_thread_report, sizeof(gameoverlay_thread_report), 0
        }

        if (GetThreadContext(thread_handle, &context) && context.Dr7)
        {
            gameoverlay_debug_report.unknown = 0
            gameoverlay_debug_report.report_id = 0x3B
            gameoverlay_debug_report.debug_register = context.Dr0
            battleye::report(&gameoverlay_debug_report, sizeof(gameoverlay_debug_report), 0
        }
    }
}

LSASS


Адресное пространство памяти процесса Windows lsass.exe, также известного как процесс Local Security Authority, тоже перебирается и о всех аномалиях сообщается серверу, как и в случае двух предыдущих проверок:

if (equals(process_entry.executable_path, "lsass.exe"))
{
    auto lsass_handle = OpenProcess(QueryInformation, 0, (unsigned int)process_entry.th32ProcessID
    if (lsass_handle)
    {
        for (address = 0
              NtQueryVirtualMemory(lsass_handle, address, 0, &lsass_memory_info, 0x30, &bytes_needed) >= 0
              address = lsass_memory_info.base_address + lsass_memory_info.region_size)
        {
            if (lsass_memory_info.state == MEM_COMMIT
              && lsass_memory_info.type == MEM_PRIVATE
              && (lsass_memory_info.protect == PAGE_EXECUTE
               || lsass_memory_info.protect == PAGE_EXECUTE_READ
               || lsass_memory_info.protect == PAGE_EXECUTE_READWRITE))
            {
                // FOUND EXECUTABLE MEMORY OUTSIDE OF MODULES
                lsass_report.unknown = 0
                lsass_report.report_id = 0x42
                lsass_report.base_address = lsass_memory_info.base_address
                lsass_report.region_size = lsass_memory_info.region_size
                lsass_report.memory_info = 
                    lsass_memory_info.type | lsass_memory_info.protect | lsass_memory_info.state
                battleye::report(&lsass_report, sizeof(lsass_report), 0
            }
        }
        CloseHandle(lsass_handle
    }
}

LSASS ранее использовался в эксплойтах для выполнения операций с памятью, так как любой процесс, которому нужно Интернет-подключение, должен предоставить доступ к нему LSASS. В настоящее время BattlEye справляется с этой проблемой, вручную очищая дескриптор процесса от операций чтения/записи, а затем подключая ReadProcessMemory/WriteProcessMemory, перенаправляя вызовы на свой драйвер BEDaisy. Далее BEDaisy решает, является ли операция с памятью законной. Если он считает, что операция законна, то он продолжает её, а в противном случае намеренно включает машине синий экран.

Различные уведомления


BattlEye собирает различную информацию и отправляет её серверу с id уведомления 3C. Эта информация состоит из следующих элементов:

  • Любое окно с флагом WS_EX_TOPMOST или его аналогами:
    • Текст окна (Unicode)
    • Имя класса окна (Unicode)
    • Window style
    • Window extended style
    • Прямоугольник окна
    • Путь к образу процесса-владельца
    • Размер образа процесса-владельца
  • Любой процесс с открытым игре дескриптором процесса (VM_WRITE|VM_READ)
    • Имя образа
    • Путь к образу
    • Размер образа
    • Доступ к дескриптору
  • Размер отдельных файлов игры:
    • ….ContentPaksTslGame-WindowsNoEditor_assets_world.pak
    • ….ContentPaksTslGame-WindowsNoEditor_ui.pak
    • ….ContentPaksTslGame-WindowsNoEditor_sound.pak

  • Содержимое отдельных файлов игры:
    • ….BLGameCookedContentScriptBLGame.u
  • Информация об обходе NtGetContextThread
    • Выполняются все инструкции перехода (E9), и адрес назначения записывается в лог

NoEye


В BattlEye реализована довольно ленивая проверка обнаружения публично доступного руткита для обхода этого античита под названием NoEye: система при помощи GetFileAttributesExA проверяет размер файла BE_DLL.dll, если эта библиотека найдена на диске.

void noeye::detect()
{
    WIN32_FILE_ATTRIBUTE_DATA file_information
    if (GetFileAttributesExA("BE_DLL.dll", 0, &file_information))
    {
      noeye_report.unknown = 0
      noeye_report.report_id = 0x3D
      noeye_report.file_size = file_information.nFileSizeLow
      battleye::report(&noeye_report, sizeof(noeye_report), 0
    }
}

Наличие драйверов


Проверяется наличие устройств Beep и Null; в случае их наличия создаётся уведомление. В обычном состоянии эти два устройства не доступны в системе, и это может говорить о том, что кто-то вручную включил устройство. Такая техника называется driver device hijacking. Это делается для того, чтобы обеспечить обмен данными IOCTL с зловредным драйвером без необходимости отдельного объекта драйвера для этого драйвера.

void driver::check_beep()
{
    auto handle = CreateFileA("\\.\Beep", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0
    if (handle != INVALID_HANDLE_VALUE)
    {
      beep_report.unknown = 0
      beep_report.report_id = 0x3E
      battleye::report(&beep_report, sizeof(beep_report), 0
      CloseHandle(handle
    }
}

void driver::check_null()
{
    auto handle = CreateFileA("\\.\Null", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0
    if (handle != INVALID_HANDLE_VALUE)
    {
      null_report.unknown = 0
      null_report.report_id = 0x3E
      battleye::report(&null_report, sizeof(null_report), 0
      CloseHandle(handle
    }
}

Sleep delta


Кроме того, BattlEye может запрашивать у текущего потока одну секунду бездействия и измеряет разность количества циклов до и после бездействия (sleep):

void sleep::check_delta()
{
    const auto tick_count = GetTickCount
    Sleep(1000
    const auto tick_delta = GetTickCount() - tick_count
    if (tick_delta >= 1200)
    {
        sleep_report.unknown = 0
        sleep_report.report_id = 0x45
        sleep_report.delta = tick_delta
        battleye::report(&sleep_report, sizeof(sleep_report), 0
    }
}

7zip


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

void module::check_7zip()
{
    constexpr auto sz_7zipdll = "..\..\Plugins\ZipUtility\ThirdParty\7zpp\dll\Win64\7z.dll"
    const auto module_handle = GetModuleHandleA(sz_7zipdll
    if (module_handle && *(int*)(module_handle + 0x1000) != 0xFF1441C7)
    {
      sevenzip_report.unknown_1 = 0
      sevenzip_report.report_id = 0x46
      sevenzip_report.unknown_2 = 0
      sevenzip_report.data1 = *(__int64*)(module_handle + 0x1000
      sevenzip_report.data2 = *(__int64*)(module_handle + 0x1008
      battleye::report(&sevenzip_report, sizeof(sevenzip_report), 0
    }
}

Уровень аппаратных абстракций


BattlEye проверяет наличие динамически подключаемой библиотеки уровня аппаратных абстракций Windows (hal.dll), и сообщает серверу, загружена ли она внутри процесса игры.

void module::check_hal()
{
    const auto module_handle = GetModuleHandleA("hal.dll"
    if (module_handle)
    {
        hal_report.unknown_1 = 0
        hal_report.report_id = 0x46
        hal_report.unknown_2 = 2
        hal_report.data1 = *(__int64*)(module_handle + 0x1000
        hal_report.data2 = *(__int64*)(module_handle + 0x1008
        battleye::report(&hal_report, sizeof(hal_report), 0
    }
}

Проверки образов


Также BattlEye проверяет различные образы, загруженные в процесс игры. Эти модули предположительно являются подписанными образами, которыми каким-то образом манипулируют, изменяя их поведение на зловредное, но подробнее мы ничего сказать о них не можем, только об их обнаружении:

nvToolsExt64_1


void module::check_nvtoolsext64_1
{
    const auto module_handle = GetModuleHandleA("nvToolsExt64_1.dll"
    if (module_handle)
    {
      nvtools_report.unknown = 0
      nvtools_report.report_id = 0x48
      nvtools_report.module_id = 0x5A8
      nvtools_report.size_of_image = (PE_HEADER*)(module_handle + (DOS_HEADER*)(module_handle)->e_lfanew))->SizeOfImage
      battleye::report(&nvtools_report, sizeof(nvtools_report), 0
    }
}

ws2detour_x96


void module::check_ws2detour_x96
{
    const auto module_handle = GetModuleHandleA("ws2detour_x96.dll"
    if (module_handle)
    {
      ws2detour_report.unknown = 0
      ws2detour_report.report_id = 0x48
      ws2detour_report.module_id = 0x5B5
      ws2detour_report.size_of_image = (PE_HEADER*)(module_handle + (DOS_HEADER*)(module_handle)->e_lfanew))->SizeOfImage
      battleye::report(&ws2detour_report, sizeof(ws2detour_report), 0
    }
}

networkdllx64


void module::check_networkdllx64
{
    const auto module_handle = GetModuleHandleA("networkdllx64.dll"
    if (module_handle)
    {
        const auto dos_header = (DOS_HEADER*)module_handle
        const auto pe_header = (PE_HEADER*)(module_handle + dos_header->e_lfanew
        const auto size_of_image = pe_header->SizeOfImage

        if (size_of_image < 0x200000 || size_of_image >= 0x400000)
        {
            if (pe_header->sections[DEBUG_DIRECTORY].size == 0x1B20)
            {
                networkdll64_report.unknown = 0
                networkdll64_report.report_id = 0x48
                networkdll64_report.module_id = 0x5B7
                networkdll64_report.data = pe_header->TimeDatestamp
                battleye::report(&networkdll64_report, sizeof(networkdll64_report), 0
            }
        }
        else
        {
            networkdll64_report.unknown = 0
            networkdll64_report.report_id = 0x48
            networkdll64_report.module_id = 0x5B7
            networkdll64_report.data = pe_header->sections[DEBUG_DIRECTORY].size
            battleye::report(&networkdll64_report, sizeof(networkdll64_report), 0
        }
    }
}

nxdetours_64


void module::check_nxdetours_64
{
    const auto module_handle = GetModuleHandleA("nxdetours_64.dll"
    if (module_handle)
    {
      nxdetours64_report.unknown = 0
      nxdetours64_report.report_id = 0x48
      nxdetours64_report.module_id = 0x5B8
      nxdetours64_report.size_of_image = (PE_HEADER*)(module_handle + (DOS_HEADER*)(module_handle)->e_lfanew))->SizeOfImage
      battleye::report(&nxdetours64_report, sizeof(nxdetours64_report), 0
    }
}

nvcompiler


void module::check_nvcompiler
{
    const auto module_handle = GetModuleHandleA("nvcompiler.dll"
    if (module_handle)
    {
      nvcompiler_report.unknown = 0
      nvcompiler_report.report_id = 0x48
      nvcompiler_report.module_id = 0x5BC
      nvcompiler_report.data = *(int*)(module_handle + 0x1000
      battleye::report(&nvcompiler_report, sizeof(nvcompiler_report), 0
    }
}

wmp


void module::check_wmp
{
    const auto module_handle = GetModuleHandleA("wmp.dll"
    if (module_handle)
    {
      wmp_report.unknown = 0
      wmp_report.report_id = 0x48
      wmp_report.module_id = 0x5BE
      wmp_report.data = *(int*)(module_handle + 0x1000
      battleye::report(&wmp_report, sizeof(wmp_report), 0
    }
}

Идентификаторы перечисления модулей


Приведём для справки id перечисления для модулей:

enum module_id
{
    nvtoolsext64    = 0x5A8,
    ws2detour_x96   = 0x5B5,
    networkdll64    = 0x5B7,
    nxdetours_64    = 0x5B8,
    nvcompiler      = 0x5BC,
    wmp             = 0x5BE

Сканирование таблиц TCP


Шелл-код BattlEye выполняет поиск списка TCP-соединений всей системы (известного под названием «таблица TCP», TCP table) и сообщает, если пользователь подключен хотя бы к одному из IP-адресов Cloudflare-шлюза, принадлежащих немецкому веб-сайту pay-to-cheat под названием xera.ph. Этот механизм был добавлен в шелл-код для обнаружения пользователей, у которых при работе игры запущен launcher, благодаря чему они легко распознаются. Единственная проблема этого механизма заключается в том, что IP-адреса Cloudflare-шлюза позже могут сменить владельцев. и если их новый владелец будет распространять ПО, подключаемое к его серверам через определённый порт, то без всяких сомнений будут происходить ложноположительные срабатывания античита.

Пользователи провайдера услуг pay-to-cheat xera.ph уже долгое время сообщают о том, что их ловят, и разработчики никак не могут с этим справиться. Мы связались с разработчиками xera.ph, чтобы сообщить им об их глупом поведении, но они неправильно нас поняли и отправили нам бесплатную копию, не подумав, что мы сможем взломать и зарелизить её. Мы так не поступим, но, наверно, не стоит бесплатно отправлять проприетарные лицензионные двоичные файлы людям, занимающимся реверс-инжинирингом, и ожидать, что их не спиратят.

void network::scan_tcp_table
{
    memset(local_port_buffer, 0, sizeof(local_port_buffer

    for (iteration_index = 0 iteration_index < 500 ++iteration_index)
    {
        // GET NECESSARY SIZE OF TCP TABLE
        auto table_size = 0
        GetExtendedTcpTable(0, &table_size, false, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0

        // ALLOCATE BUFFER OF PROPER SIZE FOR TCP TABLE
        auto allocated_ip_table = (MIB_TCPTABLE_OWNER_MODULE*)malloc(table_size

        if (GetExtendedTcpTable(allocated_ip_table, &table_size, false, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0) != NO_ERROR)
            goto cleanup

        for (entry_index = 0 entry_index < allocated_ip_table->dwNumEntries ++entry_index)
        {
            const auto ip_address_match_1 = 
                allocated_ip_table->table[entry_index].dwRemoteAddr == 0x656B1468 // 104.20.107.101

            const auto ip_address_match_2 = 
                allocated_ip_table->table[entry_index].dwRemoteAddr == 0x656C1468 // 104.20.108.101

            const auto port_match = 
                allocated_ip_table->table[entry_index].dwRemotePort == 20480

            if ( (!ip_address_match_1 && !ip_address_match_2) || !port_match)
                continue

            for (port_index = 0 
                 port_index < 10 && 
                 allocated_ip_table->table[entry_index].dwLocalPort != 
                    local_port_buffer[port_index 
                 ++port_index)
            {
                if (local_port_buffer[port_index])
                    continue

                tcp_table_report.unknown = 0
                tcp_table_report.report_id = 0x48
                tcp_table_report.module_id = 0x5B9
                tcp_table_report.data = 
                    BYTE1(allocated_ip_table->table[entry_index].dwLocalPort) | 
                    (LOBYTE(allocated_ip_table->table[entry_index.dwLocalPort) << 8

                battleye::report(&tcp_table_report, sizeof(tcp_table_report), 0

                local_port_buffer[port_index] = allocated_ip_table->table[entry_index].dwLocalPort
                break

            }
        }

cleanup:
        // FREE TABLE AND SLEEP
        free(allocated_ip_table
        Sleep(10
    }
}

Типы уведомлений


Приведём для справки все известные типы уведомлений из шелл-кода:

enum BATTLEYE_REPORT_ID
{
    MEMORY_GUARD            = 0x21,
    MEMORY_SUSPICIOUS       = 0x2F,
    WINDOW_TITLE            = 0x33,
    MEMORY                  = 0x35,
    PROCESS_ANOMALY         = 0x38,
    DRIVER_BEEP_PRESENCE    = 0x3E,
    DRIVER_NULL_PRESENCE    = 0x3F,
    MISCELLANEOUS_ANOMALY   = 0x3B,
    PROCESS_SUSPICIOUS      = 0x40,
    LSASS_MEMORY            = 0x42,
    SLEEP_ANOMALY           = 0x45,
    MEMORY_MODULE_SPECIFIC  = 0x46,
    GENERIC_ANOMALY         = 0x48,
    MEMORY_MODULE_SPECIFIC2 = 0x5B,
}

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 73

    +3
    А это все реально хоть как-то работает?
    Например в 7 Days to Die китайские читеры (с китайскими IP) начисто игнорируют античит, совершая форменную жесть. (Например почти мгновенное раскапывание того, что по механу должно бы раскапываться многие часы).
    Есть-ли в этом античите проверка на заведомо недопустимые действия в игре (ну или по крайней мере на подозрительные), или все это целиком на откуп самой игре отдается?

    upd.
    Случайно вспомнил, что все-таки что в 7 days to die Easy Anticheat используется.

    А как у battleye работает соединение с сервером? Я заметил, что когда у того проблемы с соединением с сервером, то он кикает не сразу при присоединении к игровому серверу, а минут через пять игры. Это сделано с какой-то целью (и при обнаружении читов будет так же, типа чтобы сразу не показывать, что античит спалил читера) или «так получилось»?
      +6

      Предполагаю, что если бэкенд программисты говнокодят, и доверяют клиенту без каких-либо проверок, то тут никакой античит не спасет.

        0
        Не работает. Я держал большую европейскую коммунити по арма3. Поток читеров не прекращался. Как и в любых других РС онлайн играх.
          0

          Можно читить и в консольных онлайн играх.


          Кажется, тут можно сформулировать так: поток наговнокоженных левой пяткой игр не прекращается.

            0

            Почему, собственно, говнокод? Это же фатальный недостаток, например, вообще всех онлайн шутеров — как ни крути на клиент придется передать координаты игрока.

          0
          Практически не работает. Раньше активно бегал в пубг и читеров было 1-2 за несколько игр. Пол года назад начался страшный наплыв все тех же китайских читеров, которые по сей день составляют 30-50% игроков на сервере(из ста). Можно представить, что сейчас происходит в игре. Видел один раз надпись о том, что игрока заблокировало прямо во время игры, но разработчики очевидно ничего не хотят предпринимать.
          0
          Спасибо за статью.

          В своё время ради интереса написал маленькое решение по противодействию таким программам поиска и изменения критических значений в памяти, как ArtMoney, путем хранения числа в шифрованном (а потому и избыточном) виде с методами манипуляции значением динамически.
            +6

            Проверка целостности 7z.dll это очень смешно.
            Помню еще в начале-середине 2000ых, так обходили кривые корейские античиты, которые преимущественно боролись с DLL Injection с помощью проверки целостности Import Address Table.
            Обход: скачивались исходники соответствующей версии 7zip, пересобирались с нужными изменениями, и клались в папку с игрой :P

              0
              А античит CRC-проверку не делал?
              0
              … Я забыл уточнить, кто владеет оборудованием, где всё это безобразие твориться? Может ли пользователь отказаться от услуг столь пристального досмотра.

              И кто может производить обыск имущества?
                +1
                Боюсь тут как в аэропорту, отказаться можно, но полететь никуда не удастся.
                  0
                  В аэропорту досмотр требуют согласно законодательству.
                  А вот представьте себе, что вам, для того, чтобы прочитать книгу (продающуюся в магазине) надо пустить в дом частных детективов, которые прощупают все швы на всех трусах в поисках чего-то, что им не нравится.

                  Как вы думаете, как быстро такую лавочку прикроют?
                    +2
                    К сожалению не прикроют — если будут просить подписать соответствующее соглашение (где вы явно всё разрешаете) — оно не противозаконно. Можете не подписывать и не пускать детективов, но и книгу не получите.

                    Именно по этой причине я перестал покупать игры с навороченными античитами — слишком интрузивно, а отдельный комп для игр (или консоль) мне не хочется покупать.

                    Специального котла в аду заслуживают однопользователькие оффлайн игры с античитами, да ещё иногда требующие интернета для игры локально.
                      0
                      Если игра покупается в публичном месте (например, магазине), то накладывать на потребителя такие обременения продавец не в праве.

                      Мне кажется, отличная тема для сутяжничества с участием роспотребнадзора и антимонопольщиков.
                        0
                        Переименовываем магазин в «Игровой клуб» и вопрос решен.

                        Хотя… в самом магазине ведь могут и не требовать досмотра, коробочку (или лицензию) выдадут, но вот чтобы начать игру — придётся согласится (EULA при установке, причём к магазину отношения не имеющая), но вы можете отказаться и потребовать деньги назад.

                        Если же говорить про покупки онлайн — то это вот, мне кажется, не совсем публичное место — нужна регистрация (вот уже и аналогия с членством в клубе), которая, в свою очередь, тоже может иметь определенные условия.

                        В общем, всё сложно… поэтому остаётся голосовать кошельком.
                      0
                      Тут аналогия не с книгой, а с соревнованиями, например, с гонками. Если ко всем предъявляются одинаковые требования (мощность двигателя и т.п.), но за их соблюдением никто не следит, то очень скоро единственными победителями станут жулики, а для остальных соревнование потеряет смысл.
                    +1
                    Выше уже про соглашение сказали и в случае с PUBG она при первом запуске спрашивает согласен ли пользователь на использование античита.
                    Однако есть другая сторона монеты — Я готов использовать и античит Faceit'a. Это(насколько мне известно), один из самых эффективных античитов, который, конечно, все равно обходят, но это очень сложно. Он настолько «жесткий», что с ним даже fps ниже на 10-15%. Он вообще лезет везде, даже запускается от администратора, вроде бы.
                    У меня отдельная машина как раз для игр и Я готов на использование чего угодно, лишь бы комфортно играть. В конце концов — Я купил игру и играть умею неплохо, но желания запускать это поделие уже нет.
                    0
                    А почему не проверяются перехваты функций? Можно ведь вызовы DirectX перехватить.
                      0
                      Потому что этот «античит» (который считает себя в праве кидать машину в синьку, судя по отрывку об lsass) ловит конкретно тех, о ком он знает, что они плохие ребята — по артефактам, присущим именно им.
                        0

                        вот из-за этого игровой компьютер превращается в консоль, на которой можно запускать только одну игру. потому что можно либо потерять несохраненные данные в других программах, либо получить бан за запущенную софтину. у меня так было, когда запустил steam параллельно с игрой, но там, возможно, другой античит среагировал.

                        • UFO just landed and posted this here
                            0
                            он банит, если параллельно VMware запущен (
                            А я просто хотела накатить виртуалку, пока комада заканчивала матч…
                          0
                          И что в этих перехватах искать?
                            0
                            если они перехвачены, то программа общается не DirectX напрямую, а с какой-то другой dll
                              0
                              Какая-то dll обращается к DirectX, и дальше что? Это может быть программа показа fps на экране или другой информации.
                            0
                            Возможностей очень много и без перехватов.
                            Вот например
                            Видео под катом


                            Просто экстремально низкие настройки графики, а какой эффект :)
                              0
                              видимо при повторном воспроизведении модераторам кажется, что человек видит через кусты и стены
                                0
                                Это как же надо любить «ногибать», чтоб играть с таким графоном.
                                  +1
                                  Я как-то писал чит под одну игру, которая мне нравилась. Так там картинка после переделки была ещё жёстче. Листвы не было совсем, танки раскрашены в цвета радуги, с отметками, где у них броня тоньше. Самые опасные юниты — офицер и снайпер были выкрашены в максимально кислотные цвета (розовый и салатовый в полосочку). Дымы были отключены совсем, за исключением ожного опасного типа гранат. У них был яркий оранжевый дым, потому что важно было знать, куда граната упала. Ну и плюс был самописный мапхак, аим, инфа о противниках, макросы на действия и т.д. Просто графон важен для начального вовлечения в игру, а потом уже важен игровой процесс, а не графон. У меня всегда настройки графики либо выкручены в ноль, либо вот как на видео понижены ещё сильнее.
                                    0
                                    Жесть. Как можно любить игру и так её насиловать?
                                      0
                                      Повторюсь: «графон важен для начального вовлечения в игру, а потом уже важен игровой процесс, а не графон». Например, когда я играл в браузерки, то специально писал скрипты, убирающие всю ненужную графику из интерфейса и оставлявшие лишь текстовую информацию. При длительной игре цели уже не посмотреть на красивый закат, а затащить катку, разгромить противника и т.д. Не до сентиментальностей.
                                      И да, жёсткие читы (мапхаки, аимы, вырезатели текстуры) я никогда не отдавал в публичное пользование. А союзникам если и давал их, то с привязкой к своему серверу (так, что мог их удалённо выключить). Потому что некоторые читы могли спокойно убить игру совсем.
                                        0
                                        Ну дык с графоном теряется и часть игрового процесса, например, камуфляж, ныканье в тени и т.д.
                                        Не говорю уж про валхаки, убивающие весь CQB процесс.
                                  0

                                  Это уже выглядит как дискриминация некоторых пользователей.
                                  Бан за слабый компьютер.

                                +1
                                У нас есть драйвер, сервис, а методы проверки так же эффективны как проверки названий окон и запущенных процессов.
                                Обычно стараются не допустить посторонний код в процесс. Для этого, к примеру, хукают LoadLibrary, проверяют валидность подписей уже загруженных DLL, блокируют создание новых потоков через CreateRemoteThread, проверяют стандартные функции на наличие посторонних хуков
                                Взаимодействие с драйвером и сервисом может идти с подписью ECDSA, что отобьет желание модифицировать передаваемые данные
                                В любом случае читать статью было весьма интересно. Спасибо!
                                  0
                                  вот это хороший подход
                                    0
                                    Взаимодействие с драйвером и сервисом может идти с подписью ECDSA,

                                    Приватные ключи все равно ж у клиента лежат, так что какая в этом польза?

                                    Некоторые интеловские материнки позволяют подключать аппаратный отладчик через один из USB портов. Вот где простор для хакинга!

                                    Или своя PCI карточка с DMA… Тоже можно неплохо развернуться.

                                    Ну и банальную виртуализацию никто не отменял, конечно же.
                                      0
                                      Виртуалки вообще банят за то, что они виртуалки
                                        0
                                        Ну это банят известные виртуалки. В принципе, вполне можно написать тонкий type 1 гипервизор и запустить винду прям под ним, как есть. Его невозможно будет задетектить, но при этом он будет иметь полный доступ к памяти машины.
                                          0
                                          Виртуальная машина всегда работает немного не так, как хост.
                                          Есть куча проверок у разного софта на эту тему, основаны на специфических инструкциях процов и/или их комбинации.
                                            0
                                            Ну она работает немного не так только в настоящих гипервизорах, где хочешь-не хочешь, а приходится эмулировать часть периферии. Если же гипервизор будет рулить только second stage MMU (чтобы спрятать себя) и не мешать доступу к остальному железу, то никакого способа узнать о его наличии я не вижу.
                                            Это точно справедливо для ARMv8, но думаю что и для Intel с AMD тоже должно быть правдой.
                                            0
                                            Уже сделано в рамках Cheat Engine
                                        0

                                        Я участвовал в создании античита внутри игры. Как раз пытались не допустить посторонний код в процесс.
                                        LoadLibrary и CreateRemoteThread каким-то явным способом не блокируются. Создание потоков удалось заблокировать побив PE Header процесса, но его нужно было восстанавливать при создании нужный потоков внутри игры или используемых библиотек. Эта защита продержалась день. Возможно на уровне драйвера можно и защищённее сделать.


                                        Если не считать подмены dll, которую вроде бы легко победить, все проблемы от древнего WriteProcessMemory, который может писать в любой процесс. По-моему в современных системах это что-то лишнее и не безопасное. Стоило бы запретить подобные функции если не из системы в целом, то хотя бы для приложений собранных с какими-то особыми флагами.

                                          0
                                          Как и я. LoadLibrary блокируется простым хуком и проверкой стека вызовов. CreateRemoteThread сложнее, но возможно даже из usermode без административных прав.
                                          Написать содержательный чит используя только манипулирование памятью процесса через ReadProcessMemory/WriteProcessMemory не такая уж и простая задача для разработчика читов, защититься от этого поможет разве что драйвер, и то не на 100%. Основные силы были брошены на защиту от dll injection, а так же на защиту периметра самой игры, если вдруг вредоносный код проникнет в процесс
                                          Любой античит не дает 100% защиты. Главная цель значительно усложнить обход, что бы создание нового чита занимало слишком много времени
                                          Я наблюдал как разные группы читоделы писали свои собственные версии, не делились наработками друг с другом, уставали и не доводили свой проект до конца
                                        0

                                        Юзермод античит это шутка юмора, сейчас даже школьники умеют загружать неподписанные драйвера, спасибо криворуким вендорам. А дальше читай память хоть до потери пульса. Остается только понять и простить читарастов :D

                                          0
                                          Первые реализации античитов на основе нейросетей показали очень неплохие результаты. В районе 96%.
                                          Можно их использовать для выявление читеров, но не банить сразу, а сливать к себе сами читы и анализировать их работу.
                                            0
                                            тоже слышал, что VAC химичит с нейросетями, но результата пока не видно. В кс читаки всё-ещё кишат.
                                              0
                                              Сливать себе — это скачивать подозрительные куски кода из клиента?
                                              +1

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


                                              Хочешь заразить десятки тысяч компьютеров? Утащить у пользователей данные платёжных карт и пароли? Надо взломать сервер управления какого-нибудь античита, а дальше дать ему команду на поиск необходимых данных.


                                              Настраиваю себе компьютер для работы и периодических игр.
                                              Уже понял, что нужно ставить отдельные копии Windows под игры и работу, диск рабочей копии в обязательном порядке шифровать, иначе очередной шелл-код очередного античита сможет утащить оттуда всё, что ему захочется.


                                              Остался только один вопрос — нужна ли отдельная копия Windows под каждую игру или достаточно одной на всех? И насколько велика вероятность, что античит одной игры начнёт воровать данные учётки того же Steam'а через установленный драйвер.

                                                0
                                                Мне кажется (с учётом поведения Windows 10), что если пользоваться этой системой и играть в современные игры, то нужна виртуальная машина с пробросом видеокарты. Всё остальное — недостаточно безопасно.
                                                Некоторые игры, кстати, уже детектят такую конфигурацию.
                                                0
                                                Ещё один пример гонки оружия и брони.
                                                Чтобы броня серьёзно вырвалась вперёд, можно сделать, например, так:

                                                Весь профессиональный соревновательный гейминг перенести на консоль.
                                                Добавить поддержку клавиатур и мышей в выпускаемые на консоли игры по умолчанию.
                                                Консоль, желательно, должна быть на x86-несовместимом железе (процессор — точно, видеокарта может быть и стандартной)
                                                Память устройства должна быть зашифрована чем-то вроде developer.amd.com/sev (чтобы исключить хардварный мод для считывания и изменения состояния памяти)
                                                Ну и, естественно, очевидные вещи вроде исполнения только подписанного кода и т.д.

                                                Вот тогда можно будет с высокой долей вероятности сказать, что игроки пользуются только теми возможностями игры, которые задумывались разработчиками изначально.

                                                Тогда все возможные «хаки» будут иметь чисто аппаратную природу (например, распознавание изображения на видео и отправка команды на мышь)
                                                  0
                                                  Как показывает практика, никакая, даже самая навороченная аппаратная защита не спасет от чисто софтового эксплоита (которые неизбежно будут как минимум в первых версиях прошивки). А после того, как система скомпроментирована, никаких преимуществ консоли перед ПК не остается.
                                                    0

                                                    Справедливости ради отмечу, что создать безопасную архитектуру на аппаратном уровне можно. Но будите ли вы плеваться от того, что там всего лишь 1.5ГГц и относительно не высокий IPC на ядро. Что для вас важнее, безопасность или производительность? Готовы ли сменить десятилетний "стек технологий" ради новой и безопасной архитектуры?
                                                    +Все чисто аппаратные эксплоиты, что я лично знаю, были созданны с целью повышения производительности, за исключением естественно.

                                                Only users with full accounts can post comments. Log in, please.