Реверс-інжиніринг і чіти

AssaultCube і захист від чітів

Практика на AssaultCube та прийоми захисту, які ускладнюють реверс.

AssaultCube і захист від чітів

🎯 Мета розділу

  • Застосувати весь курс до реальної гри: сканування, структура, ланцюжок вказівників, тренер.
  • Використати відкритий код AssaultCube, щоб передбачити розкладку структури гравця.
  • Запустити тренер лише в офлайн-режимі (тренування з ботами).
  • Знати, які прийоми захищають гру від методів цього курсу.
  • Зрозуміти головне правило: клієнту довіряти не можна.
  • Підсумувати вивчені інструменти та наступні кроки.

🔑 Ключові терміни

  • AssaultCube: безкоштовна FPS з відкритим кодом на рушії Cube.
  • player1: глобальний вказівник гри на локального гравця.
  • Relative offset: відстань між полями структури, відома з коду.
  • Авторитетний сервер (server-authoritative): істину про стан гри визначає сервер, клієнт лише показує.
  • Обфускація значень: зберігання числа в зашифрованому чи зміненому вигляді.
  • Контроль цілісності: перевірка, що код і дані не змінено.
  • Антічіт: окремий драйвер чи модуль, який шукає чіти.

Практика: тренер для AssaultCube на Windows, Linux і macOS

AssaultCube підходить для навчання з трьох причин: вона безкоштовна, її код відкритий, а офлайн-режим з ботами не зачіпає інших гравців.

Тренуйтеся лише в режимі Practise with bots або на власному сервері. Використання тренера на публічних серверах руйнує гру іншим гравцям і порушує правила серверів.

Встановлення гри

Версії та файли беріть зі сторінки cubers.net/download (актуальна версія на час написання — 1.3.0.2). Вихідний код: репозиторій assaultcube/AC на GitHub.

Завантажте інсталятор або ZIP, запустіть assaultcube.bat чи bin_win32\ac_client.exe. Клієнт 32-бітний, тому вказівники займають 4 байти.

Розкладка структури з вихідного коду

У 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+20bool, 1 байт, далі 3 байти вирівнювання
ammo[0]+24перший int після вирівнювання
ammo[i]+24 + 4·iammo[GUN_ASSAULT] = +48
mag[0]+6024 + 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. Він використовує відносні зміщення з таблиці, тому для іншої зброї змініть індекс.

Цей тренер у курсі не запускався проти справжньої збірки AssaultCube на цій машині: код компілюється, а логіку ланцюжка перевірено на грі-мішені. Якщо ваші числа не збігаються з таблицею, прочитайте розкладку у власному дампі: вона виграє перед будь-якою таблицею.

Що можна додати далі

  • Окремі режими: нескінченні гранати, миттєва перезарядка (запис у gunwait).
  • Перехід з external на internal (розділ «Internal-чіт»): знайти player1 за сигнатурою.
  • Читання позицій інших гравців у списку players і вивід відстані.
  • Візуалізація (ESP) вимагає матриці проєкції і відрізняється складністю; її не розглядаємо.

Захист: як розробники ускладнюють читання і зміну пам'яті

Усе, що виконується на комп'ютері гравця, гравець може прочитати й змінити. Тому захист будується в шарах, і найсильніший з них — не на клієнті.

Рівень 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).
  • Написання власного захисту: шифрування значень у грі-мішені та спроба його зламати.
Copyright © 2026