Гайд Nemesis Anti-Cheat, часть 1: Язык описания данных

Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
Notion:
Пожалуйста, авторизуйтесь для просмотра ссылки.
/
Пожалуйста, авторизуйтесь для просмотра ссылки.


Привет, дорогой читатель! Буду честен, это очередная не интересная статья о очередных велосипедах. Все мои статья пишутся чисто для развлечения, по приколу так сказать…

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

Сегодня мы начнём с идеи передачи данных между анти-читом, и сервером. Почему не протобафф?
  1. Протобафф диктует стандарт, который каждый гейм хакер знает
  2. Слишком большие размеры
  3. Ну, а еще мне просто интересно сделать что-то такое, вопросы?
Проблемы нет, а решение найдётся
Да, ты всё правильно понял. Проблемы просто нет, я её выдумал в своей голове, а почему нет? В целом проблема достаточно простая и фундаментальная, и вопрос звучит так:
Почему бы нам не передавать тупо поток байт, как это делает флатбуфферс? Но при этом автоматически понимать что это за пакет, и при этом иметь возможность легко обфусцировать, и модифицировать наш сгенерированный код?
И вот из этого концепта сделана первая альфа версия языка описания данных для анти-чита.
Получается, вкратце нам нужен язык, который:
  1. Будет простой для описания данных
  2. Не будет давать реверс-инженеру лишней информации
  3. Удобный, и при этом достаточно лёгкий!
  4. Ну и самое главное, его можно легко дополнить в случае чего, либо в целом переписать кодген добавив свою обфускацию, или виртуальную машину. А почему нет?
А что делать то? Стандарт
Чтобы что-то передавать между разными ЯП нужно чтобы это что-то было стандартизировано, очевидно же! Вот для таких целей у языка есть небольшая спецификация ( я её по приколу накидал, если честно ).

Salary Dependent Bytes
Ладно, начнём. Я вот изучал исходники телеграма, и там у них тоже есть свой протокол, и я решил оттуда украть автоматическое определение пакета. Как это работает?
  1. У нас есть название пакета, оно уникальное типа, если вы вдруг сами не поняли
  2. Мы берём CRC32 хэш от этого названия пакета, и запоминаем
  3. И когда отправляем пакет добавляем как заголовок первые 4 байта этот хэш
  4. А уже там где принимают уже классифицируем нужный класс по этому хэшу
Удобно же! Жаль это уже придумали до меня, и активно используют. Дальше нам нужно определить как байты будут лежать, предлагаю самый простой вариант, хоть и не самый быстрый ( вспоминаем FlatBuffers, где поступили чуть по другому, и из-за этого получили максимальную скорость ) это тупо взять, и без выравнивания полей отправлять. Такой подход позволяет нам быстро, и удобно передавать данные

ПлюсыМинусы
ЛегкоСкорость
Не создаём дополнительных трудностей для разной архитектурыДополнительные аллокации

То есть при условном банальном пакете
Код:
packet TextMessage {

    content: string = "Hello world!"

    author: string = "sapdragon"

    is_encrypted: bool = false

}
Бинарное представление выглядело бы так:
Код:
[Заголовок пакета]
E7 A5 5C 12     | CRC32 хеш имени пакета "TextMessage"

[Поле: content (string)]
0C 00 00 00     | Длина строки (12 байт)
48 65 6C 6C     |
6F 20 77 6F     | ASCII "Hello world!"
72 6C 64 21     |

[Поле: author (string)]
0A 00 00 00     | Длина строки (10 байт)
73 61 70 64     |
72 61 67 6F     | ASCII "sapdragon"
6E              |

[Поле: is_encrypted (bool)]
00              | false
И ничего лишнего. Возможно стоило разве, что добавить конец пакета для проверка его целостности, но это по желанию. В моём случае я просто ориентируюсь на его размер, и попытку парсинга.

Пакеты, с чем?
Итак, дорогой читатель, ты уже знаешь, как выглядит пакет в бинарном виде. Теперь давай разберемся, как же мы будем эти пакеты описывать. Ведь нам нужно что-то простое, но в то же время достаточно гибкое, чтобы можно было передать всё что угодно…

Начнем с того, что наш язык будет использовать ключевое слово packet. Почему? Да потому что "структура" слишком обычно, а "класс" слишком объектно-ориентированно.
Код:
packet PlayerInfo {
    name: string
    health: u32
    position: [3]f32
    inventory: []Item
}
Видишь? Просто, лаконично, и сразу понятно, что это пакет. А теперь давай разберем каждую строчку этого шедевра:
  1. name: string - строковое поле. Ничего сложного, правда?
  2. health: u32 - беззнаковое 32-битное целое число. Потому что отрицательного здоровья не бывает (ну, почти).
  3. position: [3]f32 - массив из трех 32-битных чисел с плавающей точкой. X, Y, Z - все как полагается, но можно сделать там отдельно пакет Vector, и составлять… Но не сейчас!
  4. inventory: []Item - динамический массив элементов типа Item. Потому что мы не знаем, сколько хлама таскает с собой игрок.
В целом язык поддерживает очень много разных типов данных, мне даже скучно их читать… Но, если у тебя всё нормально с фокус таймом, ты там не сидишь в тиктоке, каждый день делать отжимания, прес качать, 10 книг в день читать, то вот:
Вот список типов, которые мы поддерживаем:


  • u8, u16, u32, u64 - беззнаковые целые
  • i8, i16, i32, i64 - знаковые целые
  • f32, f64 - числа с плавающей точкой
  • bool - логическое значение (true/false)
  • string - строка (потому что без строк никуда)
  • []T - динамический массив элементов типа T
  • [N]T - статический массив из N элементов типа T
А еще мы поддерживаем наследование. Почему? Потому что иногда нужно создать иерархию пакетов, удобно ведь же, у нас это больше не наследование, а встраивание, воот…
Код:
packet Entity {
    id: u64
    position: [3]f32
}


packet Player : Entity {
    name: string
    health: u32
}
Player наследует все поля от Entity и добавляет свои, вроде должно быть понятно.
Пора переходить на более тяжелые вещи, эффект от которых перекроет наркотики. Нет, я говорю о дженериках. Они константные, и определяются во время процесинга, т.е в сгенерированном коде их не будет! Снова пример:
Код:
packet Container<T> {
    capacity: u32
    items: [T]
}


packet Inventory {
    backpack: Container<Item>
    quickslots: Container<Weapon>

}

Еще мы поддерживаем много всего, давайте я быстро расскажу, и закончим с этой главой. Первое это конечно же значения по умолчанию, хотя я изначально был против их добавления, но что-то шизануло меня, и я поменял точку зрения.
Код:
packet DefaultsAreCool {
    name: string = "Unknown"
    age: u8 = 18
    is_cool: bool = true
}
Теперь, можно просто пустой конструктор вызвать, и всё будет заполнено, удивительно, да? Я ироинизирую, если что.

Еще были позаимствованны из одного ЯП концепция енамов, я думаю вы и так всё знаете, поэтому не буду рассказывать, а лишь оставлю пример.
Код:
enum MessageType : u8 {
    TEXT,
    IMAGE,
    VIDEO,
    AUDIO
}
Да, тут нужно указывать свой тип, чтобы мы точно знали размер, логично же. И остальное только примерами:
Код:
packet Address {
    street: string
    city: string
    zip: u32
}

// constuctor defaut values for custom types
packet Person {
    name: string
    age: u8
    address: Address = { street: "Main St", city: "New York", zip: 10001 } //
}

// lol hello!
/* many
hello world */

import "common_types.ns"

// custom overload types...
type UserID = u64
На этом всё, я рассказал все возможности языка, прикольно же, да? Да похуй, даже, если не очень.

Как это обрабатывать, то?
Итак, у нас есть язык. Красивый, элегантный, и разработчик тоже крутой ( я если кто не понял ) Нам теперь нужен парсер, который сгенерирует нам наш код для вашего п2с за 300 евро в месяц. Всего есть несколько этапов:
  1. Парсим файл на токены
  2. Из токенов строим дерево
  3. Проверяем это дерево
  4. Генерируем код
  5. Пишем “готово!!”
Первый этап: лексический анализ, или "Разбей все на кусочки”
Представь, что ты разбираешь конструктор LEGO. Сначала ты высыпаешь все детали на стол и начинаешь их сортировать. Красные к красным, синие к синим, а странные детальки, назначение которых ты не понимаешь, в отдельную кучку.
Вот что делает лексический анализатор (или, как его ласково называют, лексер):
Код:
def lexical_analysis(code):
    tokens = []
    for char in code:
        if char.isalpha():
            tokens.append(("IDENTIFIER", char))
        elif char.isdigit():
            tokens.append(("NUMBER", char))
        elif char in "{}:;":
            tokens.append(("SYMBOL", char))
        # И так далее...
    return tokens
На выходе мы получаем список токенов, и уже понимаем где что… Хз, кто это придумал, но мне кажется он достаточно умный, и это действительно удобный способ.

Второй этап: Синтаксический анализ, или "Собери их обратно, но красиво”
Теперь, когда у нас есть кучка токенов, пора собрать из них что-то осмысленное, может дерево? Да-да, ты не ослышался. Дерево. Абстрактное синтаксическое дерево (AST), если быть точным.
Код:
def parse_packet(tokens):
    if tokens[0] != ("KEYWORD", "packet"):
        raise ParseError("Expected 'packet'")

    packet_name = tokens[1][1]
    fields = []

    # Пропускаем открывающую скобку
    i = 3
    while tokens[i] != ("SYMBOL", "}"):
        field_name = tokens[i][1]
        field_type = tokens[i+2][1]
        fields.append((field_name, field_type))
        i += 4  # Пропускаем имя, двоеточие, тип и точку с запятой

    return Packet(packet_name, fields)
( это не настоящий код, если что )

По сути он делает то же самое, что ты делаешь, собирая конструктор по инструкции. "Так, мне нужна красная деталька 2x4, потом синяя 1x2...". Только вместо деталек у нас поля и типы.

Третий этап: Семантический анализ, или "Проверка на здравый смысл”
Отлично, у нас есть красивое дерево! Но оно может оказаться немного... бессмысленным
Семантический анализ по факту просто смотрит на наше дерево и задает вопросы:
  1. "Эй, ты пытаешься использовать тип Unicorn, но я такого не знаю. Ты его точно определил?"
  2. "Массив отрицательного размера? Серьезно?"
Код:
def semantic_analysis(ast):
    for packet in ast.packets:
        for field in packet.fields:
            if field.type not in KNOWN_TYPES:
                raise SemanticError(f"Unknown type: {field.type}")
                # etc
Четвёртый этап: Кодогенерация, или "Блять, ты же собирал самолёт, почему у тебя вышла машина?”
Итак, у нас есть красивое абстрактное синтаксическое дерево (AST). Оно, конечно, прекрасно, но, увы, совершенно бесполезно… Нам же нужен код, а не деревья

Мы для этого будем используем Jinja2. Нет, это не название нового аниме гаремника. Это шаблонизатор с возможностями пайтона, вот прикол так выглядит:
Код:
// packet_template.cpp
class {{ packet.name }} {
public:
    {% for field in packet.fields %}
    {{ field.type }} get_{{ field.name }}() const { return m_{{ field.name }}; }
    void set_{{ field.name }}({{ field.type }} value) { m_{{ field.name }} = value; }
    {% endfor %}

private:
    {% for field in packet.fields %}
    {{ field.type }} m_{{ field.name }};
    {% endfor %}
};
Увы, я его до сих пор не закончил в проекте, кто хочет можете заняться, разрешаю.

Заключительный аккорд
"Но подождите," — скажешь ты, — "разве этого достаточно для защиты от читеров?" О, мой наивный друг, конечно нет!
Чтобы на этом прочном фундаменте, действительно сделать защиту нужно чуть добавить, например:
  • Шифрование: Давайте завернем наши пакеты в несколько слоев всяких AES.
  • MBA (Mixed Boolean-Arithmetic): Превратим наши простые арифметические операции функции сериализации в булевый пиздец. Потому что 2+2 это слишком просто, давайте сделаем это через XOR, AND и правую руку соседа.
  • Виртуализация: А почему бы не запустить наш код в виртуальной машине внутри виртуальной машины внутри... ты понял идею, сотни хендлеров.
  • Фантазия: Да что угодно, возьми bin2bin Есенина, докинь в наш проект, добавь пару “трансформов”, и уже страшно…
Что же вы можете сделать? Что вас ждёт дальше? — бесконечные возможности! Как бы не ныли люди, что защита это скучно, она предоставляет как и всё програмированние бесконечные возможности, лишь идиоты будут давать себе рамки.

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

Репозиторий:
Пожалуйста, авторизуйтесь для просмотра ссылки.


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


нет ответов спустя 15 секунд после публикации, югейм умирает...
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2024
Сообщения
46
Реакции[?]
8
Поинты[?]
14K
Кому это вообще нужно1719960884594.pngебать ты хакер :sunglasses: :sunglasses: :sunglasses:
-реп тебе за такую хуйн
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
Кому это вообще нужноПосмотреть вложение 280422ебать ты хакер :sunglasses: :sunglasses: :sunglasses:
-реп тебе за такую хуйн
ты что конченный? какой еще майнкрафт анти-чит? Да, даже, если и был бы он, то как это сообщение относится к теме?
Не стоит комментировать, если это для тебя куча непонятных слов, умнее не станешь
 
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2024
Сообщения
46
Реакции[?]
8
Поинты[?]
14K
ты что конченный? какой еще майнкрафт анти-чит? Да, даже, если и был бы он, то как это сообщение относится к теме?
Не стоит комментировать, если это для тебя куча непонятных слов, умнее не станешь
ты че ахуел так с фанатами общаться? Да, оно так и есть, то это сообщение прямо относится к теме?
Не стоит комментировать, если это для тебя куча непонятных слов, умнее не станешь, ток фанбазу растеряешь и минус реп получишь
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
ты че ахуел так с фанатами общаться? Да, оно так и есть, то это сообщение прямо относится к теме?
Не стоит комментировать, если это для тебя куча непонятных слов, умнее не станешь, ток фанбазу растеряешь и минус реп получишь
у меня нет фанатов, а ты шизанутый чуток
 
unbound
Пользователь
Статус
Оффлайн
Регистрация
27 Окт 2019
Сообщения
273
Реакции[?]
91
Поинты[?]
61K
Ура, хоть какое-то разнообразие в этой реке из реселлеров и майнкрафтеров... СПАСИБО!
 
Забаненный
Статус
Оффлайн
Регистрация
24 Июн 2024
Сообщения
28
Реакции[?]
3
Поинты[?]
3K
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Привет, хорошая статья, спасибо
 
Сверху Снизу