Гайд Анализ внутреннего модуля EasyAntiCheat

Пользователь
Пользователь
Статус
Оффлайн
Регистрация
6 Май 2025
Сообщения
110
Реакции
59
Данная тема создана в образовательных целях. Она не направлена на нанесение вреда EasyAntiCheat'y или другим организациям. Обратная разработка анти-чита была проведена с целью изучения и пополнения знаний о работе EasyAntiCheat'a.
В качестве примера, мы возьмем игру Rust ( в ней как раз находится последняя версия анти чита )

О чем пойдет речь в этой теме:

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

Введение:

EasyAntiCheat делится на 3 части:

eac_launcher.dll, eac_internal.dll, easy_anti_cheat.sys

В этой теме мы рассмотрим только внутренний модуль и немного лаунчер

eac_launcher - модуль, который загружается в rust.exe. С помощью него происходит запуск драйвера анти чита и передача буфера внутреннего модуля
eac_internal - модуль, который загружается в rustclient.exe. В этом модуле находятся основные проверка в игре ( сканирование памяти, общения с анти читом, отправка репортов на сервер )

Анализ:

Изначально, буфер внутреннего модуля передается зашифрованным. Немного пореверсив лаунчер, я нашел функцию шифровки модуля:

C++:
Expand Collapse Copy
    void encrypt_module( uint8_t* address, const uint32_t size ) {
        uint32_t new_size{ };
        address[ size - 1 ] += 3 - 3 * size;

        while ( new_size < size ) {
            address[ new_size ] -= -3 * new_size - address[ new_size + 1 ];
            ++new_size;
        }
    }

На основе этого алгоритма, я сделал и функцию дешифрования модуля:

C++:
Expand Collapse Copy
    void decrypt_module( uint8_t* address, const uint32_t size ) {
        uint32_t new_size = size - 2;
        address[ size - 1 ] += 3 - 3 * size;

        while ( new_size ) {
            address[ new_size ] += -3 * new_size - address[ new_size + 1 ];
            --new_size;
        }

        address[ 0 ] -= address[ 1 ];
    }

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

По адресу [ RSP + 0x28 ] будет находится указатель на энкриптнутый буфер
По адресу [ RSP + 0x30 ] будет находится указатель на размер зашифрованного буфера

Создание дампера:

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

Поиск функции сетапа анти чита и установление на нее точки останова:

C++:
Expand Collapse Copy
   void thread( ) {
        while ( 1 ) {
            MEMORY_BASIC_INFORMATION mbi{ };

            for ( uint64_t address{ }; VirtualQuery( reinterpret_cast< void* >( address ), &mbi, sizeof( MEMORY_BASIC_INFORMATION ) ); address += mbi.RegionSize ) {
                if ( mbi.Protect == PAGE_EXECUTE_READWRITE && mbi.State & MEM_COMMIT ) {
                    const auto& found = reinterpret_cast< void* >( utils::find_pattern( address, mbi.RegionSize, ( uint8_t* )"\x44\x89\x4C\x24\x00\x4C\x89\x44\x24\x00", "xxxx?xxxx?" ) );

                    if ( !found ) {
                        Sleep( 5 );
                        continue;
                    }

                    // found

                    hooks::hook.handler = &hooks::setup_easy_anti_cheat;
                    hooks::hook.hook_address = found;
                    hooks::hook.original = *reinterpret_cast< uint8_t* >( found );

                    *reinterpret_cast< uint8_t* >( found ) = 0xCC;
                    return ExitThread( 0xeac );
                }
            }
        }
    }

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

Большое спасибо за прочтение, это была моя первая статья, надеюсь на конструктивную критику.

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.
 
/del
p.s. resend due to a long approve
 
Последнее редактирование:
Мастер.
 
Не дописал в предыдущем сообщении официальные названия модулей анти-чита:
Код:
Expand Collapse Copy
1. "eac_launcher" => "easyanticheat_OS.eac.trampoline"
2. "eac_payload" => "easyanticheat_OS.eac.ingame"

Абсолютно все сдампленные модули под все системы в одном паке: Rust (07.05.2025)
 
Не дописал в предыдущем сообщении официальные названия модулей анти-чита:
Код:
Expand Collapse Copy
1. "eac_launcher" => "easyanticheat_OS.eac.trampoline"
2. "eac_payload" => "easyanticheat_OS.eac.ingame"

Абсолютно все сдампленные модули под все системы в одном паке: Rust (07.05.2025)
Забыл про пароль: 321
Модеры, апрувните уже сообщение выше, пожалуйста.
 
More powerful guide: see ( x86 non-EOS anti-cheat, but hasn't changed much since then ) ( no-ad ).
Нету рестора оригинального байта на IP так, что после дампа лаунчер крашнет.

Пожалуй, дополню.
По адресу [ RSP + 0x28 ] будет находится указатель на энкриптнутый буфер
По адресу [ RSP + 0x30 ] будет находится указатель на размер зашифрованного буфера
Это будет 5 и 6 аргумент: see ( no-ad ).

Как игра инициализируется анти-чит:
Код:
Expand Collapse Copy
1. Загружается "easyanticheat_OS.eac.ingame" (официальное название) в игру.
2. Windows: драйвер анти-чита хукает "GetProcAddress" в игре; MacOS и Linux: файл античита находится на ПК.
3. Следом игра запускается, инициализируя Epic Online Services (EOS).
4. Происходит вызов "EOS_AntiCheatClient_BeginSession".
5. Из этого вызова сам "EOSSDK-OS-Shipping.xxx" вызывает инициализацию анти-чита Windows: по "GetProcAddress", подставляя в аргумент hModule - 0xE08 (0xEAC на non-EOS билдах) и получая адреса функций из "easyanticheat_OS.eac.ingame", MacOS & Linux: получает адресса функций из "easyanticheat_OS.eac.ingame" по dlsym.
6. Расписывать целиком внутреннюю работу "easyanticheat_OS.eac.ingame" я не буду, не давая легкий метод по эмуляции анти-чита, но вы уже можете начать хукая "GetProcAddress" и отлавливая его вызовы.

"easyanticheat_OS.eac.ingame":
Код:
Expand Collapse Copy
1. Общается с драйвером.
2. Отправляет пакеты.
3. Общается со Steam.
4. Общается с EOS.
5. Для некоторых игр в нем есть CERBERUS, который отслеживает геймплей игры и собирает следующие данные:
   - "game_round_start"
   - "map_name"
   - "game_round_end"
   - "winning_team_id"
   - "player_spawn"
   - "player"
   - "team_id"
   - "character_id"
   - "player_despawn"
   - "player_death"
   - "player_killer"
   - "player_revive"
   - "player_revived"
   - "player_reviving"
   - "player_tick"
   - "player_position"
   - "player_viewrotation"
   - "player_health"
   - "player_tickflags"
   - "player_useweapon"
   - "player_fov"
   - "weapon_id"
   - "melee_attack"
   - "player_takedamage"
   - "player_victim"
   - "player_victim_position"
   - "player_victim_viewrotation"
   - "player_attacker"
   - "player_attacker_position"
   - "player_attacker_viewrotation"
   - "player_attacker_fov"
   - "hitbone_id"
   - "damage_taken"
   - "damage_flags"
   - "player_downed"
   - "victim"
   - "victim_shots_fired"
   - "victim_shots_landed"
   - "attacker"
   - "attacker_shots_fired"
   - "attacker_shots_landed"
   - "game_round_start_v2"
   - "mode_name"
   - "round_time_seconds"
   - "start_frame_number"
   - "start_delta_seconds"
   - "game_round_start_v3"
   - "gamesessionid"
   - "player_use_ability"
   - "ability_id"
   - "ability_duration_ms"
   - "ability_cooldown_ms"
   - "ability_effect_flags"

Также могу дать фору некоторым людям: для эмуляции античита не нужно ничего девиртуализировать, все интересное на видном месте. Можно сделать редиректы на оригинальный модуль в вашем эмуляторе. Но желающие могут с легкостью девиртуализировать его, так как стоит немного измененный VMProtect 2, да и сами вмки не сложные.
1746639576085.png


По теме: достаточно хорошая, но материала по этому и так много.
+rep
 
Данная тема создана в образовательных целях. Она не направлена на нанесение вреда EasyAntiCheat'y или другим организациям. Обратная разработка анти-чита была проведена с целью изучения и пополнения знаний о работе EasyAntiCheat'a.
В качестве примера, мы возьмем игру Rust ( в ней как раз находится последняя версия анти чита )

О чем пойдет речь в этой теме:

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

Введение:

EasyAntiCheat делится на 3 части:

eac_launcher.dll, eac_internal.dll, easy_anti_cheat.sys

В этой теме мы рассмотрим только внутренний модуль и немного лаунчер

eac_launcher - модуль, который загружается в rust.exe. С помощью него происходит запуск драйвера анти чита и передача буфера внутреннего модуля
eac_internal - модуль, который загружается в rustclient.exe. В этом модуле находятся основные проверка в игре ( сканирование памяти, общения с анти читом, отправка репортов на сервер )

Анализ:

Изначально, буфер внутреннего модуля передается зашифрованным. Немного пореверсив лаунчер, я нашел функцию шифровки модуля:

C++:
Expand Collapse Copy
    void encrypt_module( uint8_t* address, const uint32_t size ) {
        uint32_t new_size{ };
        address[ size - 1 ] += 3 - 3 * size;

        while ( new_size < size ) {
            address[ new_size ] -= -3 * new_size - address[ new_size + 1 ];
            ++new_size;
        }
    }

На основе этого алгоритма, я сделал и функцию дешифрования модуля:

C++:
Expand Collapse Copy
    void decrypt_module( uint8_t* address, const uint32_t size ) {
        uint32_t new_size = size - 2;
        address[ size - 1 ] += 3 - 3 * size;

        while ( new_size ) {
            address[ new_size ] += -3 * new_size - address[ new_size + 1 ];
            --new_size;
        }

        address[ 0 ] -= address[ 1 ];
    }

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

По адресу [ RSP + 0x28 ] будет находится указатель на энкриптнутый буфер
По адресу [ RSP + 0x30 ] будет находится указатель на размер зашифрованного буфера

Создание дампера:

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

Поиск функции сетапа анти чита и установление на нее точки останова:

C++:
Expand Collapse Copy
   void thread( ) {
        while ( 1 ) {
            MEMORY_BASIC_INFORMATION mbi{ };

            for ( uint64_t address{ }; VirtualQuery( reinterpret_cast< void* >( address ), &mbi, sizeof( MEMORY_BASIC_INFORMATION ) ); address += mbi.RegionSize ) {
                if ( mbi.Protect == PAGE_EXECUTE_READWRITE && mbi.State & MEM_COMMIT ) {
                    const auto& found = reinterpret_cast< void* >( utils::find_pattern( address, mbi.RegionSize, ( uint8_t* )"\x44\x89\x4C\x24\x00\x4C\x89\x44\x24\x00", "xxxx?xxxx?" ) );

                    if ( !found ) {
                        Sleep( 5 );
                        continue;
                    }

                    // found

                    hooks::hook.handler = &hooks::setup_easy_anti_cheat;
                    hooks::hook.hook_address = found;
                    hooks::hook.original = *reinterpret_cast< uint8_t* >( found );

                    *reinterpret_cast< uint8_t* >( found ) = 0xCC;
                    return ExitThread( 0xeac );
                }
            }
        }
    }

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

Большое спасибо за прочтение, это была моя первая статья, надеюсь на конструктивную критику.

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.
разберем несколько крутых вещей
если size < 2 ты получаешь new_size = -1 что приведёт к переполнению std::uint32_t
ты читаешь address[new_size + 1] не проверяя что это в допустимом диапазоне
на последней итерации new_size == size - 2 но ты читаешь address[size - 1] это ладно еще но бтв если где то size передан неверно может произойти out of bounds...

проблема также есть и в функциии thread
пример проблемы
если VirtualQuery вернет по какой то причине mbi.RegionSize == 0? тогда у тебя произойдет infinite loop.....
гайд конечно полезный но сам код написан некачественно если честно
 
это невозможно, в "eac_launcher.dll" всегда статично вшивается внутриигровой модуль с сайзом больше двух, не знаю почему ты приебался к дешифровке, так как такая же 1:1 в самом анти-чите.
алсо согласен с тобой, что код хуйня.
 
это невозможно, в "eac_launcher.dll" всегда статично вшивается внутриигровой модуль с сайзом больше двух, не знаю почему ты приебался к дешифровке, так как такая же 1:1 в самом анти-чите.
алсо согласен с тобой, что код хуйня.
то что в античите так сделано не значит что это хорошо
у еак приоритет защита а не читаемость или надежность внешней интеграции
также проверка на сайз это хорошая практика в любом случае
 
надежность внешней интеграции
не согласен с тобой, у них в коде дохуя проверок, которые режут перформанс, но не сработают никогда.
для них важны как и защита, так и надёжность
 
не согласен с тобой, у них в коде дохуя проверок, которые режут перформанс, но не сработают никогда.
для них важны как и защита, так и надёжность
можешь показать хотя бы одну?
 
Не знал, что девочка стример умеет в реверс :))))
 
Назад
Сверху Снизу