Node.JSとFridaを使用して、マルチプレイヤーをゲームに追加します。 パート1





Habréには、Fridaツールについての言及が既にいくつかあります( 「Frida-nodeまたは少し奇妙なコード」「JavaScriptとリバースエンジニアリングのタッチポイント」 )。 ある記事ではすでにFridaを実際に使用することについて言及していますが、このツールはプログラムの機能をリバースエンジニアリングおよび調査するためのフレームワークとして使用されています(ハックも可能)。



私のお気に入りのシングルプレイヤー玩具の1つを本格的なマルチユーザーに変えるプロセスについてお話したいと思います。



すぐに警告したいと思います。そのようなプロセスでは、私はほとんど初心者なので、システムプログラミングの第一人者から腐ったトマトが飛び込んでも驚かないでしょう。 一方で、私の記事が他の初心者がフリーダを使い始めることを許可することを望みます(そしてそれだけでなく)、グルの怒りのコメントから私は自分にとって有益なものを描きます。 また、記事を書き続けます(もちろん肯定的な評価があります)。私はマルチプレイヤーの開発の過程で正しいでしょう。



与えられた:

Node.Js + Frida + frida-node

ストリートリーガルレーシング:レッドライン

SLRR:Javaパック



Fridaをインストールする



frida-nodeの最初のバージョンがリリースされてから、多くの時間が経過しました。 モジュールにはFridaバイナリが含まれているため、次の手順で簡単にインストールをセットアップできます。

  1. Node.Jsのダウンロードとインストール(記事5.4.1の執筆時点)
  2. インストールするときは、npmインストールチェックマークを忘れずに
  3. 必要な場所(プロジェクト用)に目的の名前のディレクトリを作成し、コンソールを実行して、 npm install frida-nodeと入力します

  4. インストールが成功したことを願っています。


ゲームの最新情報







ゲームの小さな物語。



ゲーム自体はActivisionによって2003年にリリースされました。 このゲームは、ハンガリーの企業Invictus Gamesによって開発されました。 ゲーム、貧弱なマーケティングによる屋根ふきのフェルト、観客の利益との明確な矛盾による屋根ふきのフェルトが離陸しなかったことがたまたまありました。 それにもかかわらず、ゲームはいくつかのファンのコミュニティを形成しています。

ゲームプレイ:車は、ほぼ完全に分解および組み立て、変更および調整された部品、非常に現実的な(当時の)物理学、身体の変形、および道路での動作を行うことができます。 コミュニティは数回、請願書に署名してInvictusに送信することで、ゲームのソースコードを取得しようとしました。 開発者は、権利と現在権利を所有しているActivisionに問題があるとして、それらの転送を拒否しました。 それにもかかわらず、ゲームのJavaコードの一部がネットワークに流れ込むことがどういうわけか起こりました。 JVMの一部の古いバージョン(Java仮想マシン、スローキャッチサポートなしでも)はゲーム自体に組み込まれています。ネットワークを操作する機能はなく、ファイルはゲームに組み込まれた形式でのみ保存および開かれます。



現時点では、同名のRAXATというニックネームを持つ同胞が非公式パッチ2.3.0LEをリリースしました。

このゲームの標準。 このパッチに基づいて、ゲームにマルチプレイヤーサポートを追加することにしました。



失敗した試行と成功した試行









私は、ゲームにマルチプレイヤーを実装したいと考えたコミュニティの最初の人ではないことにすぐに注目したいと思います。 また、ゲームに必要な機能を追加しようといくつか試みました。 私はもともと、Delphiで記述されたdllを使用してこれを行いました。これはプロセスに挿入されました。 この方法は機能しましたが、モジュールに何かを追加するのはかなり時間がかかりました。



Fridaツールが助けとなり、V8エンジンをJavaScriptプロセスに埋め込み、内部からプロセスを操作できるようになりました。 最初の最も重要なタスクは、ゲームの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);                                              });                       }) }
      
      







埋め込みスクリプトからの「ペイロード」の場合、独自の方法で処理する必要があります。 これを行うために、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. フリダに正しいものを探すように教えられなかった

    特定のアプリケーションをさらに使用するためのアプリケーションのメモリ内のテキスト

    メモリの領域。
  3. 動作します。


重要なポイント:このスクリプトでは、変数の作成とメモリの割り当てを注意深く監視する必要があります。 作成される変数が多すぎる場合、ある時点でガベージコレクターが起動し、プロセスがしばらくの間ハングします。 また、メモリを絶えず割り当てると、GCでさえ対処できないリークが発生します。



 //    (  ) 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) {           } });
      
      







おそらくこの段階では、多くの人が私が聞いてうれしいコメントを持っているでしょう。



少しのJava



ゲームではJavaの簡易バージョンが使用されるため、この素晴らしい言語で少し書く必要があります。



興味深い点:ゲーム内に直接、JavaコンパイラがJVMのバイトコードに組み込まれ、javaファイルをsrcフォルダー内の適切なディレクトリに置くだけで、ゲームが開始されるとクラスファイルが作成されます。



テストでは、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("&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();           }
      
      







次は?



ゲーム内でデータを転送することを学ぶ必要があります。 これまでのところ、Fridaで記憶に必要な行を見つけることはできませんでした(ArtMoneyのように、手は金色ですが、そこから成長しません)。 方法を教えてくれる人がいれば素晴らしいでしょう。 ファイルを介したデータ交換の実装があり(ゲームには独自の形式があります)、遅く、不安定で、遅れていました。 私の考えでは、関数の戻り値の置換を使用してデータを送信しようとします(ただし、理想的にはメモリを介して必要です)。 また、ゲーム自体への統合に関しては、多くの作業が残っています。 以下の記事の内容は、プロジェクトの進行状況によって異なります。



All Articles