Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд Анализ механизмов защиты драйвера EasyAntiCheat [ Часть 1 ]

Продавец
Продавец
Статус
Оффлайн
Регистрация
6 Май 2025
Сообщения
245
Реакции
219
Всем привет. В этой статье мы разберем некоторые механизмы защиты, которые используются в драйвере EasyAntiCheat_EOS'a ( был использован растовский еак, дата билда ~ 10 марта этого года )
Основываясь на результате обратной разработки, мы рассмотрим:

1. Сбор телеметрии
2. Проверка целостности системных драйверов
3. Шифрование репортов

* Сбор телеметрии:

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

C++:
Expand Collapse Copy
void collect_process_telemetry( PEPROCESS process, bool use_extended_info ) {
    const auto packet = allocate_pool( 0x330 );

    if ( memory::is_valid( packet ) ) {
        // Получаем информацию о процессе

        const auto is_wow64 = PsGetProcessWow64Process( process ) != 0;
        const auto pid = PsGetProcessId( process );

        const auto process_info = copy_process_info( 0, 0, 0, 0x801, is_wow64, 0, pid, 0 );

        // Если включен флаг с дополнительной информацией, то копируем в буфер с данными еще и название процесса

        if ( memory::is_valid( process_info ) ) {
            if ( use_extended_info ) {
                char buffer[ 16 ]{ };

                // PsGetProcessImageFileName

                if ( get_process_name( process, buffer ) ) {
                    auto dest = process_info + 0x20;

                    for ( int idx{ }; idx < 15 && buffer[ idx ]; ++idx )
                        dest[ idx ] = ( wchar_t )buffer[ idx ];

                    dest[ 15 ] = 0;
                }
            }
        }
      
        // Заполняем хеадер пакета

        *( uint64_t* )pool = process_info; // Указатель на буфер с данными
        *( DWORD* )( pool + 0x28 ) = 1; // Статус
        *( DWORD* )( pool + 0x30 ) = 0x7429AC76; // Тип пакета
        *( DWORD* )( pool + 0x40 ) = 0; // Флаги (?)
        *( uint64_t* )( pool + 0x38 ) = 0; // Дополнительная информация

        // Отправляем пакет на сервер

        send_telemetry_packet( pool, 0 );
        ex_free_pool_with_tag( process_info );
    }
}

1. Копирование информации о процессе ( парс пе хеадера процесса в нужный формат, копирование названия процесса ( если установлен флаг дополнительной информации ) )
2. Выделение пулла, заполнение его информацией
3. Отправление на сервер

* Проверка целостности системных драйверов:

Еак проходится по каждому загруженному драйверу ( он их получает с помощью zw_query_system_information ( SystemModuleInformation ) ), получает их DriverObject и проходится по каждому MajorFunction, проверяя адрес диспатча )
Анти-чит делает это для того, чтобы выявлять хуки функций драйверов. Чаще всего они используются для создания спуферов или коммуникации с юзермодом.
Псевдокод:

C++:
Expand Collapse Copy
bool check_driver_dispatch( PDRIVER_OBJECT driver_object, uint32_t index, void* buffer, uint32_t* status_code ) {
    if ( status_code )
        *status_code = 0;

    if ( !driver_object ) {
        if ( status_code )
            *status_code = 2; // driver_object драйвера не найден

        return false;
    }

    const auto major_function = driver_object->MajorFunction[ index ];

    if ( !major_function ) {
        if ( status_code )
            *status_code = 4; // Функция с таким индексом не имеет реализации

        return false;
    }

    const auto base_address = driver_object->DriverStart;
    const auto size = driver_object->DriverSize;

    if ( !base_address
         || !size ) {
        if ( status_code )
            *status_code = 5; // Не удалось определить границы драйвера

        return false;
    }

    bool found{ };

    if ( major_function >= base_address && major_function <= ( base_address + size ) ) {
        if ( status_code )
            *status_code = 7; // Проверка пройдена, функция не была перехвачена

        found = true;

        if ( buffer ) {
            *( uint32_t* )buffer = major_function - base_address; // Сохраняем RVA функции
            *( uint32_t* )( ( uintptr_t )buffer + 4 ) = size; // Сохраняем размер драйвера

            memcpy( ( char* )buffer + 8, get_module_name( base_address ), 255 ); // Копируем имя модуля
        }
    } else
        if ( status_code )
            *status_code = 6; // Функция указывает в чужую память => детект

    return found;
}

1. Проверка нa то, находится ли функция в модуле
2. Копирование информации о драйвере
3. Отправление репорта, если status_code != 7 || !found ( отправление репорта находится в той функции, где вызывается проверка на диспатч )

* Шифрование репортов:

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

C++:
Expand Collapse Copy
void create_encrypted_key( ) {
    const auto buffer = reinterpret_cast< uint8_t* >( allocate_pool( 0x1000 ) );

    if ( !buffer )
        return;

    // 0x2C4 -> SystemExpirationDate
    // 0x14 -> SystemTime
    // 0x260 -> BootId

    const auto v16 = *( ULONG* ) 0xFFFFF780000002C4 ^ *( ULONG* )0xFFFFF78000000014;
    const auto v17 = *( ULONG* )0xFFFFF78000000260 ^ *( ULONG* )0xFFFFF78000000008;
    const auto v18 = ( 1533831705 * ( buffer >> 2 ) ) ^ *( LONG* )0xFFFFF78000000018;
    const auto v19 = *( LONG* )0xFFFFF7800000000C ^ ( 1533831705 * ( ( unsigned __int64 )&buffer >> 2 ) );

    buffer[ 4 ] = v18 ^ 0x97B9BC33;
    *buffer = -96604097;
    buffer[ 1 ] = -345804981;
    buffer[ 5 ] = v17 ^ 0xE220A00D;
    buffer[ 2 ] = 144253742;
    buffer[ 7 ] = 897887310;
    buffer[ 3 ] = v16 ^ 0xA868AC37;
    buffer[ 8 ] = -123907290;
    buffer[ 6 ] = v19 ^ 0x79BB2376;

    /* 
      v15 = buffer;
      v20 = v19 ^ v16 ^ v18 ^ v17 ^ 0x4C53A039;
    */
}

Генерация энкриптнутого ключа для дальнейшего шифрования буфера с репортом.

Вывод:
- С помощью данной статьи вы можете узнать полезную информацию о EasyAntiCheat'e. Надеюсь, эта статья была полезной!
 
Последнее редактирование:
Ого, инновации! Проверка MajorFunction через DriverStart? ЕАК такими темпами к 2030-му году додумается, что можно еще и IRP-хуки чекать. Автору спасибо за экскурс в мезозойскую эру реверс-инжиниринга, ждем вторую часть про то, как "неожиданно" работает ReadProcessMemory.
 
Ого, инновации! Проверка MajorFunction через DriverStart? ЕАК такими темпами к 2030-му году додумается, что можно еще и IRP-хуки чекать. Автору спасибо за экскурс в мезозойскую эру реверс-инжиниринга, ждем вторую часть про то, как "неожиданно" работает ReadProcessMemory.
Ого, инновации! Пихать паблик треды с других форумов в нейронку и репостить их на югейм, не прочитав из них не слова, и не разбираясь в теме. Автору спасибо за экскурс в мир ии клоунов, ждем еще посты сделанные через ии.
 
Ого, инновации! Проверка MajorFunction через DriverStart? ЕАК такими темпами к 2030-му году додумается, что можно еще и IRP-хуки чекать. Автору спасибо за экскурс в мезозойскую эру реверс-инжиниринга, ждем вторую часть про то, как "неожиданно" работает ReadProcessMemory.
а я то тут причем, если я код не выдумываю. "Основываясь на результате обратной разработки, мы рассмотрим:". будь ты немного поумнее, то ты мог бы сам в этом убедиться.
 
Назад
Сверху Снизу