Читы и античиты

от Миши

Вступление

Еще в 2009-2015 годах, до работы над Mirror & mmorpg, я пытался узнать о ММО, занимаясь их реверс-инжинирингом и продавая ботов, чтобы зарабатывать на жизнь. Я поделюсь некоторыми уроками, извлеченными на основе вопросов в нашем Discord. Эта статья является неполной и предназначена для краткого ознакомления с наиболее часто задаваемыми темами в нашем discord. Если вы хотите узнать больше, дайте мне знать.

Сначала мы разберемся с Server Authority и Client Authority, что является первым крупным вектором атаки. Мы также поговорим об атаках, не зависящих от Authority (авторитета), и о том, как защититься от них.

Как правило, никогда не доверяйте клиенту!

Server Authority vs Client Authority

Сначала о главном. Зеркало по умолчанию является серверо-авторитарным. Другими словами, сервер принимает все решения. Мошенники обычно модифицируют клиент, чтобы использовать игры, в которых клиенту доверяют принятие некоторых решений (aka client authority).

Другими словами, пока вы используете полные полномочия сервера, с вами все в порядке, пока кто-то физически не взломает ваш серверный компьютер. Если вы используете авторитарность клиента для некоторых частей своей игры (например, для перемещения), то это те части, о которых вам нужно беспокоиться.

Просто для ясности, вот разница между авторитарным сервером и авторитарным клиентом, объясненная на примере использования зелья здоровья

Server AuthorityClient Authority

Клиент: могу я использовать это зелье?

Клиент: Я использую это зелье. Мое новое здоровье - 100!

Сервер: проверка...

Сервер: ¯\_(ツ)_/¯

Сервер: ваше новое здоровье равно 100!

На практике вам необходимо проверять любой ввод данных клиентом в [Command]. Вот пример видео о том, как кто-то использовал игру, созданную с помощью Mirror, где разработчики не проверяли вводимые клиентом данные. В игре, вероятно, есть функция CmdSellItem по типу этой:

[Command]
void CmdSellItem(int slot, int amount)
{
    // получаем предмет игрока в слоте инвентаря
    Item item = player.inventory[slot];
    
    // продаём npc
    item.amount -= amount;
    player.gold += item.price * amount;
}

Обратите внимание, как мы слепо доверяем клиенту отправку правильной суммы. Никакой проверки вообще нет. Если у игрока есть только один предмет, но хакер отправляет 'amount = 100', игра бы всё равно ему поверила и продала 100 предметов. Вместо этого нам нужно проверять любой ввод:

[Command]
void CmdSellItem(int slot, int amount)
{
    // допустимый слот?
    if (0 <= slot && slot <= player.inventory.Count)
    {
        // получаем предмет игрока в слоте инвентаря
        Item item = player.inventory[slot];
        
        // действительная сумма?
        if (0 < amount && amount <= item.amount)
        {
            // продаём npc
            item.amount -= amount;
            player.gold += item.price * amount;
        }
    }
}

Авторитарный клиент - корень всего зла

Доверие клиенту в перемещении

Если зеркало по умолчанию полностью использует авторитарность сервера, а Client Authority допускают мошенничество, то зачем кому-либо использовать Client Authority?

Потому что это легко. Многие игры используют авторитарность клиента для его перемещения. При использовании авторитарного сервера в этом деле, клиент должен был бы запрашивать сервер перед каждым перемещением. Это приводит к большой задержке между нажатием клавиши и просмотром фактического движения. Это совсем не весело.

В режиме client authority, игрок перемещается, как только нажимается клавиша. Вместо того чтобы попросить сервер переместить его, он сообщает серверу, что он переместился. Это здорово, но также позволяет читерам сообщать серверу все, что им заблагорассудится, например: "Я переехал сюда в два раза быстрее".

Сетевое перемещение это трудно. Быстрое перемещение, которое также является серверо-авторитарным на самом деле возможно (rubberbanding / прогнозирование / и т.д.), Но многие предпочитают не делать этого сначала, чтобы сэкономить месяцы времени на разработку.

Доверие клиенту в вводимых данных

В некоторых жанрах, таких как шутеры от первого лица, доверять клиенту некоторые части игры всё таки придется. В данном случае это прицеливание. Всякий раз, когда мы доверяем клиенту, этим доверием могут воспользоваться хакеры. В играх жанра FPS Aim-бот может притворяться, что быстрее наводит курсор на другого игрока. И поскольку сервер доверяет клиенту перемещать курсор, это открывает возможности для обмана, не имеющего простого решения.

Подводя итог, можно сказать, что мы можем захотеть или нуждаться в том, чтобы доверить клиенту некоторые части нашей игры. Это те части, которые нам нужно защитить от читеров.

"читы" в Server Authority

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

Боты - это инструменты, которые анализируют состояние игры и генерируют инпут для автоматической добычи золота или убийства монстров, пока игрока нет рядом. Это может доходить до крайностей, когда некоторые игроки используют сотни ботов для фарма, а затем продают внутриигровое золото за реальные деньги.

Имейте в виду, что серверо авторитарные читы - это проблема роскоши. Если ваша MMO становится настолько успешной, что люди разрабатывают ботов, значит, вы в значительной степени добились успеха.

Защита от серверо-авторитарных "читов" выходит за рамки первоначальной разработки. У нас будет достаточно времени, чтобы разобраться с ними после релиза. Кто-то, кто запускает бота в своем подвале, не представляет серьезной угрозы до тех пор, пока ваша игра не выйдет из-под контроля.

И просто для ясности, обнаружить ботов можно как на стороне клиента, так и на стороне сервера. Но беспокойтесь об этом через 5 лет, когда возникнет проблема, а не сегодня.

Как создаются читы

Давайте кратко рассмотрим, как на самом деле создаются читы.

Ваша игра хранит в своей памяти огромное количество важной информации. Например: местоположение местного игрока, местоположение других игроков, местоположение монстров, здоровье, имена и т.д.

Поиск ячеек памяти

Большинству читов необходимо считывать часть этой информации из памяти вашей игры. Такие инструменты, как Cheat Engine, позволяют вам искать в памяти игры определенные значения. Например, если у вас 100 единиц здоровья, то вы выполняете поиск по "100" и можете найти 10 000 мест в памяти со значением "100". Но если вы примете зелье и увеличите свое здоровье до 200, то, скорее всего, сможете сузить его до нескольких значений, которые раньше были "100", а теперь изменены на "200". Если вы сделаете это пару раз, то обычно сможете сузить поиск до одного места в памяти. Например, состояние здоровья локального игрока может храниться по адресу памяти 0xAABBCCDD.

Но есть одна проблема: в следующий раз, когда мы запустим игру, игра снова настроит мир, и здоровье вашего игрока, скорее всего, больше не будет находиться по тому же адресу памяти. Такие инструменты, как Cheat Engine, позволяют вам "найти то, что обращается..." к этой ячейке памяти, установив точки останова. Снова используйте зелье, сработает точка останова, и теперь вы знаете, какая часть вашей игры обращается к этой ячейке памяти.

Вместо простого Health, у вас может быть Player->Health (это упрощение, на практике вы переходите от 0xAABBCCDD к указателю со смещением, подобным [0x00FF00FF+0x8] где 0x00FF00FF находится местоположение вашего объекта player в памяти, и 0x8 является смещением для Player->Health. Вполне вероятно, что Player->Mana был бы на +0x12, или в следующем месте в памяти. Этот процесс можно повторять до тех пор, пока у вас не будет Game->Player->Health где Game наконец, это адрес относительно точки входа в программу.

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

Этот процесс можно повторить для инвентаря, навыков, монстров, позиций и т.д. Чем больше информации мы сможем найти, тем легче будет писать читы.

Если ваша игра использует client authority, тогда мы действительно сможем изменить здоровье игрока в памяти! Если мы используем Server Authority, то мы все еще можем изменить его в памяти, но изменение будет видно только на этом клиенте. Сервер не доверяет клиенту здоровье, и в следующий раз, когда он синхронизирует новый уровень здоровья с клиентом, значение в памяти будет перезаписано снова.

Это то, как работает [SyncVar] в Mirror! Вы можете изменить их в Cheat Engine, но никого это не волнует, потому что они являются авторитетными для сервера.

Затрудняем поиск ячеек памяти

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

class Player
{
    int Level; // at +0 in memory
    int Health; // at +4 in memory
    int Mana; // at +8 in memory
}

И игра меняет их на:

class Player
{
    int Class; // at +0 in memory
    int Level; // NOW at +4 in memory
    int Health; // NOW at +8 in memory
    int Mana; // NOW at +12 in memory
}

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

Время от времени возиться с расположением памяти - хороший способ усложнить реверс-инжиниринг. Защита от реверс инжиниринга является функцией return vs. effort. Никто не будет тратить 10 часов в день на реверс-инжиниринг, если взлом в итоге принесет всего 10 долларов в месяц. Чем усерднее мы будем это делать, тем меньше это будет стоить.

Проецирование значений памяти

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

struct AntiCheatInt
{
    int projected;
    public int Value
    {
        get => projected + 1;
        set => projected = value - 1;
    }
}

Это упрощенный пример, но идея заключается в том, чтобы не сохранять наше "100-процентное" здоровье непосредственно в памяти. Вместо этого мы сохраняем значение, измененное на единицу или на более сложные проекции. Это уже делает весь процесс первоначального поиска Cheat Engine действительно удручающим, при этом практически не добавляя риска. На самом деле ничего не может пойти не так, если вы сделаете это в Unity.

Проецирование значений памяти - это простой способ сделать разработку читов более раздражающей. Обратите внимание, что это незначительно влияет на производительность, и обратите внимание, что это полезно только в том случае, если вы используете IL2CPP в Unity.

При защите от читов существует прекрасный баланс между тем, чтобы усложнить жизнь читеру и в то же время не раздражать честных игроков. Некоторые методы, такие как упаковка UPX (см. ниже), с высокой вероятностью вызовут раздражение у всех. Другие методы, такие как проецирование памяти, имеют низкую вероятность вызвать у кого-либо раздражение.

Затрудняем доступ к памяти

Существуют различные методы, позволяющие сделать реверс-инжиниринг более болезненным с самого начала. Например:

  • Поддельные точки входа, которые динамически изменяются, например, с помощью exe-упаковки, такой как UPX packer. Их не так уж сложно распаковать, но это добавляет сложностей.

    • Обратите внимание, что исполняемые файлы, упакованные в UPX, часто помечаются как вирусы.

  • Обнаружение отладчиков, таких как Cheat Engine/MHS, с помощью IsDebuggerPresent. Обратите внимание, что это довольно легко обойти, потому что все знают о IsDebugger. Более продвинутые методы могут включать в себя такие хитрости, как измерение времени между инструкциями. Например, если мы измеряем простое умножение целых чисел с помощью StopWatch во время выполнения и в итоге это занимает несколько миллисекунд, то кто-то, скорее всего, выполняет этот код с помощью отладчика.

  • Виртуализация с помощью таких инструментов как Themida или Enigma Packer это святой грааль, когда дело доходит до защиты от реверс инжиниринга. Если найти ячейки памяти в обычных процессах сложно, то найти их в процессах внутри виртуальных машин на порядки сложнее. Раньше, когда мы занимались реверс-инжинирингом игр, мы бы никогда не стали касаться виртуализированных процессов просто потому, что усилия по сравнению с наградой никогда не стоила бы того. Никто не собирается тратить полгода на анализ инструкций вашей виртуальной машины, если только ваша игра не такая масштабная, как World of Warcraft.

    • Обратите внимание, что виртуализированные исполняемые файлы часто помечаются как вирусы. Вам понадобится пользовательский движок виртуализации, который не помечен как вирус.

Обратите внимание, что многие из этих методов могут быть рискованными в Unity, которая уже вводит несколько уровней сложности, переходя от C#->IL(->IL2CPP->Assembly). Вероятность того, что перепутанные точки входа что-то где-то сломают, высока. Как эмпирическое правило, в любом случае используйте IL2CPP, поскольку это меняет игру с IL на Assembly, что уже намного сложнее для реинжиниринга. Если мошенничество становится серьезной проблемой, подумайте о виртуализации.

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

Ollydbg/IDA/Code Caves

Допустим, в вашей игре есть такая функция, как:

void SetHealth(int health)
{
    this.health = health;
}

Который мог бы создать (упрощенный) ассемблерный код, подобный:

...
mov edi, eax // edi это this.health, eax это новое значение
...

Хакеры могут использовать расширенные средства отладки, чтобы изменить сборку вашей игры таким образом, чтобы:

...
mov edi, 100 // всегда устанавливает здоровье на 100
...

Вместо поиска и изменения значений в памяти с помощью Cheat Engine, можно напрямую модифицировать собственный ассемблерный код игры.

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

...
JMP 4 // переходим к нашему пользовательскому коду
...
mov edi, eax // Делаем что нибудь своё
... custom code ... // делаем все, что захотим
JMP 2 // везвращаемся к исходной функции
...

В C#, это было бы эквивалентно вводу пользователем своего собственного кода в нашу функцию работоспособности, например:

void SetHealth(int health)
{
    CodeCave(health);
}

void CodeCave(int health)
{
    this.health = health;
    // творите здесь магию
    // например, если health==0, то вызовите код
    // который нажимает на кнопку возрождения, чтобы возродиться
    // автоматически.
}

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

Wall Hack / ESP

В шутерах от первого лица Wall hack'и являются одним из самых распространенных читов. Люди могут изменить ваш исполняемый файл, чтобы показать игроков за стенами. Это достаточно легко сделать и довольно распространено.

Чтобы защититься от этого:

  • Максимально усложните реверс-инжиниринг (см. главу выше).

  • Используйте Interest Management чтобы не отображать удаленных игроков. Вы могли бы реализовать управление интересами на основе raycast, при котором игроки отправляются клиентам только в том случае, если их действительно видят.

    • Обратите внимание, что вам нужен какой-то допуск, чтобы отправлять их достаточно рано, например, отправлять за 1 секунду до того, как они будут замечены. Это не идеально, но это лучше, чем позволять игрокам постоянно видеть всех других игроков. Interest Management имеет огромное значение для этого.

  • Обнаруживайте Wall hack'и во время выполнения и баньте читеров, использующих их.

Это сложная проблема, даже таким популярным играм, как Counter-Strike, действительно трудно с этим справиться. Это постоянная битва.

Speed Hack

Если вы решили использовать клиент-авторитарное перемещение, потому что это проще, то, скорее всего, рано или поздно вы столкнетесь со спидхаками в своей игре. Спидхаки могут быть реализованы различными способами, начиная от простого изменения Player.Speed в памяти, до возни с тактовой частотой компьютера, с которой довольно сложно справиться в Unity.

Чтобы защититься от этого:

  • Проверьте скорость перемещения на сервере.Допускайте некоторое странное смещение по причинам неполадок в сети. Многие игры допускают смещение в 10-15%, но все, что выше этого, скорее всего, является speed hack'ом.

Боты

Как упоминалось ранее, боты особенно опасны, поскольку они не требуют никаких реальных читов или полномочий клиента. Кроме того, они могут испортить экономику вашей игры и сделать ее просто неинтересной для игрока, если все вокруг вас - боты.

Чтобы защититься от этого:

  • Затрудняйте поиск ячеек памяти. Смотрите главы выше.

  • Время от времени корректируйте расположение своей памяти. Иногда добавляйте ненужные значения между Player.Health и Player.Mana.

  • Время от времени корректируйте свой сетевой протокол. Самым продвинутым ботам даже не нужно читать вашу память. Они напрямую работают с функциями отправки / восстановления вашей игры. Время от времени изменяйте коды операций и макет вашего NetworkMessage, и вы сделаете реверс-инжиниринг действительно болезненным.

  • Обнаруживайте известные Bot.exe обрабатывая по контрольной сумме, имени и т.д.

    • Обратите внимание, что это слишком часто помечает вашу игру как вирусную. Игры не должны искать запущенные процессы.

  • Обнаруживайте паттерны ботов на сервере. Вот как я бы это сделал, если бы боты стали серьезной проблемой.

    • В простейшей форме, если кто-то играет 24/7 в течение недели подряд, то это, вероятно, бот или, в редких случаях, какой-нибудь парень в интернет-кафе.

    • Если игрок всегда использует один и тот же путь или постоянно проходит уровни в одном и том же месте, вероятно, это бот.

  • Добавьте кнопку Report в вашу игру. Изучите сообщения об игроках. Попробуйте поговорить с ними и посмотреть, ответят ли они и т.д.

  • Спавните монстров-ханипотов в местах повышенной активности. Если в определенной области за определенный период времени было убито много монстров, создайте в этой области монстра другого вида, чрезвычайно сильного монстра. Обычные игроки заметили бы это и на некоторое время переехали бы в другое место. Боты попадут в него и умрут.

Опять же, это упрощенные ответы на сложную проблему. Если ваша игра станет успешной, это будет постоянная битва. И это прекрасно, когда вы знаете, что вы вообще участвуете в битве.

Тихое, медленное обнаружение

Одна из самых больших ошибок, которые допускают игры, заключается в том, что они сообщают пользователю, когда был обнаружен чит или инструмент реверс инжиниринга. Все, что это делает, - сообщает реверс-инженеру, где искать проблему в коде, чтобы отключить проверки.

Если мы проделаем всю работу по обнаружению читов и отладчиков, нам следует хранить молчание и использовать полученную информацию в своих интересах. Вместо того чтобы громко объявлять о попытке обмана, тихо отправьте некоторую информацию на сервер. Отметьте игрока в базе данных.

Не стоит сразу же никого банить или кикать. Разумнее подождать произвольное количество времени.

  • Пользователи могут попробовать несколько читов с разными версиями в течение месяца.

  • Реверс-инженеры могут использовать разные инструменты и модифицировать игру по-разному.

Если мы будем банить людей только раз в месяц, то совершенно не очевидно, что стало причиной запрета. Это потребует огромного времени на проверку того, какие читы обнаруживаются, а какие нет.

Бесшумное обнаружение - это самый мощный инструмент, который у нас есть, чтобы выиграть борьбу с мошенниками. Используйте время и информацию с пользой для себя.

Бесплатные vs платные игры

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

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

Итог

Подводя итог, можно сказать, что мошенничество - сложная тема, и окончательного решения никогда не будет. Имхо, сделайте все, что вы можете, авторитетным для сервера. Что касается движения, то, по крайней мере, стремитесь сделать его авторитетным сервером в какой-то момент, например, после выпуска вашей игры, когда вы начнете видеть первые быстрые взломы или когда у вас действительно появится некоторая передышка.

Как только ваша игра станет успешной, вероятно, кто-то вступит в битву. Вы многое можете сделать, чтобы усложнить задачу.

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

Этой теме можно было бы посвятить целую книгу, но я надеюсь, что вы усвоили из нее несколько основ.

Last updated