Сказ о том, почему LelResetFeg со спидраном не свезло

3 дня назад во время прохождения GTA: San Andreas на скорость товарищу LelResetFeg на голову упал вертолёт. Некоторые игроки начали подозревать стримера в нечестной игре, но если подумать, то ситуация выглядит слишком глупо
Если вы ранее играли в GTA: SA, то вполне возможно, что подобное с вами уже случалось: совершенно рандомная последовательность клавиш приводила к активации какого-нибудь чита.
В этом посте мы разберёмся, что же приводит к такому поведению игры

Дальше будет много технических подробностей, а сочетания, которые мог случайно прожать стример, будут в конце

Для начала, нужно понять, как игра хранит список чит кодов.
Этот вопрос решается простым гуглением:

GTAG Modding:
San Andreas stores the last 29 characters typed on the keyboard, hashes the resulting string and compares it with the hashes of the cheats. Because the cheats are stored in hash form, it is much harder to figure out what they are. Most cheats have more than one code due to hash collisions and the intended cheat phrases for many took a long time to find.
...
Cheats have a minimum length of 6 characters and are stored all in upper-case.


Отлично, теперь мы знаем следующее:

- Чит коды хранятся в виде хешей
- Хеш-функция достаточно слабая (много коллизий)
- Длина чит кода может быть от 6 до 29 символов

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

Пример: Вы можете хешировать «Войну и мир» Л. Н. Толстого и на выходе получить строку вроде "73279c854ad356869d3f59150ebc964d53269aed" (хеш по алгоритму SHA-1). Затем, если вам скинут архив с кучей книг и рандомными названиями, то найти среди них нужное издание книги вы сможете просчитав хеши всех книг, и найдя среди них книгу с хешем "73279c854ad356869d3f59150ebc964d53269aed" (не читая ни названий, ни содержания).
Вероятность того, что хеш SHA-1 совпадёт с хешем другой книги - крайне мала.

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

Итак, что мы можем сделать дальше?
Поверим англоязычным товарищам на слово и будем считать, что хеш-функция действительно слабая.
Для этого есть некоторые предпосылки, например, оптимизация: во времена разработки GTA: SA (напомню, игра вышла в конце 2004 года) трата кучи ресурсов компьютера на просчёт хеша последних введённых символов на клавиатуре была бы... не лучшим вложением.

Наша задача - найти как можно больше коллизий для чит кода OHDUDE (спавн вертолёта Hunter), для её упрощения (и ускорения расчётов) мы ограничимся набором символов "WASD"

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

Есть хеши чит-кодов:

Сказ о том, почему LelResetFeg со спидраном не свезло GTA: San Andreas, Программирование, Читы, Реверс-инжиниринг, Видео, Длиннопост

Каждый хеш длиной в 4 байта, с такой длиной куча коллизий - не сюрприз.
Запомним хеш нужного нам чита - 0xE958788A
На ум сразу приходит CRC32 (32=8*4 - от количества бит в хеше), вот только не понятно, какая его разновидность используется в игре.

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

Сказ о том, почему LelResetFeg со спидраном не свезло GTA: San Andreas, Программирование, Читы, Реверс-инжиниринг, Видео, Длиннопост

На этом этапе я провёл достаточно много времени, обнаружил на форумах утверждение, что в игре используется CRC-32/JAMCRC. Также было упоминание о расчётах CRC-32 в исходных кодах инструмента для изменения игровых файлов, но результаты никак не сходились.
До тех пор, пока до меня не дошло, что гораздо удобнее хранить историю нажатых клавиш в перевёрнутом виде. Посмотрим:

Сказ о том, почему LelResetFeg со спидраном не свезло GTA: San Andreas, Программирование, Читы, Реверс-инжиниринг, Видео, Длиннопост

Ха, вот оно как!
Значит, осталось перебрать все возможные последовательности нажатия кнопок W, A, S и D, хеш CRC-32/JAMCRC которых будет равен 0xE958788A.

Конечно, делать это мы будем не ручками, а при помощи небольшой программы.
В ней нет почти ничего интересного, суть сводится вот к чему:
- Получаем на вход ожидаемый чит код
- Считаем его хеш
- Перебираем все возможные сочетания WASD (длиной до 17 символов, но это можно изменить)
- Выплёвываем все совпадения, предварительно перевернув строку задом наперёд
Самые любопытные могут почитать исходный код. (Писалось под Linux/g++, заводилось на WSL)

Итак, после перебора 17 179 869 100 возможных сочетаний мы получаем следующий список:

1. DASD AWAA WSSW WAW
2. AWDD WAAD SAAA WASS S
3. SWSW SWAA AWWW ASWW S
4. SSWD AWSS ADWD SSDW A
5. SDAW WDWS DDWS WSAW A
(если проверять все возможные сочетания, список будет гораздо длиннее, но перебор 288 230 376 151 711 744 сочетаний займёт... скажем, достаточно продолжительное время)


5 коллизий, и это только 4 кнопки с макс. длиной 17 символов! Время проверить:

Ни одной осечки :)
Такой вот ночной детектив.
Будете генерировать свои версии чит-кодов - кидайте в комментарии, может кому-нибудь они ещё понадобятся.

Лига Геймеров

44.4K постов88.9K подписчиков

Добавить пост

Правила сообщества

Ничто не истинно, все дозволено, кроме политоты, за нее пермач, идите на ютуб
Помни!
- Новостные/информационные публикации постим в pikabu GAMES
- Развлекательный контент в Лигу Геймеров



Нельзя:

Попрошайничать;

Рекламировать;

Оскорблять участников сообщества;

Нельзя оценивать Toki Tori ниже чем на 10 баллов из 10;

Выкладывать ваши кулвидосы с только что зареганных акков - пермач

Вы смотрите срез комментариев. Показать все
50
Автор поста оценил этот комментарий
В чем прикол использования хеша всесто простого сравнения строк?
раскрыть ветку (46)
20
Автор поста оценил этот комментарий

Чтобы ни в бинарнике игры, ни во время проверки в памяти, чит-последовательности не появлялись в открытом виде.

раскрыть ветку (6)
2
Автор поста оценил этот комментарий

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

раскрыть ветку (3)
1
Автор поста оценил этот комментарий

нутк пасхалка получается неплохая)

раскрыть ветку (2)
1
Автор поста оценил этот комментарий

Такую пасхалку тебе потом как баг заведут)

Например как то пытались в один довольно известный шутер баллистику завезти. Игроки просто не поняли прикола когда их начало убивать рикошетами. Убрали в итоге

раскрыть ветку (1)
1
DELETED
Автор поста оценил этот комментарий

что за шутер?

2
Автор поста оценил этот комментарий

но хранить-то их можно и в зашифрованном виде! Зачем именно хеш?

раскрыть ветку (1)
Автор поста оценил этот комментарий
Хэш - чисто численный, в то время, как строка - это ещё одна обертка над кодами символов. Короче, так тупо проще
79
Автор поста оценил этот комментарий

Как минимум, чтобы после релиза игры все сразу не ринулись читерить :)
Определённо, были и иные причины, но пока что больше ничего не приходит в голову

раскрыть ветку (9)
54
Автор поста оценил этот комментарий
Иллюстрация к комментарию
ещё комментарии
2
Автор поста оценил этот комментарий

Если эти читы зараннее предусмотрены, то никто не будет от них защищаться :)
Я более чем уверен что у них были и другие "читы для разработчиков" которые работали только в определенной конфиурации компилятора.

4
Автор поста оценил этот комментарий

Что за минус за заднем плане? От Dr. Dre?

раскрыть ветку (3)
7
Автор поста оценил этот комментарий

Главная тема gta sa

раскрыть ветку (2)
5
Автор поста оценил этот комментарий

Благодарствую)

Да, я не играл ни в одну GTA)

раскрыть ветку (1)
16
Автор поста оценил этот комментарий
Предпросмотр
Автор поста оценил этот комментарий

Там же вроде как Арт-money работал еще)

14
Автор поста оценил этот комментарий

Обычно используется и то и то. Т.е. сначала ищется хеш в таблице, а потом сравнивается сам набор символов. Почему тут нет второй части - хз

раскрыть ветку (3)
Автор поста оценил этот комментарий

Обычно - это когда? Сравнивать сперва по хешам можно только если таблица довольно большая - от 10, или даже от 100 элементов, чтобы сложность сравшения хешей O(1) была реально больше, чем перебор O(N), да и то, сперва обрабатывается входная строчка, затем ищется хеш, затем идет сравнение, так что тут O(1) + o(2) по сути

раскрыть ветку (2)
Автор поста оценил этот комментарий

я про то, что после сравнения хешей (если они используются) необходимо прямое сравнение

раскрыть ветку (1)
1
Автор поста оценил этот комментарий

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

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

15
Автор поста оценил этот комментарий

прикол в том что это порт игры с PS2

5
Автор поста оценил этот комментарий

Чтобы сделать для них хеш таблицу, например

5
Автор поста оценил этот комментарий

Про crc32 не совсем уверен, но при условии использования простых алгоритмов хеширования посимвольное сравнение строк будет тратить больше процессорного времени, чем вычисление хеша с последующим его сравнением

раскрыть ветку (22)
28
Автор поста оценил этот комментарий

Так не нужно при каждом введеном символе сравнивать всю строку. Предположим у тебя есть поток символов и есть список читов: (это не полный список, но я думаю понятно, что такой метод будет работать с каким угодно длинным списком)

AEDUWNV

AEZAKMI

ANOSEONGLASS

ASNAEB

OSRBLHH

OUIQDMW

PROFESSIONALKILLER

PROFESSIONALSKIT

TURNDOWNTHEHEAT

TURNUPTHEHEAT

WANRLTW


На основе этого списка генерируем дерево поиска. Первый уровень дерева это первая буква последовательности, второй уровень, это вторая и так далее.


В результате чего первый уровень дерева будет выглядеть как A, O, P, T, W. Получив символ отличный от этих 5 мы его игнорируем. То есть нужно всего 5 сравнений. Получив например символ А мы перемещается на соотстветствующую ему ветвь дерева.


На этой ветви ожидаемый следующий символ будет E, N, S. Три сравнения. Если введенный входит в список, то продвигаемся дальше по дереву, до тех пор пока не дойдем до вызова функции. Если же хотя бы на оном этапе мы получаем символ не входящий в ожидаемую последовательность, то возвращаемся к началу дерева.


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

раскрыть ветку (21)
3
Автор поста оценил этот комментарий

Вариант конечно хороший, но не стоит забывать что игра изначально делалась под консоль с всего 32мб ОЗУ, а такое дерево займет гораздо больше памяти, чем обычный массив пар хеш:обработчик. А так же есть вероятность что разработчики чисто по инерции могли выбрать наиболее оптимальный по объему использованной памяти вариант

раскрыть ветку (2)
3
Автор поста оценил этот комментарий

С одной стороны да, это будет больше чем просто массив строк. С другой стороны в абсолютном измерении разница будет не очень большой. Я без проблем реализую этот алгоритм на 8 битном микроконтроллере.

раскрыть ветку (1)
1
Автор поста оценил этот комментарий

Отлично, вы приняты

Автор поста оценил этот комментарий

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

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

Вот в этой фразе есть подвох: "На основе этого списка генерируем дерево поиска." Как это хранить? Если это написано на С++, то вы, вероятно, будете использоваться std::map, который будет выделять в куче каждую следующую пару ключ-значение. И это практически гаратированно приведет к огромному количеству кеш миссов при поиске. Если использовать unordered map то все данные будут храниться компактее. Конечно можно заморочиться и начать писать свои аллокаторы которые будут все это дело складывать в один кусок памяти, но это уже оверинжиниринг для такой простой фичи.

К тому же вам может быть не известно начало последовательности. Например вы последовательно нажимаете клавиши "ABCDEFGH". Зная, допустим, что максимальная длинна чит кода 5 символов, вам нужно проверить  комбинации: A, AB, ABC, BC, ABCD, BCD, CD и так далее (+ оптимизации на минимальный размер чит-кода). В таком случае будет очень много поисков и все они будут начинаться не с одного и того же символа, что не позволяет просто спускаться по бинарному дереву поиска во время набора (но отлично работает для автодополнения).


А ещё оба этих подхода будут разбивать друг друга по всем арактеристикам при определенных количествах пар ключ-значение (этих данных у нас, увы, нет).

2
Автор поста оценил этот комментарий

я думаю игра просто пересчитывает кеш каждый раз когда добавляется символ а потом по мепу проходится в поисках соотвествия.
У деревьев поиска есть минус, они запекаются. Поэмтому в картах например от деревье поиска ушли (в стандарте nds была такая штука как nvc - next valid character которая являлась по сути деревом поиска).

раскрыть ветку (16)
3
Автор поста оценил этот комментарий

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


И да не совсем понимаю в чем проблема "запекания" деревьев (если я правильно понял, что именно ты под этим термином подразумеваешь, то построение делается на этапе компиляции ну или как вариант на этапе запуска и на производительность для пользователя во время игры ни как не влияет)

раскрыть ветку (15)
1
Автор поста оценил этот комментарий

Ага, по аналогии со светом который заранее считается и "запекается" в карту.

Возможно ресурсов не было и сделали как проще.

раскрыть ветку (7)
1
Автор поста оценил этот комментарий

А блин, я дятел, в первом коменте про настоящие карты, которые в навигаторах, а 2ой компент про игровые карты...

раскрыть ветку (4)
1
Автор поста оценил этот комментарий

Фукс?

раскрыть ветку (3)
Автор поста оценил этот комментарий

м? не понял... я говорил про стандарт навигационых карт NDS. и про игровые карты типо Quake 2 где свет запечен в саму карту и не считается во время игры.

раскрыть ветку (2)
Автор поста оценил этот комментарий

"— Эй вы, Фукс, — говорю я, — какой же вы, к черту, матрос?


— А я, — ответил он, — не матрос, я сейчас просто так застрял на мели, а друзья мне посоветовали климат переменить…


— Позвольте, — перебил я, — а как же мне Лом говорил, что вы в картах умеете разбираться?


— О, это сколько угодно, — отвечает он. — Карты — это моя специальность, карты — это мой хлеб, только не морские, а, простите, игральные карты. Я, если хотите знать, я карточный шулер по профессии."


Андрей Некрасов. «Приключения капитана Врунгеля»

раскрыть ветку (1)
Автор поста оценил этот комментарий

А.... Просто я умудрился поработать и с теми и с другими....

Автор поста оценил этот комментарий

Построение такого дерева для относительно небольшого списка читов при желании даже вручную делается за приемлемое время. Тут же речь идёт не о миллионах записей, а о сотне или около того (я хз сколько читов в GTA, но не думаю что сильно больше сотни, а скорее даже меньше)

раскрыть ветку (1)
Автор поста оценил этот комментарий

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

1
Автор поста оценил этот комментарий

При таком алгоритме декомпиляцией сразу выдаст все последовательности чит-кодов. Ни разу вы наверное код на продажу не писали, когда цель стоит всё спрятать подальше :)

А по быстродействию - даже на технике 2004 года это была неуловимо небольшая разница.

раскрыть ветку (6)
3
Автор поста оценил этот комментарий

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


2) далее как мы видим из статьи декомпилировав хеши в принципе можно без проблем подобрать рабочие читы, которые конечно будут не такими как задумывали разработчики, но в целом рабочими.

раскрыть ветку (5)
Автор поста оценил этот комментарий

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

раскрыть ветку (1)
1
Автор поста оценил этот комментарий

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

Автор поста оценил этот комментарий

Ну, легко рассуждать, когда вам уже всё нашли, выяснили, разжевали и в рот положили. Даже топикстартер на 80% использовал инфу, выясненную до него. А с нуля это довольно непросто выявить, скрипткиддисы отсеиваются. Разработчик не ставит цель написать "неломаемую" защиту, защита расчитывается на то, чтобы продержаться заданное время (например полгода с выхода).

раскрыть ветку (2)
1
Автор поста оценил этот комментарий

Попробуй объяснить какой резон разработчикам вообще прятать такую вещь как читы?

раскрыть ветку (1)
Автор поста оценил этот комментарий

Потому что могут :)

Вы смотрите срез комментариев. Чтобы написать комментарий, перейдите к общему списку