Модификация игр на примере Arcanoid

Доброго времени суток!

Введение


На протяжении некоторого времени я наблюдал за блогом Assembler на хабре в виду того, что там начали появляться более чем отличные статьи по анализу различных keygen'ов и «reverse engineering». Я давно хотел заняться чем-то подобным и модифицировать какую-нибудь игру на J2ME. Я долго бродил по интернету в поисках хорошей, но в тоже время лёгкой для понимания (в плане анализа) игры. Однажды, я копался на сайте моего друга программиста (кстати, он тоже пишет программы для J2ME. Кто использовал ProPaintMobile — тот знает, о ком я говорю. И я нашёл её — это был простенький Арканоид. Видимо, это было чьё-то домашнее задание, или же он писался просто «just for fun», но тем не менее эта игра оказалась именно тем, чем нужно.


Что нам потребуется


Для того, чтобы произвести модификацию нам необходимы следующие компоненты:
  1. Декомпилятор, который выдал бы нам исходный код, чтобы было легче ориентироваться в процессе изменения байт-кода. Лично я использую для этих целей Java Decompiler, который вы можете стянуть здесь;
  2. Дизассемблер байт-кода виртуальной машины, который нам этот самый байт-код будет выдавать — JavaByte, который можно скачать здесь;
  3. Сам подопытный — Arcanoid;
  4. Ну и ещё неплохо было бы ознакомиться со спецификацией, в которой изложены инструкции виртуальной машины, которую вы можете почитать здесь.

Пожалуй всё. На самом деле, выбор софта дело индивидуальное, и, я бы сказал интимное. Поэтому вы можете с лёгкостью использовать, например, Jasmin вместо Java Decompiler.

Что нам теперь делать


Шаг первый

Сперва нам следует открыть вражескую машину эту самую игру и посмотреть, что там можно модифицировать. Так как исходного jar файла у нас нет, но есть преверифицированные классы и MANIFEST.MF, то давайте запакуем нашу игру. Сказано — сделано.


Структура нашего архива должна выглядеть именно так, как указанно выше. Осталось только переделать расширение — с zip на jar и…

Шаг второй

И запустить нашу игру. Давайте чуть-чуть поиграемся.


Продолжаем играть…


Упс. Ладно, ещё две жизни осталось. Играем дальше.


Всё, доигрался. Надо с этим что-то поделать.

Шаг третий

Делаем с этим что-нибудь, а именно декомпилируем. Нет проблем. Роемся в исходниках и понимаем, что самое интересное хранится в файле GCanvas.java. Давайте посмотрим внимательнее, что тут к чему:



Здесь мы с вами видим конструктор, переменные и методы.

Стоит напомнить, что мы ищем с вами где у нас убавляются жизни. Поищем упоминание live или же lives. Тут нам повезло. Так как игра не обфусцирована, то мы видим с вами переменные и методы с более-менее реальными именами. И тут наш взгяд сразу бросается на такую строку в самом начале исходного файла:


int m_m_LivesII;


Комментарии излишни. Это и есть счётчик наших жизней. Осталось лишь найти где эта переменная уменьшается. А вот где, как указывает декомпилятор, это строка 170:


this.m_m_LivesII -= 1;


Давайте изучим данный участок кода:


while (this.m_m_goGameZZ)
    {
      if (!this.m_m_playGameZZ)
      {
        if (this.m_m_boomZZ)
        {
          this.m_m_ballcBallcBall.setVisible(false);
           _AnimateBoomcGraphicsV(g);
             this.platform.setVisible(false);
               this.m_m_LivesII -= 1;


Тут, можно сказать, нам даётся полная инструкция: если переменная m_m_goGameZZ, которая является булевой находится в значении true, то игра идёт. При столкновении же, тоже булевая переменная под именем m_m_playGameZZ становится в значение false, то у анимируется взрыв платформы (_AnimateBoomGraphics), далее платформа становится невидимой (platform.setVisible(false)) и уже затем у нас отнимается жизнь, что вы можете увидеть в коде, который находится выше. Конечно же, первое, что приходит на ум — это вместо того, чтобы из переменной m_m_LivesII вычитать единицу, надо видоизменить код, чтобы её прибавить. Но это не по феншую как-то. Тогда уж просто приравняем переменную к единице. И перекомпилируем? Это нужно перекомпилировать, преверифицировать классы, а затем заново собрать всё в архив. Нет уж, мне лень. Но есть способ просто поправить байт-код класса и тогда нам не нужно будет выполнять первых два шага. Движемся дальше…

Какой там у нас шаг? Ах да, четвёртый

Для того, чтобы править байт-код у нас есть JavaBite. Что же воспользуемся им.
Откроем наш класс (Classes -> Add Java Class и увидим вот такую картину:



Надеюсь вы помните, где находится кусок кода, который наз интересует? Точно, это метод run. Заглянем в него:



Давайте пробежим глазами инструкции с самого начала. Стоп! Ничего не напоминает?



Да, это именно то, что нам нужно. Через инструкцию вниз вы заметите инструкцию isub — это оно!
Вообще, выражаясь русским языком эта инструкция означает, что мы что-то вычитаем из переменной m_m_LivesII. Нам же нужно, чтобы данная переменная всегда была равна единице. Этого очень легко добиться. Давайте изменим инструкцию isub на nop. Щёлкаем правой кнопкой мыши и выбираем Edit Instructions. Тут даже скринов не надо. В итоге инструкции будут выглядеть так:



Закинем наш уже неоригинальный класс с заменой старого и попытаемся открыть нашу игру, но вместо того, чтобы в неё поиграть мы получим вот такое сообщение об ошибке:



Разбор полётов

Из данного сообщения мы можем с вами понять, что в классе содержится ошибка. Да, кэп!
Но какая? Всё дело в том, что nop в данном случае вызовет дисбаланс желудка стека и, чтобы нам решить проблему нам также нужно заnopить и инструкцию iconst_1. Почему? Это я вам оставлю в качестве домашнего задания. Эта инструкция находится на расстоянии вытянутой руки сразу же после нашего nop. Кстати, вот и она:



Собственно, делаем то, что должны были сделать с ней ещё вчера. Вуаля!



Теперь пересоберём нашу игру. И наконец в неё поиграем.




Теперь же, когда мы упустим наш шарик за пределы платформы, мы всегда будем видеть:



Нам не страшен серый волк. Теперь можно смело играть в игру до потери пульса (хотя она наскучит вам после минуты, т.к. там всего два уровня) и не бояться смерти!

Заключение


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

До скорых встреч!

P.S. При проведении данного опыта ни один арканоид не пострадал!
+2
11 января 2012, 16:44
11
krovatti 97,5

комментарии (13)

+5
simbiod #
Не все скриншоты одинаково полезны… (с)
0
krovatti #
И то верно. Но лучше с ними.
+2
milky_cookie #
спасибо, интересная тема!
0
krovatti #
Всегда пожалуйста. Рад, что интересно! :)
–1
Pe4enie #
К чему геморрой с изменением байт-кода, когда все исходники не обфусцированы и лежат перед тобой?

jar cvfm arcanoid.jar manifest.mf *.class *.png
+1
Prototik #
99% декомпиляторов на всем сложнее хелло ворлда начинают выдавать такую муть… И таки большинство приложений, которые придется вскрывать, будут обфусицированны.
0
krovatti #
Совершенно верно. Например, относительно недавно, сидев в J2ME приложении ДругВокруг (не сочтите за рекламу), заметил, что не проигрывается звук, когда приходит сообщение. Открыл — исправил. Дело сделано. :)
0
krovatti #
О папочке src забываем и работаем ручками. ;)
0
krovatti #
Название этого топика как бы подуразумевает, что в оригинальные исходники вы смотреть не будете. Иначе, зачем вы её вообще читали?
0
krovatti #
Промахнулся сначала.

Название этого топика как бы подуразумевает, что в оригинальные исходники вы смотреть не будете. Иначе, зачем вы её вообще читали?
0
Pe4enie #
Так если подразумевает и изменили бы байт-код чего-нибудь посерьёзнее, чем арканоид, у которого даже исходники не обфусцированы.
0
krovatti #
Согласен, здесь я промахнулся. Но данный опыт я проворачивал давно и вы знаете, взял умышленно это приложение, чтобы мало-мальски начинать вникать. Поэтому поделился опытом в первозданном виде. И по количеству фидбэка от ридонлей понял, что не зря.
0
krovatti #
А, нет, не промахнулся. :)

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