Начинающий
Начинающий
- Статус
- Оффлайн
- Регистрация
- 25 Фев 2026
- Сообщения
- 6
- Реакции
- 2
Всем привет 
Читал разборы на американском сайте про обход EAC и BattlEye. Сам я больше по ассемблеру и низкоуровневой оптимизации, но вот решил попробовать написать что-то для rust. И сразу же попал в историю, которую с моим опытом не могу объяснить.
Задача сделать так, чтобы мой код для аима светился в памяти. Все знают, что современные античиты смотрят не только на сигнатуры, но и на аномалии в page tables, например, наличие исполняемых страниц в куче или странные флаги у регионов памяти. Я подумал: а почему бы не использовать huge pages 2M для выделения памяти под хук? Если игра сама активно использует огромные страницы для своих структур, античит может посчитать мои страницы за свои.
Схема: выделяю память под бинд для функции трейса пули через ммап с флагами MAP_HUGETLB (нужны права, но в ядре 5.4+ можно и без капов через MAP_ANONYMOUS или MAP_HUGETLB). Копирую туда свой ассемблерный код, который делает вызов в оригинальную функцию, а потом прыгает обратно. Чтобы античит не нашел этот код по хэшам, я добавил обфускацию, динамически расшифровываю тело хука перед копированием. Вроде бы всё логично.
```cpp
// мойпсевдокод выделения памяти под шеллкод
void* alloc_huge_exec_page() {
void* mem = mmap(nullptr, 2 * 1024 * 1024,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
if (mem == MAP_FAILED) {
// падаю в маллок если huge не доступны
mem = aligned_alloc(0x1000, 4096);
mprotect(mem, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
}
return mem;
}
Пишу бинд для функции ProcessHit или что-то подобное. Для теста просто перехватываю вызов, логирую позицию и возвращаю управление. Собираю в режиме O2 на Clang 16. На тестовом сервере с выключенным античитом всё работает: хук срабатывает, задержки в пределах нормы. Но как только включаю EAC начинаются странности. Игра не вылеиает, но мой хук срабатывает через раз, а иногда игра вылетает с нарушением доступа к памяти в моём коде. При этом если я использую обычные 4K страницы ммап без HUGETLB и EAC не ругается.
Начинаю копать глубже. Снимаю счётчики через perf для процесса игры. Сравниваю два запуска: с huge-страницами для хука и с обычными. С обычными страницами: L1/L2 cache misses в норме, dTLB-load-misses около 0.5%, iTLB-load-misses вообще в районе шума 0.01%. С huge-страницами dTLB-load-misses упали до 0.1% как и ожидалось, но iTLB-load-misses взлетели до 10%. И одновременно выросло количество промахов L2 кэша в области кода.
Смотрю в ассемблер своего хука (вот кусок, который генерирует Clang для пролога, мойпсевдокод):
```assembly
; пролог на обычной странице 4K
Hooked_ProcessHit:
push rbp
mov rbp, rsp
sub rsp, 0x20
mov [rbp-0x18], rdi
mov [rbp-0x20], rsi
call qword ptr [log_hit] ; мой логгер
mov rdi, [rbp-0x18]
mov rsi, [rbp-0x20]
jmp qword ptr [original_ProcessHit] ; переход обратно
; тот же код, но на huge-странице 2M машинный код идентичный, адреса те же
По идее код одинаковый. Почему же iTLB начинает выёпываться? Я думаю, дело в том, что huge-страница для кода это редкость. В игре весь исполняемый код секции .text обычно размещается на 4K страницах. Когда процессор видит, что инструкции идут со страницы размером 2M, он может подругому работать с prefetch-ером. Но главное античит, возможно, мониторит частоту iTLB misses. Высокий уровень промахов может быть признаком того, что исполняемый код прыгает по разным huge-страницам, а это нехарактерно для обычной игры. Я заметил, что код на huge-странице часто выгружается из TLB при переключении контекста. Видимо ядро сбрасывает TLB для огромных страниц подругому чем для мелких. Из-за этого каждый раз, когда игра получает процессорное время, мой код заново грузится в iTLB, вызывая промахи. По итогу хотел спрятаться за huge-страницами, а получил прямо противоположный эффект. Античит теперь видит аномально высокие iTLB-промахи в контексте игры и, вероятно, флагает это как подозрительное поведение. А access violation может возникать из-за того, что страница временно не имеет флага EXEC (если античит динамически меняет права доступа через ядерный модуль).
Вопрос: как правильно маскировать исполняемый код, чтобы не вылезать из статистики TLB? Может, надо использовать не huge pages, а наоборот, мешать свой код с легитимным через page aliasing? Или античиты уже смотрят на page walk depth и использование 2M страниц только в data-регионах?
Заранее спасибо за идеи, а то я уже закопался в даташиты к интел и посты по EAC

Читал разборы на американском сайте про обход EAC и BattlEye. Сам я больше по ассемблеру и низкоуровневой оптимизации, но вот решил попробовать написать что-то для rust. И сразу же попал в историю, которую с моим опытом не могу объяснить.
Задача сделать так, чтобы мой код для аима светился в памяти. Все знают, что современные античиты смотрят не только на сигнатуры, но и на аномалии в page tables, например, наличие исполняемых страниц в куче или странные флаги у регионов памяти. Я подумал: а почему бы не использовать huge pages 2M для выделения памяти под хук? Если игра сама активно использует огромные страницы для своих структур, античит может посчитать мои страницы за свои.
Схема: выделяю память под бинд для функции трейса пули через ммап с флагами MAP_HUGETLB (нужны права, но в ядре 5.4+ можно и без капов через MAP_ANONYMOUS или MAP_HUGETLB). Копирую туда свой ассемблерный код, который делает вызов в оригинальную функцию, а потом прыгает обратно. Чтобы античит не нашел этот код по хэшам, я добавил обфускацию, динамически расшифровываю тело хука перед копированием. Вроде бы всё логично.
```cpp
// мойпсевдокод выделения памяти под шеллкод
void* alloc_huge_exec_page() {
void* mem = mmap(nullptr, 2 * 1024 * 1024,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
if (mem == MAP_FAILED) {
// падаю в маллок если huge не доступны
mem = aligned_alloc(0x1000, 4096);
mprotect(mem, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
}
return mem;
}
Пишу бинд для функции ProcessHit или что-то подобное. Для теста просто перехватываю вызов, логирую позицию и возвращаю управление. Собираю в режиме O2 на Clang 16. На тестовом сервере с выключенным античитом всё работает: хук срабатывает, задержки в пределах нормы. Но как только включаю EAC начинаются странности. Игра не вылеиает, но мой хук срабатывает через раз, а иногда игра вылетает с нарушением доступа к памяти в моём коде. При этом если я использую обычные 4K страницы ммап без HUGETLB и EAC не ругается.
Начинаю копать глубже. Снимаю счётчики через perf для процесса игры. Сравниваю два запуска: с huge-страницами для хука и с обычными. С обычными страницами: L1/L2 cache misses в норме, dTLB-load-misses около 0.5%, iTLB-load-misses вообще в районе шума 0.01%. С huge-страницами dTLB-load-misses упали до 0.1% как и ожидалось, но iTLB-load-misses взлетели до 10%. И одновременно выросло количество промахов L2 кэша в области кода.
Смотрю в ассемблер своего хука (вот кусок, который генерирует Clang для пролога, мойпсевдокод):
```assembly
; пролог на обычной странице 4K
Hooked_ProcessHit:
push rbp
mov rbp, rsp
sub rsp, 0x20
mov [rbp-0x18], rdi
mov [rbp-0x20], rsi
call qword ptr [log_hit] ; мой логгер
mov rdi, [rbp-0x18]
mov rsi, [rbp-0x20]
jmp qword ptr [original_ProcessHit] ; переход обратно
; тот же код, но на huge-странице 2M машинный код идентичный, адреса те же
По идее код одинаковый. Почему же iTLB начинает выёпываться? Я думаю, дело в том, что huge-страница для кода это редкость. В игре весь исполняемый код секции .text обычно размещается на 4K страницах. Когда процессор видит, что инструкции идут со страницы размером 2M, он может подругому работать с prefetch-ером. Но главное античит, возможно, мониторит частоту iTLB misses. Высокий уровень промахов может быть признаком того, что исполняемый код прыгает по разным huge-страницам, а это нехарактерно для обычной игры. Я заметил, что код на huge-странице часто выгружается из TLB при переключении контекста. Видимо ядро сбрасывает TLB для огромных страниц подругому чем для мелких. Из-за этого каждый раз, когда игра получает процессорное время, мой код заново грузится в iTLB, вызывая промахи. По итогу хотел спрятаться за huge-страницами, а получил прямо противоположный эффект. Античит теперь видит аномально высокие iTLB-промахи в контексте игры и, вероятно, флагает это как подозрительное поведение. А access violation может возникать из-за того, что страница временно не имеет флага EXEC (если античит динамически меняет права доступа через ядерный модуль).
Вопрос: как правильно маскировать исполняемый код, чтобы не вылезать из статистики TLB? Может, надо использовать не huge pages, а наоборот, мешать свой код с легитимным через page aliasing? Или античиты уже смотрят на page walk depth и использование 2M страниц только в data-регионах?
Заранее спасибо за идеи, а то я уже закопался в даташиты к интел и посты по EAC

