В этой статье я поп­робую показать, как соз­дать собс­твен­ный чит, который будет про­тивос­тоять исполь­зуемым в играх анти­чит‑сис­темам. Для это­го нам понадо­бит­ся поуп­ражнять­ся в ревер­се и поз­накомить­ся с устрой­ством игр, написан­ных на Unity.
 

Античит

Итак, анти­чит — это некая прог­рамма, которая меша­ет игро­кам в онлай­новые игры получать нечес­тное пре­иму­щес­тво за счет исполь­зования сто­рон­него ПО. Не буду пытать­ся объ­яснить это на абс­трак­тном при­мере, луч­ше давай сра­зу перей­дем к прак­тике. По дороге все пой­мешь!

 

Quick Universal Anti-Cheat Kit

Так как нет (или я прос­то не нашел) анти­читов уров­ня ядра с откры­тым исходным кодом, то выбирать будем из опен­сор­сных анти­читов, в которых при­сутс­тву­ет толь­ко анти­чит поль­зователь­ско­го уров­ня. Мой выбор пал на анти­чит Quick Universal Anti-Cheat Kit (далее Quack).

Сра­зу пре­дуп­реждаю, что Quack — это лишь прос­тень­кая опен­сор­сная демонс­тра­ция кон­цепции. Обой­ти ее про­ще, чем сис­темы, которые исполь­зуют­ся в популяр­ных играх. Все ком­мерчес­кие анти­читы работа­ют как на поль­зователь­ском уров­не, так и  на уров­не ядра. При­чем самые важ­ные защит­ные фун­кции обыч­но реали­зова­ны имен­но в виде драй­вера.

Од­нако для обу­чения Quack сго­дит­ся как нель­зя луч­ше, и изло­жен­ное даль­ше дол­жно стать фун­дамен­том для будущих изыс­каний.

warning

Статья име­ет озна­коми­тель­ный харак­тер и пред­назна­чена для оцен­ки защищен­ности кода игр. Автор и редак­ция не несут ответс­твен­ности за любой вред, при­чинен­ный с при­мене­нием изло­жен­ной информа­ции. Обратная раз­работ­ка анти­читов наруша­ет поль­зователь­ские сог­лашения игр, поэто­му, пов­торяя опи­сан­ное в этой статье на прак­тике, ты дей­ству­ешь на свой страх и риск!

 

Архитектура Quack

Да­вай пос­мотрим на общую архи­тек­туру анти­чита гла­зами ее авто­ра. Далее я объ­ясню, для чего нужен каж­дый выделен­ный на схе­ме ком­понент.

Архитектура Quack
Ар­хитек­тура Quack

Зе­леным цве­том я выделил кли­ент­скую часть (то, что будет работать на компь­юте­ре игро­ка):

  • Protected video game — игра, которую мы запус­каем на сво­ем компь­юте­ре и которую будет защищать анти­чит;
  • Anti-cheat .DLL — поль­зователь­ская часть анти­чита, DLL, которая сущес­тву­ет в кон­тек­сте соз­данно­го про­цес­са игры и которая отве­чает за защиту памяти про­цес­са игры;
  • Standalone usermode anti-cheat process — поль­зователь­ская часть анти­чита. Это глав­ный модуль анти­чита (оркес­тра­тор), который обща­ется с поль­зователь­ской и ядер­ной час­тями, а так­же дер­жит связь с сер­вером анти­чита;
  • Kernel mode anti-cheat — ядер­ная часть анти­чита. Отве­чает за защиту двух дру­гих модулей анти­чита. Не реали­зова­но.

Си­ним цве­том я выделил сер­верную часть (то, что будет работать вне компь­юте­ра игро­ка):

  • Master server — отве­чает за хра­нение учет­ной записи игро­ка и управле­ние ею;
  • Game server — отве­чает за отсле­жива­ние сос­тояния эле­мен­тов в игре, а так­же мес­тополо­жения игро­ков и вра­гов на кар­те;
  • Anti-cheat database — база дан­ных игро­ков.
 

Настраиваем Quack

Ес­ли пос­ле раз­верты­вания Quack (как это сде­лать, смот­ри в руководс­тве на GitHub и на YouTube) у тебя ничего работа­ет, то советую пов­ниматель­нее изу­чить сле­дующие фай­лы и нас­тро­ить сетевые адре­са и пор­ты в соот­ветс­твии с реаль­ными усло­виями.

GameDevCAServer\Program.cs
var settings = MongoClientSettings.FromConnectionString(env["DB_URI"]);
settings.ServerApi = new ServerApi(ServerApiVersion.V1);
var mongoClient = new MongoClient(settings);
database = mongoClient.GetDatabase(env["DB_NAME"]);
Quack-server\src\main.js
class Config {
static DB_URI = process.env.DB_URI
static DB_NAME = process.env.DB_NAME
static PORT = process.env.PORT
static CLIENT = new MongoClient(this.DB_URI)
static DEV_MODE = true
static VERSION = process.env.npm_package_version
}
Quack-internal\constants.hpp
namespace constants {
const LPCWSTR W_DLL_NAME { L"Quack-internal" };
const LPCSTR DLL_NAME{ "Quack-internal" };
const std::string VERSION { "0.6.5" };
constexpr unsigned IPC_PORT = 5175; // Local machine communications port
constexpr unsigned NET_PORT = 7982; // Foreign network communications port
static constexpr bool DBG = false;
}
Quack-client\constants.hpp
namespace constants {
const std::string VERSION{ "0.4.2" };
const std::string NAME{ "Quack" };
constexpr unsigned IPC_PORT = 5175; // Local machine communications port
constexpr unsigned NET_PORT = 7982; // Foreign network communications port
static constexpr bool DBG = false;
}
Quack-client\flashpoint.cpp
http::Client cli{ "localhost", constants::NET_PORT };
 

Проверяем работоспособность Quack

Для начала нам нуж­но убе­дить­ся в том, что все работа­ет. Для это­го так же, как и автор анти­чита, будем исполь­зовать его игру Inertia, инжектор Destroject и внут­ренний чит Inertia-cheat. Под­клю­чим­ся к сер­веру по адре­су lh (localhost) с ником xakep0.

Подключение к игровому серверу
Под­клю­чение к игро­вому сер­веру

В кон­соли игро­вого сер­вера видим, что про­изош­ло под­клю­чение.

Лог игрового сервера
Лог игро­вого сер­вера

Так­же в базе дан­ных анти­чита видим, что появи­лась запись.

База данных античита
Ба­за дан­ных анти­чита

На­чина­ем тест. Для это­го в коман­дной стро­ке выпол­ним Destroject.exe Inertia (не забыв рядом положить Inertia-cheat.dll). Видим, что инжект чита успешно выпол­нен.

Внедренный чит
Внед­ренный чит

Ак­тивиру­ем чит нажати­ем кла­виши E.

Активированный чит
Ак­тивиро­ван­ный чит

Ви­дим, что чит акти­виро­ван, но сра­зу же про­исхо­дит бан.

Сообщение о бане в игре
Со­обще­ние о бане в игре

Пос­мотрев в БД анти­чита, мы можем узнать, из‑за чего нас забани­ли.

Сообщение о бане в БД
Со­обще­ние о бане в БД

Про­буем сме­нить ник­нейм, но нас все рав­но не пус­кают.

Попытка смены ника
По­пыт­ка сме­ны ника

Ес­ли попытать­ся открыть Cheat Engine, он через пару секунд зак­роет­ся, но бан не при­летит.

 

Unity

Преж­де чем рас­чехлять IDA Pro и прис­тупать к ревер­су анти­чита, сде­лаем неболь­шое отступ­ление. Если ты уже заг­лянул в код чита, то мог заметить сле­дующий учас­ток:

Inertia-cheat\player.cpp
std::optional<Player> GetPlayer() {
auto start_point = reinterpret_cast<std::uintptr_t>(GetModuleHandleA("UnityPlayer.dll"));
start_point += offsets::player.start_point;
// Get a pointer to health
const auto health_ptr = TraverseChain(start_point, offsets::player.ptr_chain).value_or(0u);
if (!health_ptr)
return std::nullopt;
const auto ammo_ptr = health_ptr - 0x4;
const Player player{
.health = reinterpret_cast<std::int32_t*>(health_ptr),
.ammo = reinterpret_cast<std::int32_t*>(ammo_ptr)
};
return player;
}

Здесь сто­ит обра­тить вни­мание на строч­ку "UnityPlayer.dll". Дело в том, что игра написа­на на движ­ке Unity.

Inertia-cheat\data.cpp
namespace offsets {
PointerChain player = {
.start_point = 0x13A1340,
.ptr_chain { 0xC2C, 0xDC8, 0xEA8, 0x18, 0x38 }
};
}

Так­же мы име­ем цепоч­ку ука­зате­лей для UnityPlayer.dll. Откро­ем ее в IDA Pro по адре­су base+0x13A1340. Видим, что здесь раз­мещено зна­чение перемен­ной из сте­ка и даль­ше поиск клас­са игро­ка идет в сте­ке.

UnityPlayer.dll без загруженных отладочных символов
UnityPlayer.dll без заг­ружен­ных отла­доч­ных сим­волов

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии