AssaultCube і захист від чітів
AssaultCube і захист від чітів
🎯 Мета розділу
- Застосувати весь курс до реальної гри: сканування, структура, ланцюжок вказівників, тренер.
- Використати відкритий код AssaultCube, щоб передбачити розкладку структури гравця.
- Запустити тренер лише в офлайн-режимі (тренування з ботами).
- Знати, які прийоми захищають гру від методів цього курсу.
- Зрозуміти головне правило: клієнту довіряти не можна.
- Підсумувати вивчені інструменти та наступні кроки.
🔑 Ключові терміни
- AssaultCube: безкоштовна FPS з відкритим кодом на рушії Cube.
player1: глобальний вказівник гри на локального гравця.- Relative offset: відстань між полями структури, відома з коду.
- Авторитетний сервер (server-authoritative): істину про стан гри визначає сервер, клієнт лише показує.
- Обфускація значень: зберігання числа в зашифрованому чи зміненому вигляді.
- Контроль цілісності: перевірка, що код і дані не змінено.
- Антічіт: окремий драйвер чи модуль, який шукає чіти.
Практика: тренер для AssaultCube на Windows, Linux і macOS
AssaultCube підходить для навчання з трьох причин: вона безкоштовна, її код відкритий, а офлайн-режим з ботами не зачіпає інших гравців.
Встановлення гри
Версії та файли беріть зі сторінки cubers.net/download (актуальна версія на час написання — 1.3.0.2). Вихідний код: репозиторій assaultcube/AC на GitHub.
assaultcube.bat чи bin_win32\ac_client.exe. Клієнт 32-бітний, тому вказівники займають 4 байти.sudo apt install assaultcube) або зберіть із вихідного коду за інструкцією репозиторію.Розкладка структури з вихідного коду
У source/src/entity.h клас гравця наслідує playerstate. Його перші поля:
class playerstate {
public:
int health, armour;
int primary, nextprimary;
int gunselect;
bool akimbo;
int ammo[NUMGUNS], mag[NUMGUNS], gunwait[NUMGUNS];
...
Номери зброї: GUN_KNIFE = 0, GUN_PISTOL, GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_GRENADE, GUN_AKIMBO, NUMGUNS (9 видів).
З цього отримуємо відносні зміщення від поля health (за умови, що int займає 4 байти):
| Поле | Зміщення від health | Пояснення |
|---|---|---|
health | +0 | |
armour | +4 | наступний int |
primary | +8 | |
nextprimary | +12 | |
gunselect | +16 | |
akimbo | +20 | bool, 1 байт, далі 3 байти вирівнювання |
ammo[0] | +24 | перший int після вирівнювання |
ammo[i] | +24 + 4·i | ammo[GUN_ASSAULT] = +48 |
mag[0] | +60 | 24 + 9·4 |
Абсолютного зміщення від початку об'єкта гравця тут нема: перед playerstate у класі є інші поля й таблиця віртуальних функцій, а їхній розмір залежить від версії і платформи. Це зміщення знаходимо скануванням.
health дамп пам'яті поруч має показати значення броні одразу за здоров'ям, а далі — числа, що змінюються при стрільбі та перезарядці.Кроки
Запустити гру й почати бій з ботами
У меню виберіть Practise with bots і мапу. Запам'ятайте здоров'я (спочатку 100).
Знайти health
Скануйте значення, отримуйте шкоду від ботів, повторюйте (розділ «Сканування пам'яті»). Cheat Engine, scanmem або trainer scan. Перевірте сусіда: за адресою +4 лежить броня.
Знайти слот player1 і зміщення health
Початок об'єкта гравця заздалегідь невідомий, тому шукаємо ланцюжок до адреси health зворотним пошуком. У Cheat Engine це Pointer scan for this address: результат має вигляд ac_client.exe+XXXXX → зміщення. У scanmem і власному сканері шукайте 4- або 8-байтове значення, що дорівнює адресі_health − k для малих k (кратних 4), до 0x400. Слот, у якому лежить база гравця, і є player1, а k — зміщення health. Якщо бінарник має символи, слот видно одразу: nm ac_client | grep player1.
Зафіксувати ланцюжок
Перезапустіть гру, прочитайте player1, додайте зміщення health і переконайтеся, що там знову 100. Тоді ланцюжок стабільний.
Запустити тренер
Команда ac приймає PID, адресу слота player1, розмір вказівника (4 або 8) і зміщення health.
sudo ./build/trainer ac <pid> <адреса_слота_player1> <4|8> <health_offset>
Тренер читає вказівник щоразу, пише здоров'ям і бронею 100, а в ammo[GUN_ASSAULT] — 20. Він використовує відносні зміщення з таблиці, тому для іншої зброї змініть індекс.
Що можна додати далі
- Окремі режими: нескінченні гранати, миттєва перезарядка (запис у
gunwait). - Перехід з external на internal (розділ «Internal-чіт»): знайти
player1за сигнатурою. - Читання позицій інших гравців у списку
playersі вивід відстані. - Візуалізація (ESP) вимагає матриці проєкції і відрізняється складністю; її не розглядаємо.
source/src/weapon.cpp функцію перезарядки або подивіться, яке з двох значень змінюється при пострілі: те, що зменшується, — набої у магазині.ammo[GUN_GRENADE] і додайте запис у тренер.GUN_GRENADE = 7, отже зміщення від health дорівнює 24 + 4·7 = 52. Додайте p.write_value<int32_t>(player + health_off + 52, 2); у цикл.Захист: як розробники ускладнюють читання і зміну пам'яті
Усе, що виконується на комп'ютері гравця, гравець може прочитати й змінити. Тому захист будується в шарах, і найсильніший з них — не на клієнті.
Рівень 1. Сервер вирішує, клієнт показує
Якщо health рахує і зберігає сервер, зміна числа у клієнті лише змінює відображення. Наступний пакет від сервера поверне правду. Це єдиний прийом, який працює проти зміни даних. Чіт тоді намагається діяти в межах правил: прицілювання (aimbot) чи показ ворогів крізь стіни, бо клієнт мусить знати частину стану.
Рівень 2. Утруднити пошук значень
| Прийом | Що робить | Як обходять |
|---|---|---|
| Шифрування значення | у пам'яті health ^ ключ, а не 100 | сканування «невідоме значення», пошук функції дешифрування |
| Дублювання | значення зберігається двічі, порівнюється | змінюють обидва |
| Розбиття на частини | число в двох половинах | аналіз коду, що збирає їх |
| Рандомізація адрес | ASLR, переміщення структур | ланцюжки вказівників, сигнатури |
Усе це збільшує час реверсу, але не робить його неможливим: код, що читає значення, мусить його розшифрувати, і ця функція знаходиться налагоджувачем (розділ «Налагоджувач»).
Рівень 3. Контроль цілісності і налагоджувачів
- Контрольна сума секції коду: змінений NOP змінює хеш.
- Виявлення налагоджувача:
IsDebuggerPresent(Windows),ptrace(PT_DENY_ATTACH)(macOS), полеTracerPidу/proc/self/status(Linux). - Перевірка списку завантажених модулів: чужа DLL чи
.dylibу процесі підозріла. - Hardened runtime і SIP на macOS,
ptrace_scopeна Linux обмежують доступ до чужого процесу на рівні ОС.
Рівень 4. Антічіт-драйвери
Комерційні рішення (EasyAntiCheat, BattlEye, Vanguard) працюють у ядрі ОС, бачать усі дескриптори процесів, перевіряють підписи драйверів, шукають відомі сигнатури чітів і надсилають телеметрію на сервер. Їхній обхід — це постійна гонка, а використання тягне блокування акаунта та апаратний бан.
Підсумок курсу
Пам'ять і процеси
Кожен процес має власні віртуальні адреси, а доступ до чужого дає ОС за спеціальним викликом.
Сканування
Зміна значення в грі + повторне сканування залишає одну адресу.
Структури і вказівники
Адреса поля — це база + зміщення. Базу знаходимо ланцюжком вказівників, який переживає перезапуск.
Асемблер і налагоджувач
Watchpoint на запис показує інструкцію, що змінює значення; патч змінює її поведінку.
External і internal
Зовнішній тренер простіший, internal-модуль потужніший і ближчий до коду гри.
Захист
Серверна авторитетність, шифрування, цілісність і антічіт ускладнюють усі ці кроки.
Що вивчати далі
- Ghidra і IDA: аналіз цілих функцій і побудова структур автоматично.
- Хуки функцій (MinHook, Frida): перехоплення викликів замість патчу байтів.
- Розбір шкідливого ПЗ та CTF-завдання на реверс (crackme, pwn.college).
- Написання власного захисту: шифрування значень у грі-мішені та спроба його зламати.
Player::health у грі-мішені так, щоб у пам'яті лежало health ^ 0x5A5A5A5A. Переконайтеся, що пошук значення 100 його більше не знаходить, а потім знайдіть його іншим способом.xor у дизасемблері через watchpoint на сусіднє поле.