На Хабре уже есть пару упоминаний об инструменте Frida («Frida-node или немножко странного кода», «Точки соприкосновения JavaScript и Reverse Engineering»). В одной статье уже упоминается использование Frida на практике, однако почти везде инструмент используют как фреймворк для реверс-инжиниринга и исследования функционала программ (может даже взлом).
Я же хочу рассказать о процессе превращения одной любимой для меня однопользовательской игрушки в полноценную, многопользовательскую.
Сразу хочу предупредить: в подобном процессе я почти новичок, поэтому не удивлюсь, если в меня полетят гнилые помидоры от гуру системного программирования. С другой стороны я надеюсь, что моя статья позволит начать использовать Frida (и не только) другим новичкам, а из гневных комментариев гуру я почерпну что-то полезное для себя. Также продолжать написание статей (при положительных оценках конечно) я буду прямо в процессе разработки мультиплеера.
Дано:
Node.Js + Frida + frida-node
Игра Street Legal Racing: Redline
SLRR: java pack
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);
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);
});
})
}
//Глобальный объект расположения в пространстве
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];
}
}
//Сохраняем в память строку (путь в никуда)
var dummy = Memory.allocAnsiString("\\\\nothing\\dev\\null");
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) {
}
});
public class MultiplayerSocket {
int connected = 0; //на потом
File dtm;
public void MultiplayerSocket(){
dtm = new File();
}
public int send(String type, String msg) {
//Открываем «никакой» файл и формируем датаграмму, которую распарсим потом
dtm = new File("&nofolder\\DTM^" + 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();
}
К сожалению, не доступен сервер mySQL