-
Автор темы
- #1
Notion:
Привет, дорогой читатель! Буду честен, это очередная не интересная статья о очередных велосипедах. Все мои статья пишутся чисто для развлечения, по приколу так сказать…
Очень давно мне пришла идиотская идея создать простой фундамент для анти-чита, который бы не полагался на что либо, а давил со всех фронтов! И вот эта серия начинает этот путь.
Сегодня мы начнём с идеи передачи данных между анти-читом, и сервером. Почему не протобафф?
Да, ты всё правильно понял. Проблемы просто нет, я её выдумал в своей голове, а почему нет? В целом проблема достаточно простая и фундаментальная, и вопрос звучит так:
Получается, вкратце нам нужен язык, который:
Чтобы что-то передавать между разными ЯП нужно чтобы это что-то было стандартизировано, очевидно же! Вот для таких целей у языка есть небольшая спецификация ( я её по приколу накидал, если честно ).
Salary Dependent Bytes
Ладно, начнём. Я вот изучал исходники телеграма, и там у них тоже есть свой протокол, и я решил оттуда украть автоматическое определение пакета. Как это работает?
То есть при условном банальном пакете
Бинарное представление выглядело бы так:
И ничего лишнего. Возможно стоило разве, что добавить конец пакета для проверка его целостности, но это по желанию. В моём случае я просто ориентируюсь на его размер, и попытку парсинга.
Пакеты, с чем?
Итак, дорогой читатель, ты уже знаешь, как выглядит пакет в бинарном виде. Теперь давай разберемся, как же мы будем эти пакеты описывать. Ведь нам нужно что-то простое, но в то же время достаточно гибкое, чтобы можно было передать всё что угодно…
Начнем с того, что наш язык будет использовать ключевое слово packet. Почему? Да потому что "структура" слишком обычно, а "класс" слишком объектно-ориентированно.
Видишь? Просто, лаконично, и сразу понятно, что это пакет. А теперь давай разберем каждую строчку этого шедевра:
Player наследует все поля от Entity и добавляет свои, вроде должно быть понятно.
Пора переходить на более тяжелые вещи, эффект от которых перекроет наркотики. Нет, я говорю о дженериках. Они константные, и определяются во время процесинга, т.е в сгенерированном коде их не будет! Снова пример:
Еще мы поддерживаем много всего, давайте я быстро расскажу, и закончим с этой главой. Первое это конечно же значения по умолчанию, хотя я изначально был против их добавления, но что-то шизануло меня, и я поменял точку зрения.
Теперь, можно просто пустой конструктор вызвать, и всё будет заполнено, удивительно, да? Я ироинизирую, если что.
Еще были позаимствованны из одного ЯП концепция енамов, я думаю вы и так всё знаете, поэтому не буду рассказывать, а лишь оставлю пример.
Да, тут нужно указывать свой тип, чтобы мы точно знали размер, логично же. И остальное только примерами:
На этом всё, я рассказал все возможности языка, прикольно же, да? Да похуй, даже, если не очень.
Как это обрабатывать, то?
Итак, у нас есть язык. Красивый, элегантный, и разработчик тоже крутой ( я если кто не понял ) Нам теперь нужен парсер, который сгенерирует нам наш код для вашего п2с за 300 евро в месяц. Всего есть несколько этапов:
Представь, что ты разбираешь конструктор LEGO. Сначала ты высыпаешь все детали на стол и начинаешь их сортировать. Красные к красным, синие к синим, а странные детальки, назначение которых ты не понимаешь, в отдельную кучку.
Вот что делает лексический анализатор (или, как его ласково называют, лексер):
На выходе мы получаем список токенов, и уже понимаем где что… Хз, кто это придумал, но мне кажется он достаточно умный, и это действительно удобный способ.
Второй этап: Синтаксический анализ, или "Собери их обратно, но красиво”
Теперь, когда у нас есть кучка токенов, пора собрать из них что-то осмысленное, может дерево? Да-да, ты не ослышался. Дерево. Абстрактное синтаксическое дерево (AST), если быть точным.
( это не настоящий код, если что )
По сути он делает то же самое, что ты делаешь, собирая конструктор по инструкции. "Так, мне нужна красная деталька 2x4, потом синяя 1x2...". Только вместо деталек у нас поля и типы.
Третий этап: Семантический анализ, или "Проверка на здравый смысл”
Отлично, у нас есть красивое дерево! Но оно может оказаться немного... бессмысленным
Семантический анализ по факту просто смотрит на наше дерево и задает вопросы:
Четвёртый этап: Кодогенерация, или "Блять, ты же собирал самолёт, почему у тебя вышла машина?”
Итак, у нас есть красивое абстрактное синтаксическое дерево (AST). Оно, конечно, прекрасно, но, увы, совершенно бесполезно… Нам же нужен код, а не деревья
Мы для этого будем используем Jinja2. Нет, это не название нового аниме гаремника. Это шаблонизатор с возможностями пайтона, вот прикол так выглядит:
Увы, я его до сих пор не закончил в проекте, кто хочет можете заняться, разрешаю.
Заключительный аккорд
"Но подождите," — скажешь ты, — "разве этого достаточно для защиты от читеров?" О, мой наивный друг, конечно нет!
Чтобы на этом прочном фундаменте, действительно сделать защиту нужно чуть добавить, например:
Всем спасибо, до новых встреч! Мой шиза-блог:
Репозиторий:
P.S. Если после прочтения этих статей ты решил забросить программирование и заняться чем-нибудь более умным, или адекватным, например, квантовой физикой, то тебя можно понять, шиза же!
нет ответов спустя 15 секунд после публикации, югейм умирает...
Пожалуйста, авторизуйтесь для просмотра ссылки.
/
Пожалуйста, авторизуйтесь для просмотра ссылки.
Привет, дорогой читатель! Буду честен, это очередная не интересная статья о очередных велосипедах. Все мои статья пишутся чисто для развлечения, по приколу так сказать…
Очень давно мне пришла идиотская идея создать простой фундамент для анти-чита, который бы не полагался на что либо, а давил со всех фронтов! И вот эта серия начинает этот путь.
Сегодня мы начнём с идеи передачи данных между анти-читом, и сервером. Почему не протобафф?
- Протобафф диктует стандарт, который каждый гейм хакер знает
- Слишком большие размеры
- Ну, а еще мне просто интересно сделать что-то такое, вопросы?
Да, ты всё правильно понял. Проблемы просто нет, я её выдумал в своей голове, а почему нет? В целом проблема достаточно простая и фундаментальная, и вопрос звучит так:
И вот из этого концепта сделана первая альфа версия языка описания данных для анти-чита.Почему бы нам не передавать тупо поток байт, как это делает флатбуфферс? Но при этом автоматически понимать что это за пакет, и при этом иметь возможность легко обфусцировать, и модифицировать наш сгенерированный код?
Получается, вкратце нам нужен язык, который:
- Будет простой для описания данных
- Не будет давать реверс-инженеру лишней информации
- Удобный, и при этом достаточно лёгкий!
- Ну и самое главное, его можно легко дополнить в случае чего, либо в целом переписать кодген добавив свою обфускацию, или виртуальную машину. А почему нет?
Чтобы что-то передавать между разными ЯП нужно чтобы это что-то было стандартизировано, очевидно же! Вот для таких целей у языка есть небольшая спецификация ( я её по приколу накидал, если честно ).
Salary Dependent Bytes
Ладно, начнём. Я вот изучал исходники телеграма, и там у них тоже есть свой протокол, и я решил оттуда украть автоматическое определение пакета. Как это работает?
- У нас есть название пакета, оно уникальное типа, если вы вдруг сами не поняли
- Мы берём CRC32 хэш от этого названия пакета, и запоминаем
- И когда отправляем пакет добавляем как заголовок первые 4 байта этот хэш
- А уже там где принимают уже классифицируем нужный класс по этому хэшу
Плюсы | Минусы |
---|---|
Легко | Скорость |
Не создаём дополнительных трудностей для разной архитектуры | Дополнительные аллокации |
То есть при условном банальном пакете
Код:
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
}
- name: string - строковое поле. Ничего сложного, правда?
- health: u32 - беззнаковое 32-битное целое число. Потому что отрицательного здоровья не бывает (ну, почти).
- position: [3]f32 - массив из трех 32-битных чисел с плавающей точкой. X, Y, Z - все как полагается, но можно сделать там отдельно пакет Vector, и составлять… Но не сейчас!
- inventory: []Item - динамический массив элементов типа Item. Потому что мы не знаем, сколько хлама таскает с собой игрок.
Вот список типов, которые мы поддерживаем:
- 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
}
Пора переходить на более тяжелые вещи, эффект от которых перекроет наркотики. Нет, я говорю о дженериках. Они константные, и определяются во время процесинга, т.е в сгенерированном коде их не будет! Снова пример:
Код:
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 евро в месяц. Всего есть несколько этапов:
- Парсим файл на токены
- Из токенов строим дерево
- Проверяем это дерево
- Генерируем код
- Пишем “готово!!”
Представь, что ты разбираешь конструктор 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...". Только вместо деталек у нас поля и типы.
Третий этап: Семантический анализ, или "Проверка на здравый смысл”
Отлично, у нас есть красивое дерево! Но оно может оказаться немного... бессмысленным
Семантический анализ по факту просто смотрит на наше дерево и задает вопросы:
- "Эй, ты пытаешься использовать тип Unicorn, но я такого не знаю. Ты его точно определил?"
- "Массив отрицательного размера? Серьезно?"
Код:
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 секунд после публикации, югейм умирает...
Последнее редактирование: