Terraria: или пишите игры правильно

  • Tutorial


Привет, дорогой хабрапользователь!

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


Вступление


Начнем, пожалуй, с того, что такое Terraria и как она появилась.

Феноменальный успех неизменно пребывающей в разработке «песочницы» Minecraft, уже принесшей Маркусу Персону миллионы, не мог остаться незамеченным. Так и случилось, вскоре появляется на свет Terraria. Занимается разработкой один единственный человек, Эндрю Спинкс, главный дизайнер и по совместительству не менее главный программист.

При взгляде на здешние «восьмибитные» пейзажи услужливое подсознание сразу спешит навесить ярлык «Minecraft в 2D». А что? В рюкзаке — кирка и топор, вокруг — случайно сгенерированные просторы. Цель — копать, строить, убивать, добывать.

Больше вы можете узнать, почитав специальные статьи об этой игре. Ну а хабр требует технической информации.

Как оно работает?


Игра написана на языке C# (.NET 4.0) с использованием фреймворка XNA, о котором я достаточно много писал на хабр, например тут, тут и тут.

Изучаем саму игрушку


Купив игру, поиграв в нее около двух недель со своими друзьями — она мне немножко наскучила, я решил более детально изучить её структуру. Как особенности структуры — можно использовать в своих целях.

Игра написана с помощью XNA и .NET, а значит — все бинарные файлы и файлы библиотек можно посмотреть насквозь с помощью рефлектора, например: .NET Reflector.

Открываем Terraria.exe, ищем точку входа Main (Program):


Видим забавные строки:
Steam.Init();
if (Steam.SteamInit)
{
       main.Run();
} else {
       MessageBox.Show("Please launch the game from your Steam client.", "Error");
}


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

Для того, чтобы обойти эту «безопасность», достаточно подменить steam_api.dll (откуда импортируются функции) или же можно пересобрать приложение, закоментировав соответствующие строчки. Ведь сам Steam никак не влияет на игру, кроме того, что добавляет туда свой Layout. Но мы пойдем более интересным способом и попробуем даже влиять на саму игру.

Вспомним, что игра написана с использованием — XNA, а значит, у нее должен быть главный класс игры, который наследуется от Microsoft.XNA.Framework.Game, далеко идти не пришлось, это класс Main.

Любая игра, написанная на XNA, имеет в себе, так называемые «компоненты», которые можно туда добавить. Компоненты могут быть как обычными (логика), так и графическими (Drawable).

А теперь давайте подумаем, что можно сделать?

Главный класс у нас Main и он имеет модификатор public (public class Main: Game {… })!
Чем это грозит? Мы можем создать новое приложение, которые будет импортировать наш Terraria.exe в качестве библиотеки и запустит её, а дальше — можно добавить свой компонент игры, и этот компонент будет иметь почти полный доступ к игре.

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

Стоило бы придать главному классу модификатор доступа отличный от public, как все бы у нас провалилось.

Все, дальше — очень просто, создаем компонент и добавляем его в main.Components. Однако, мне захотелось так же порисовать на spriteBatch'e террарии. С DrawableCompontent возникли сложности, т.к. он рисуется до основной прорисовки класса Main, как бы я не играл с DrawOrder.

Потом, я еще раз взглянул на класс Main, у него отсутствовал модификатор sealed, что так же доставило и упростило мне жизнь. Идея стала куда проще: просто унаследоваться от нашего Main.

Практика, пишем код


Создаем новое консольное приложение, подключаем в качестве библиотек Microsoft.Xna.Framework.*, Terraria.exe.

Теперь создадим класс, который будет наследоваться от Main:
sealed class InjectedMain : Terraria.Main
{
        private SpriteFont font;
        private SpriteBatch spriteBatch;

        internal InjectedMain() : base() { }

        protected override void LoadContent()
        {
            base.LoadContent();

            font = Terraria.Main.fontMouseText; // получаем какой-нибудь шрифт
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
        }
}


Теперь идем в точку входа и заставим запустится наш игровой класс:
static void Main(string[] args)
{
     try { Program.game = new InjectedMain(); }
     catch { Console.WriteLine("fail, sorry :("); Console.ReadKey(); return; }

     Program.game.Run();
}


Ну и нарисуем что-нибудь, добавим в наш переопределенный Draw:

spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);
spriteBatch.DrawString(font, "Hello habrahabr!", new Vector2(5f, 5f), Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 1f);
spriteBatch.End();


Результат:


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

Ну и напоследок сделаем что-нибудь эффектное, какой-нибудь хак.

У игрока террарии есть одно интересное свойство: ghost, которое превращает игрока в каспера приведение и позволяет проходить сквозь стены и летать по миру (наверняка, фишка для девелопера). Так сделаем же так, чтобы при нажатии и удержании Left Shift — игрок становился злым и коварным.

Идем в метод Update:

KeyboardState state = Keyboard.GetState();
Player local = Main.player[Main.myPlayer]; // получаем нашего игрока

local.ghost = state.IsKeyDown(Keys.LeftShift);
if (local.ghost)
{
        local.Ghost();
}

// пишем в чат
if (state.IsKeyDown(Keys.LeftShift) && oldKeyboardState.IsKeyUp(Keys.LeftShift))
{ Terraria.Main.NewText("Ghost activated!", 200, 200, 255); }

 if (state.IsKeyUp(Keys.LeftShift) && oldKeyboardState.IsKeyDown(Keys.LeftShift))
{ Terraria.Main.NewText("Ghost deactivaed!", 200, 200, 255); }

            oldKeyboardState = state;



Запускаем игру и становимся приведением по клику на шифт:


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

Отдельно хочется сказать про класс Player, где есть функция Save/Load, которая позволяет сохранять и загружать игроков соответственно, принимает и отдает она сам класс игрока Player. Т.е. мы можем изменить игрока чуть менее, чем полностью, сохранить его и использовать в игре. Или же, например, сохранить всех игроков на сервере в файлы, а потом закинуть их в папку Players и играть ими.

Мораль


Всегда используйте модификаторы доступа как надо, а классы, которые конечны — sealed (запрещает наследование). Для таблетки от паранойи верности можно еще и обфусцировать код.

Так же, если реализуете мультиплеер — сделайте достойную синхронизацию и так, чтобы вся логика проверялась на сервере, а в случае резкого несоответствия — отключать игрока. К примеру, как игрок может моментально переместиться из одной точки карты в другую за время, которое меньше секунды? Увы, сервер террарии считает это нормальным.

Эта статья писалась исключительно в ознакомительных целях: как на примере простых модификаторов — можно написать нехилый хак.

Исходники статьи, увы, не буду прикладывать, идея понятна.

До новых встреч!
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 77

    +2
    Да, допущения, конечно, грубые, но обращаешь внимание на них только тогда, когда этими дырами кто-то воспользуется. Предлагаю отправить письмо разработчикам на эту тему.
    Кстати по поводу sealed — они еще и помогают компилятору выдавать более оптимальный код за счет упрощения вызовов методов класса.
      +4
      Предлагаю отправить письмо разработчикам на эту тему.
      К сожалению разработчики больше не поддерживают игру.
        +1
        Одна надежда на Starbound. Он, кстати, на С++ пишется. По сложнее, наверно, будет с хаком :)
          +2
          Очень жаль, а я только ее купил по-честному.
            +2
            Ничего, играть всё равно можно долго и с удовольствием. В компании интереснее.
        +2
        Кривость синхронизации позволяет играть в мультиплеере, модифицируя память клиента…
          +2
          Ну это уже классика :-)
          +2


          День 200-ый
          Посетители столовой с ужасом находят, что, чтобы насыпать соли, они должны подойти к официанту, предьявить паспорт, получить специальный 8-значный одноразовый код к солонке. Для получения перца процедуру следует повторить.

          А теперь представьте, что бы натворил ForhaxeD, если бы это была бы игра с открытым исходным кодом… Ведь речь в статье не о серверной статье (где безусловно всё надо проверять), а о клиентской.

          Кстати, такие «недочёты» в клиентах могут специально существовать для привлечения создателей модов, что в свою очередь может сильно повысить популярность и среди игроков.
            +1
            Этой статьей — я хотел показать о важности понимания того языка/фреймворка на котором ты пишешь, специально можно «осуществить» только API, который позволял бы делать эти самые ваши моды без извращений.
              +3
              На написание хоть какого-то SDK находятся силы лишь у единиц игросоздателей, обычно всё ограничивается недокументированным форматом хранения неисполняемых данных. Впрочем, даже это мало когда мешало моддерам в написании улучшений, если игра того достойна. Здесь же речь об инди-игре, которая вряд ли бы вышла в свет, если автор начал писать ещё и SDK. Посему предлагаю считать возможность наследования классов скорее фичей, чем багом.
                0
                А что там, простите, писать? Можно было элементарно сделать подключение библиотек на «лету», воспользоваться тем же System.Reflection. Еще один весомый аргумент тот, что код террарии и без модификаторов насыщен самыми разнообразными костылями, я полагаю это тоже фичи :-)
                –9
                блин. Ну нельзя, автор, «показать о важности»!!! Твою статью читать невозможно, потому что «почитав специальные статьи об этой игре, а хабр требует технической информации», а так же «я решил более детально изучить её структуру и о том, как особенности структуры — можно использовать в своих целях»

                Ну ты что, «соответственно», в свободное от хабра время консультантом в М-Видео работаешь?
                  +6
                  Знаете, о таких вещах — следует писать в личку. Так же, не плохо бы, быть более уважительнее к другим пользователям. Мы с вами не знакомы, поэтому предпочитаю начать общение в более дружественном ключе, например, научиться употреблять «вы» в диалоги с незнакомыми людьми.
                    –7
                    Прежде чем поучать других, поучился бы сам. «О таких вещах» пороть надо, а не в личку писать. Потому что ты своими графоманскими эксерсизами просто изнасиловал глаза минимум 170 людям. Я понимаю, в редакторе комментариев нет компилятора, который укажет на ошибки, но, коллега: наши интерпретаторы валятся на «ваших» скриптах.
                      +3
                      Вот и написали бы в личку список ошибок и спасли бы от изнасилования кучу народа.
                      А так — сначала автор изнасиловал ошибками, а потом вы ударили по людям своими хамскими комментариями.
                        –6
                        Обычно я это делаю. Но на этом конкретном тексте мои личные блоки на безграмотное письмо были пробиты буквально в первом абзаце. Мне не хотелось бы, чтобы в этом конкретном случае автор получил кредиты за грамотно и хорошо написанную статью, потому что автор такой статьи не писал. Текст написан так, что топики с тегом «перевод» выглядят просто текстами Пушкина. Этот текст нельзя поправить, его можно только переписать заново, сохранив лишь вставки кода.

                        Я вот даже не понимаю как кодящий человек может так писать. Ведь у программеров превалирует шаблонное мышление. Где и как можно нахвататься таких шаблонов как «показать о том»? Как можно написать «я решил более детально изучить её структуру и о том, как особенности структуры — можно использовать в своих целях» — так же даже грузчики на овощных складах не говорят. Ну как?
                          –5
                          Вот приколитесь — автор «поправил» текст, цитирую:
                          «Купив игру, поиграв в нее около двух недель со своими друзьями — она мне немножко наскучила, я решил более детально изучить её структуру. Как особенности структуры — можно использовать в своих целях.»

                          Как можно писать «купив игру, она мне наскучила»? Либо у автора основной язык ангийский (там, если не ошибаюсь, в худлитературе причастные обороты так могут строится), либо чат-бот, причем простенький.
                          «Как особенности структуры — можно использовать в своих целях». Вот о чем хотел нам показать автор этим предложением, а?
                            +3
                            Ну вот и напишите в личку и предложите вариант получше
                              –4
                              как скажете. вы же лучше знаете, как _надо_ и как _следует_
                                +3
                                Ну реально, вот зачем это в комментах? Только бесит.
                                Я вот статью по диагонали прочитал, потому ошибки вообще не зацепили. А в комментах просто срач развели. Аж тошно.
                                  –4
                                  Знаете, о таких вещах — следует писать в личку. Так же, не плохо бы, быть более уважительнее к другим пользователям.
                                    +1
                                    А вы не передергивайте других, и сами немного уважайте других — все ошибаются.
                                      –3
                                      а вы посмотрите в словаре значение слова «передергивать»
                      +2
                      Предлагаю перед тем как ругаться о чистоте языка, научиться писать «также» и «так же» правильно :-)
                        –5
                        о да, я далеко не идеал
                      +5
                      Разработчик писал на форуме, что он просто решил поизучать XNA, в результате получилось нечто играбельное, and the rest is history. А отсутствие защиты или обфускации — его сознательное решение.
                    +9
                    «Купив игру, поиграв в нее около двух недель со своими друзьями — она мне немножко наскучила, я решил более детально изучить её структуру и о том, как особенности структуры — можно использовать в своих целях.»

                    Ставлю сотню на то, что в детстве вы разбирали на детали свои игрушки :)
                      +23
                      все разбирали.
                        +21
                        Ну, почему же. Многие, например, наоборот искали разобранные и собирали.
                        Из них, наверное, получились отличные гентушники :)
                          +4
                          моя полицейская машинка обратно не собралась :(
                            +10
                            Я не смог собрать обратно часы. Много часов.
                              +4
                              Особенно большие будильники с противной пружиной.
                            • UFO just landed and posted this here
                                0
                                да я в садике разбирал, было тогда года 4-5.
                                Как работает заводная пружина внутри я так тогда и не понял
                                • UFO just landed and posted this here
                              0
                              Разбирали и собирали с другом из одной кучи игрушек другую.
                            +1
                            Все мы…
                              +2
                              Поэтому и не боюсь за свою сотню )
                            +1
                            уже давно в среде игроков терарии есть tmod и tshock. Tschok точно такие моменты проверяет и сопротивляется. Однако предметы себе выдавать все равно можно
                              0
                              Максимум, что сделал Tschok — кинул меня в старую позицию, когда я пытался пролететь пол карты. Однако, на короткие дистанции — все нормально.

                              И вообще, такие вещи должны быть реализованы разработчиками, а не любителями, в этом суть статьи.
                                0
                                мне кажется разработчику терарии все равно. Он даже пользовательские моды не любит.
                              +4
                              Раз уж разговор про Террарию и Стим. Предлагаю всем хабраюзерам, имеющим аккаунты в стиме попытать удачу в розыгрыше игры. Розыгрыш приватный, закончится через 3 дня.
                              Сслыка:
                              www . steamgifts . com/giveaway/Sq9c4/terraria
                                +19
                                Хабр, ты идиот. Прочитав эту статью, я вспомнил, что у меня есть одна не подаренная копия Террарии в Стиме.
                                Решил отдать её случайному хабраюзеру, для чего создал приватную раздачу для хабраюзеров на специально созданном для этого сервисе, не требующем регистрации, без реферальной системы, смс и прочих неприятных вещей.
                                В итоге заминусовали и слили карму. Кто нибудь из минусующих может объяснить мне причину такого негодования?
                                Кстати, на раздачу люди все равно подписываются.
                                  +7
                                  Это выглядело как спам, особенно разбитая ссылка. И надо было уточнить что проводишь именно ты, а не какая-то конторка.
                                    0
                                    Это что, серьёзно требутся 50 баксов на счету для работы с этим сервисом? Не, матожидание потерянных денег в районе 50$ — 0.025*40$ ~ 49$, ну нафиг.
                                      0
                                      Требуется игр, накупленных на 50$, чтобы зарегистрироваться на стимгифтах, тем более учитывается американская стоимость игры — то есть вполне достаточно иметь один-два ААА-тайтла, чтоб зарегистрироваться.
                                        +1
                                        Ах вот оно как. Пошёл попытаться сделать sign in по второму разу — в этот раз всё вышло безупречно, нужное количество денег на привязанных играх нашлось. Тем не менее как-то же я в первый раз эту ошибку увидел?.. Спасибо, в общем. Узнал об этом сервисе.
                                  +2
                                  Я это просто тут оставлю. Это если будет интересно что-то подобное.
                                  kag2d.com/
                                    +3
                                    [quote]Игра написана на языке C# (.NET 4.0) с использованием фреймворка XNA[/quote]
                                    Перенесите эту строчку в раздел «Мораль», дописав перед ней «А всё потому, что ___»
                                      +1
                                      Прошу прощенья, тэги всегда пишу от руки, а по кнопке «Предпросмотр» промахнулся.
                                      • UFO just landed and posted this here
                                      +5
                                      Акценты в статье совершенно не те сделаны по-моему. Расковырять исходники можно практически от чего угодно. А вот то, что хакнутый клиент оставляет возможность играть в мультиплеер — однозначный косяк.
                                        +1
                                        Вот именно. И если даже код обфусцирован, можно расковырять протокол обмена данными. Это примерно как в веб-приложениях с джаваскриптовой валидацией данных. Она как бы не помешает, но проверка данных на сервере — обязательна.
                                        +7
                                        Может я слишком резок в оценке и суждениях, но я не верю, что эта статья набрала столько плюсов! В мире приложений .NET, которые никак не защищены, есть проблемы и покрупнее. Даже если вы «обезопасили» своё приложение словами protected/internal/sealed, на них есть другое слово в ответ — Reflection. Действительно попортить жизнь реверс-инженерам могут только серьёзные обфускаторы, шифрование сборок и иные серьёзные механизмы. Кулхацкеры, блин…
                                          0
                                          Да, в общем, даже обфускаторы особенно не могут. До тех пор, пока оно все сводится к четко определенной виртуальной машине — все будет достаточно тривиально деобфусцироваться. Единственный более-менее работающий способ — это уходить от существующей виртуальной машины, например, запуская в ней эмулятор какой-то другой машины (например, регистровую машину типа x86 или ARM) и компилировать защищаемый код под эту другую архитектуру. Разумеется, это будет работать достаточно медленно. И, конечно — это все тоже security through obscurity.
                                            +2
                                            Вот именно! Блин, открытие века — библиотеки можно подгружать и наследовать типы! Это же каким хакером надо быть. Думаю Reflection будет шоком — там можно даже круто обойти модификатор private и почуствовать себя богом в отдельно взятом процессе.
                                            • UFO just landed and posted this here
                                                +5
                                                Если на клиенте есть уязвимость, нужно всегда ожидать, что она по вам ударит, на каком бы языке вы не писали. Пишите изначально исходя из того, что исходники вашего клиент утекли в сеть и сотни злобных школьников хакеров думают, как взломать ваш сервер.
                                                +2
                                                Совершенно согласен, ещё и таким тоном написано, как будто автор — бог, а автор игры — лох.
                                                0
                                                С другой стороны, тут есть небольшой плюс: как я понимаю, именно это и позволило написать wrapper для запуска игры под маком. Wrapper на Mono, наследуя(?) основной класс, отключает проверку Steam (хотя он есть под маком, но игре он не нужен, как написано выше), запускает всё это дело вполне себе нативно. Хотелось бы узнать детали подхода, сам плохо разбираюсь.
                                                  +7
                                                  Вообще-то автор игры писал, что
                                                  the decision to not obfuscate Terraria was made for a reason...
                                                    0
                                                    Не помню точно, как дела обстоят в .NET, а в Java меня бы не остановил какой-то там package private (friendly) доступ. Я бы просто создал класс в том же пакете (но в другом jar-файле) и всё бы работало как надо. Ну в крайнем случае можно и через javaagent как угодно всё сделать. Не верю, что в .NET не прокатят подобные трюки, пусть даже friendly-доступ действует на уровне сборки, а не на уровне пакета (неймспейса). Вплоть до того, что поменять один байтик в dll-файле. Так что без обфускации и грамотного запутывания тут никак не обойтись. Ну а то, что про валидацию написано — очень и очень справедливо. На самом деле, именно это и есть основной косяк.
                                                      0
                                                      Terraria: или пишите игры правильно
                                                      Неподходящее название т.к. статья просто показывает как пользоваться классами из сторонних библиотек. А произвести инъекцию в память стороннего приложения труда не составляет и это не из-за неправильно написанной игры. Если и говорить о безопасности данной игры то самая большая проблема — хранение профиля локально у игрока. Рефлектор есть берем класса отвечающий за профиль и редактируем сейв заполняя его топ айтемами.
                                                        0
                                                        Я не силён в .NET, но мне интересно, можно ли с помощью этого «рефлектора» пересобрать приложение, например, под Mono для Linux?
                                                          0
                                                          Реализации XNA под *nix нет
                                                            0
                                                            А как же XNA для Mono?
                                                              0
                                                              Неожиданно. Не слышал о нём.
                                                              В общем всё зависит от используемых библиотек. Если оные есть под *nix, то можно, т.к. утилиты, подобные reflector позволяют полностью восстановить исходный код.
                                                              • UFO just landed and posted this here
                                                        • UFO just landed and posted this here
                                                            +1
                                                            Игра сделана for fun. Думаю, создатель не заморачивался по поводу хаков и прочего, т.к. нет принципиальной соревновательной цели. «Песочница».
                                                              +1
                                                              Спасибо за отличную статью!
                                                              Интересно, а можно ли как-то обойти проверку видеокарты?

                                                              Кстати,
                                                              К примеру, как игрок может моментально переместиться из одной точки карты в другую за время, которое меньше секунды? Увы, сервер террарии считает это нормальным.

                                                              Есть такой предмет, как Magic Mirror
                                                                0
                                                                Проверка видеокарты происходит на уровне самого фреймворка.
                                                                0
                                                                Стоило бы придать главному классу модификатор доступа отличный от public, как все бы у нас провалилось.

                                                                Модификаторы доступа не мешают вызывать методы/свойства через Reflection. Просто в случае sealed/private враппер состоял бы из сплошных Type.GetType(...).GetMethod(...).Invoke(...);
                                                                  0
                                                                  Мне вот чисто из интереса, а если сделать что-то не-паблик, кому это помешает открыть байткод, сделать это паблик и не париться?
                                                                    0
                                                                    Да, выше говорили об «валидации клиента», дабы не пускать с «хакнутым». А как вы предлагаете это сделать так, чтобы нельзя было обойти? Давно интересует этот вопрос.
                                                                      0
                                                                      И что даст исспользование sealed/internal/private?
                                                                      .net легко декомпилируется и reflection работает с самой первой версии? Что помешает мне пересобрать версию с нужными мне модификаторами?
                                                                      Любую защиту при наличии физического доступа к иссполняемому коду можна сломать/обойти, вопрост только в стоимости.
                                                                      Для разработчика игры защитой могла бы быть валидация логики на серверной части + обфускация и навешивание протектора на клиент, но это усложняет разработку и увеличивает её стоимость что для indy-разработчика может быть неприемлемо.
                                                                        0
                                                                        Игра больше не поддерживается, но ведь благодаря не обфусцированному коду, доступу почти к бОльшей части функций… можно по сути реализовать чуть ли не все то, что обещал разработчик, но не реализовал?!

                                                                        Only users with full accounts can post comments. Log in, please.