Информационный портал по безопасности » Программирование » Добавляем в игру мультиплеер с помощью Node.JS и Frida. Часть 1

 

Добавляем в игру мультиплеер с помощью Node.JS и Frida. Часть 1

Автор: admin от 16-01-2016, 19:42, посмотрело: 499

Добавляем в игру мультиплеер с помощью Node.JS и Frida. Часть 1

На хабре уже есть пару упоминаний об инструменте Frida(Frida-node или немножко странного кода, Точки соприкосновения javascript и Reverse Engineering).
В одной статье уже упоминается использование Frida на практике, однако почти везде инструмент используют как фреймворк для реверс-инжиниринга и исследования функционала программ (может даже взлом).
 
Я же хочу рассказать о процессе превращения одной любимой для меня однопользовательской игрушки в полноценную, многопользовательскую.
 
Сразу хочу предупредить: в подобном процессе я почти новичок, поэтому не удивлюсь, если в меня полетят гнилые помидоры от гуру системного программирования. С другой стороны я надеюсь, что моя статья позволит начать использовать Frida (и не только) другим новичкам, а из гневных комментариев гуру я почерпну что-то полезное для себя. Также продолжать написание статей (при положительных оценках конечно) я буду прямо в процессе разработки мультиплеера.
 
Дано:
Node.Js + Frida + frida-node
Игра Street Legal Racing: Redline
SLRR: java pack
 

Установка Frida


С момента выпуска первой версии frida-node прошло много времени. Модуль обзавелся своими бинарниками Frida, поэтому установку можно просто расписать по шагам:

  • Скачиваем и устанавливаем Node.Js (на момент написания статьи 5.4.1)

  • При установке не забываем ткнуть в галочку установки npm

  • Создаем в нужном нам месте директорию с нужным именем (для проекта), запускаем в ней консоль и вводим npm install frida-node

  • Надеюсь, установка прошла удачно.


  •  

    Что с игрой


    Добавляем в игру мультиплеер с помощью Node.JS и Frida. Часть 1

    Небольшая история игры.
    Сама игра была выпущена в 2003 году компанией Activision. Игру разработала Венгерская компания Invictus Games. Случилось так, что игра, толи из-за плохого маркетинга, толи из за явного несоответствия интересам аудитории, не взлетела. Несмотря на это, у игры образовалось несколько сообществ фанатов, скорее всего из-за особенностей
    геймплея: Автомобиль можно практически полностью разбирать и собирать, менять и настраивать детали, очень реалистичная (на тот момент) физика, деформация кузова, и поведение на дороге. Несколько раз сообщество пыталось заполучить исходные коды игры, подписывая петиции и отправляя их в Invictus. Разработчики отказывались их передавать, ссылаясь на проблемы с правами и компанией Activision, которой в данный момент принадлежат права. Несмотря на это, как-то случилось так, что в сеть утекло часть java кода игры. В саму игру встроена какая-то старая и урезанная версия JVM (Java Virtual Machine, даже без поддержки throw-catch), полностью отсутвуют функции для работы с сетью, а файлы сохраняются и открываются только во встроенном в игру формате.
    На данный момент нашим земляком, с ником RAXAT был выпущен неофициальный патч 2.3.0LE, который стал
    стандартом для этой игры. На базе этого патча я и решил добавить в игру поддержку мультиплеера.
     

    Неудачные и удачные попытки


    Добавляем в игру мультиплеер с помощью Node.JS и Frida. Часть 1

    Сразу хочу заметить, что я не первый из сообщества, кто захотел реализовать мультиплеер в игре. Также я сам сделал несколько попыток добавить нужные функции в игру. Изначально я делал это с использованием dll, написанной на Delphi, которая инжектировалась в процесс. Такой метод работал, но добавлять что-либо в модуль было достаточно трудоемкой задачей.
    На помощь пришел инструмент Frida, который позволяет внедрять в процесс javascript движок V8, и работать с процессом «изнутри». Самой важной задачей по началу являлось добавить обмен данными между JVM игры и внешним процессом Node.js.
     
    Код проекта валяется на Github: https://github.com/lailune/SLRRMultiplayer однако представляет из себя исключительно тестовую версию, и то, что я имею на данный момент.
     
    Ссылку на игру не привожу по понятным причинам.
     
    Скрипт, загружаемый внутрь процесса будет называться injectScript.js. Название самого скрипта приложения не важно, я назвал его app.js
     
    Наш скрипт будет запускать бинарник игры, и передавать pid процесса модулю Frida для внедрения скрипта.
     
    var frida = require('frida');
    var spawn = require('child_process').spawn;
     
    var injectScript = fs.readFileSync('injectScript.js', "utf8");
    var workingDir = 'C:/SLRR/'; //Да простят меня боги за абсолютный путь
     
    //Переходим в директорию игры и запускаем её
    process.chdir(workingDir);
    var gameProcess = spawn(
                workingDir + 'StreetLegal_Redline.exe',
                [], {
                            stdio: 'inherit'
                });
     
    //Передаем id процесса «куда следует»
    AttachHook(gameProcess.pid);
    

     
    Сама функция AttachHook содержит весь код инициализации Frida:

    1. Подключаемся к процессу

    2. Загружаем наш внутренний скрипт во внедренный V8 (в это время V8 проверяет скрипт на ошибки и компилирует его в байт код)

    3. Ставим обработчик сообщений из скрипта

    4. Запускаем скрипт, и выводим сообщение об успехе или ошибке.


     
     
    function AttachHook(pid) {
                frida.attach(pid)
                            .then(function (session) {
                                       return session.createScript(injectScript);
                            })
                            .then(function (script) {
                                       script.events.listen('message',function (message, data) {
                                                   handleMessage(script, message.payload.name, message.payload.data);
                                       });
                                       script.load()
                                                   .then(function () {
                                                               console.log('Hook script injected.');
                                                   })
                                                   .catch(function(error) {
                                                               console.log('Hook Error:', error.message);
                                                   });
                            })
    }
    

     
    В случае прихода какой-либо «посылки» (payload) из нашего внедренного скрипта мы должны его обработать по своему. Для этого есть функция handleMessage, в которой, правда, пока реализован только прием информации о позиции игрока в виртуальном пространстве.
     
    //Глобальный объект расположения в пространстве
    var pos = {x: 0.0, y: 0.0, z: 0.0, sy: 0.0, sp: 0.0, sr: 0.0, angle: 0.0};
     
    function handleMessage(script, type, data) {
                if (type == "POS") {
                            var tmp = data.split(';');
                            pos.x=tmp[0];
                            pos.y=tmp[1];
                            pos.z=tmp[2];
                }
    }
    

     
    Теперь мы можем распоряжаться этими данными так, как нам захочется, например: передать их на сервер.
     

    injectScript.js


     
    Способ передачи данных из игры я выбрал крайне возмутительный: перехватываю вызов CreateFileA.
    Почему:

    1. Так проще всего. Достаточно открыть файл с
      «нужным названием», внутри которого будут данные, которые мы передаем.

    2. Я так и не смог научить Frida искать нужный
      текст в памяти приложения, для дальнейшего использования определенной
      области памяти.

    3. Это работает.


     
    Важный момент: в этом скрипте надо тщательно следить за созданием переменных и выделением памяти. Если будет создано слишком много переменных, то в один прекрасный момент запустится сборщик мусора, и процесс подвиснет на время намертво. Также если постоянно выделять память, получится утечка, с которой даже GC не справится.
     
    //Сохраняем в память строку (путь в никуда)
    var dummy = Memory.allocAnsiString("nothingdevnull");
     
     
    var message="";
     
    //Подключаем перехватчик
    для функции CreateFileA. Аналог Hook 
    Interceptor.attach(Module.findExportByName('kernel32.dll','CreateFileA'), {
                onEnter: function onEnter(args) {
                            //Считываем первый параметр функции
                            message = Memory.readUtf8String(args[0]);
    
                            //Проверяем на наличие константы DTM^ (ничеголучше не придумал)
                            //Данные из игры передаются в формате DTM^payloadname^data
                            if (message.indexOf('DTM^') != -1) {
                                        //Разделяем, убираем лишнее, отправляем «наверх»
                                      message = message.split('^'); 
                                       send({name: message[1], data:message[2]});                    
                            }
     
                },
                onLeave: function onLeave(retval) {
                }
    });
    

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

    Немного Java


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

    Интересный момент: прямо внутрь игры встроен компилятор java в байт код для JVM,  достаточно положить java файл в соответствующую директорию внутрь папки src, и при запуске игры будет создан class файл.
     
    Для теста я использовал класс City (реализует базовые функции для управления городом в игре). В дальнейшем планирую вынести реализацию своего псевдо-сокета в отдельный глобальный класс.
    Пока реализует только передачу данных наружу.
     
     
     
    public class MultiplayerSocket {
                int connected = 0; //на потом
                File dtm;
    
                public void MultiplayerSocket(){
                            dtm = new File();
                }
    
                public int send(String type, String msg) {
                            //Открываем «никакой» файл и формируем датаграмму, которую распарсим потом
                            dtm = new File("&nofolderDTM^" + type + "^" + msg);
                            dtm.open(File.MODE_READ); //открываем файл на чтение (открытие на запись вызовет ошибку с соответствующим сообщением в error.log)
                            dtm.close(); //закрываем. Из-за бага игры это обязательно
                            return 0; //По идее тут должен быть статус отправки
                }
    }
     
    //создаем экземпляр объекта
    MultiplayerSocket MP;
              ….
               public void enter(GameState prev_state ){
                                       //Инициализируем
                                       MP = new MultiplayerSocket();
                                       ….
               }
              ….
     
                //функция для отправки текущего положения машины игрока 
                public void sendPositionDatagram() {
                            if (MP) {
                                       if (player.car && player.car.chassis) {
                                                   Vector3 pos = player.car.getPos(); //Получаем позицию игрока
                                                   MP.send("POS", pos.x + ";" + pos.y + ";" + pos.z); //отправляем наружу
                                       }
                            }
                }
     
                //Вызывается перед рендером каждого кадра (до 30 fps)
                public void frame(){
                            sendPositionDatagram();
                }
    

     

    Что дальше?


     
    Надо научится передавать данные внутрь игры. Находить нужную строку в памяти с Frida пока так и не получилось (а-ля ArtMoney, руки золотые, но растут не оттуда). Будет супер, если найдутся люди, которые подскажут как. Была реализация с обменом данными через файл (а у игры свой формат), было медленно, нестабильно, лагало. В мыслях попробовать отправлять данные через подмену return какой-либо функции (но в идеале нужно через память). Также в плане интеграции в саму игру предстоит много работы. Содержание следующих статей зависит от прогресса по проекту.

    Источник: Хабрахабр

    Категория: Программирование / Game Development

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

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    • bowtiesmilelaughingblushsmileyrelaxedsmirk
      heart_eyeskissing_heartkissing_closed_eyesflushedrelievedsatisfiedgrin
      winkstuck_out_tongue_winking_eyestuck_out_tongue_closed_eyesgrinningkissingstuck_out_tonguesleeping
      worriedfrowninganguishedopen_mouthgrimacingconfusedhushed
      expressionlessunamusedsweat_smilesweatdisappointed_relievedwearypensive
      disappointedconfoundedfearfulcold_sweatperseverecrysob
      joyastonishedscreamtired_faceangryragetriumph
      sleepyyummasksunglassesdizzy_faceimpsmiling_imp
      neutral_faceno_mouthinnocent