フリーダノードまたは奇妙なコード

この記事を読んだすべての人への挨拶。

どういうわけか、ハブにフリーダと呼ばれる素晴らしいものの言及が事実上ないことが起こりました。 それらの中で最も賢明なのは、数行のコードと一般的な説明です( HabraFridaから、実際にこのことの存在について学んだので、著者に感謝します)。

要するに、FridaはGoogle JSエンジン(V8)をターゲットプロセスにインジェクトすることを約束し(もちろん、保護がない場合)、組み込みのjsコードはメモリを操作し、関数呼び出しをインターセプトし、同じ呼び出しを行い、処理することができますその他のわいせつ。

正直に言うと、私は非常に平凡で、主にMMORPGのRunes of Magicに精通しています。これにより、コーディングの方法を学び始め、プログラミングの現在の知識のかなりの部分がつながります。 実際、私はまだいろいろな違いを書くのを楽しんでいます(穴あき、ちなみに、おもちゃ...オブジェクトの描画から始まり、sql-injectで終わる素晴らしいバグはありませんでした)。 だから彼女のために、フリーダでちょっとしたテストコードを書いた。

なぜnode.jsなのか? シンプル 最後に、それは異常なプログラミングのハブです)



私にとって最も難しい部分は、 node-fridaのアセンブリでした 。 なんで? はい、2013年のスタジオを置くのは面倒でした。 この間違いを繰り返さないでください-2015年には、組み立てることはできません。 誰かがそれを必要とするならば、私は後で勝つx64のために組み立てられたアドオンを投稿することができます。



それでは、コードに取り掛かりましょう。 プロジェクト自体はノードの通常のプロジェクトであり、私にとっての起動スクリプトはapp.jsと呼ばれ、それから開始します。



var processName = process.argv[2]; var script="'use strict'; "; [ './views/js/frida/romdata.js', './views/js/frida/romlib.js', './views/js/frida/rompackets.js', './views/js/frida/rom.js' ].forEach(function(elem){ script+=fs.readFileSync(elem, 'utf8'); }); frida.attach(processName).then(function (session) { return session.createScript(script); }).then(function (script) { script.load().then(function () { console.log("script loaded"); }); }).catch(function (err) { console.error(err); });
      
      







このコードでは、次のことが行われます。引数(ノードapp.js Client.exe-> process.argv [2]-> Client.exe)からプロセス名を読み取り、ターゲットプロセスに組み込まれるjsファイルのリストを指定します。 ES6のクラスを使用するには、厳密な使用が1か所で必要でした(必要な機能を実装するために見つけた最も簡単な方法)。

次に、ターゲットプロセスへの接続が呼び出され、jsファイルの内容で構成される最終行がそのプロセスに埋め込まれ、最終スクリプトが初期化されます。



これまでのところ、単純なことですよね?

ちなみに、ここではすべてのコードが表示されているわけではありません-本質を絞っただけです。 元のプロジェクトのコードは、あらゆる種類の異なるテストピースでほとんど台無しにされています。

しかし、十分な歌詞は、先に進みましょう。



クライアント自体は、さまざまな関数を使用して、関数を呼び出し、関数を呼び出し、関数を呼び出します...無駄に脳を殺します。 2つの機能を使用しました。1つは送信、2つは受信(トラフィック暗号化の前)です。

一般に、伝達関数は次のようになります。

SendToLocal(パケットサイズ、(void *)packata);

packetdataは、一般に次のような構造の一種です。



 struct PG_Move_CtoL_PlayerMoveObject { GamePGCommandEnum Command; int GItemID; RolePosStruct Pos; ClientMoveTypeENUM Type; float vX; float vY; float vZ; int AttachObjID; PG_Move_CtoL_PlayerMoveObject() { Command = EM_PG_Move_CtoL_PlayerMoveObject; } };
      
      







これは、一般的に(先を見て)、jsで次のようになります:



 var Move_CtoL_PlayerMoveObject = new MakeStruct(Memory, memalloc, { command: {type: 'int', value: 24}, gitemid: {type: 'int', value: 2045}, x: {type: 'float', value: 1000.0}, y: {type: 'float', value: 1000.0}, z: {type: 'float', value: 1000.0}, dir: {type: 'float', value: 154}, type: {type: 'int', value: 0}, vX: {type: 'float', value: 0.0}, vY: {type: 'float', value: 0.0}, vZ: {type: 'float', value: 0.0}, attachObjID: {type: 'int', value: 0} });
      
      







しかし、それについては後で。



それでは、関数のインターセプトの例を見てみましょう。



  Interceptor.attach(ptr("0x6694E0"), { onEnter: function(args) { var dataptr = ptr(args[2]); var val = Memory.readU32(dataptr); } }
      
      







一般に、すべては非常に簡単です:0x6694E0はインターセプトされた関数のアドレス、onEnterは関数が呼び出されたときのイベント(まだonLeaveがあり、詳細はJS APIを参照)、argsはインターセプトされた関数の引数です。

私の場合、SendToLocal((int)size、(void *)data)関数がインターセプトされることを覚えています。これは、args [2]にデータへのポインターがあることを意味します(ptrはNativePointer(ポインターを操作するための非常に便利なラッパー)に変換します)。このコードは、プラスの場合のようなものと同等です(間違えなければ、どういうわけかプラスを使用できませんでした)。



 void SendToLocal( int Size , void* Data ){ int *dataptr=(int*)Data; int val = dataptr[0]; }
      
      







Move_CtoL_PlayerMoveObjectを少し高く見ると、val == Move_CtoL_PlayerMoveObject.commandであることがわかります。 これをさらに使用して、コマンドID(var packetname = GamePGCommandEnum.enumName(val)でパッケージの名前を取得します。GamePGCommandEnumはC ++列挙型をjsに移植するための自作関数で、GamePGCommandEnum.enumNameはそのidで列挙型の名前を取得します)。



このInterceptor.attachの魅力は、元の関数が呼び出される前に呼び出されることです。つまり、誰も引数を変更したり、受信したポインタによって送信データのメモリを上書きしたりする必要はありません。 たとえば、args [1] = 10101010101の精神で何かを使用すると、関数の動作が非常に不可解になる可能性があります(クライアントクラッシュから始まり、送信データのサイズが変更されたため、パケットを無視して終了します)。



MakeStruct-バイナリデータを構造の類似性に逆シリアル化するために必要な関数に進みましょう。



 function toString(buf, length) { var binary=new Uint8Array(buf); var result=new Uint8Array(length); for(var i=0; i<result.length; i++) result[i]=binary[i]; //console.log(length, result); return String.fromCharCode.apply(null, result); } function fromString(str, length) { var buf = new ArrayBuffer(length); // 2 bytes for each char var bufView = new Uint8Array(buf); for (var i=0, strLen=length; i<strLen; i++) { if(i<str.length) bufView[i] = str.charCodeAt(i); else bufView[i] = 0; } return buf; } var MakeStruct = function(Memory, ptr, options){ var _this=this; _this.offset = 0; _this.struct={}; _this.types={ byte: function(name, size){ _this.struct[name] = { type: 'byte', value: Memory.readS8(ptr.add(_this.offset)), size: 1, offset: _this.offset }; _this.offset+=1; }, short: function(name, size){ _this.struct[name] = { type: 'short', value: Memory.readS16(ptr.add(_this.offset)), size: 2, offset: _this.offset }; _this.offset+=2; }, int: function(name, size){ _this.struct[name] = { type: 'int', value: Memory.readS32(ptr.add(_this.offset)), size: 4, offset: _this.offset }; _this.offset+=4; }, float: function(name, size){ _this.struct[name] = { type: 'float', value: Memory.readFloat(ptr.add(_this.offset)), size: 4, offset: _this.offset }; _this.offset+=4; }, string: function(name, size, typesize){ //console.log(size, typesize); var mem=Memory.readByteArray(ptr.add(_this.offset), typesize); _this.struct[name] = { type: 'string', value: toString(mem, size), size: typesize, offset: _this.offset }; _this.offset+=typesize; }, bytes: function(name, size){ var mem=Memory.readByteArray(ptr.add(_this.offset), size); _this.struct[name] = { type: 'bytes', value: mem, size: size, offset: _this.offset }; _this.offset+=size; } }; _this.update = function(){ for(var key in _this.struct){ if(_this.struct.hasOwnProperty(key)){ var item=_this.struct[key]; switch(item.type){ case 'byte': Memory.writeS8(ptr.add(item.offset), item.value); break; case 'short': Memory.writeS16(ptr.add(item.offset), item.value); break; case 'int': Memory.writeS32(ptr.add(item.offset), item.value); break; case 'float': Memory.writeFloat(ptr.add(item.offset), item.value); break; case 'bytes': Memory.writeByteArray(ptr.add(item.offset), item.value); break; case 'string': Memory.writeByteArray(ptr.add(item.offset), fromString(item.value, item.size)); break; } } } } _this.sizeof = function(){ var s=0; for(var key in _this.struct){ if(_this.struct.hasOwnProperty(key) && _this.struct[key].size){ s+=_this.struct[key].size*1; } } return s; } _this.print = function(){ for(var key in _this.struct){ if(_this.struct.hasOwnProperty(key) && _this.struct[key].size){ console.log(key, _this.struct[key].value); } } console.log(''); } _this.struct['update']=_this.update; _this.struct['sizeof']=_this.sizeof; _this.struct['print']=_this.print; _this.struct['ptr']=ptr; for(var key in options){ if(options.hasOwnProperty(key)){ var elem=options[key]; _this.types[elem.type](key, typeof elem.size==="number" || typeof elem.size==="undefined"?elem.size:_this.struct[elem.size].value, elem.typesize); if(elem.value) _this.struct[key].value=elem.value; } } return _this.struct; };
      
      







toString / fromString関数は、char配列を操作するためのヘルパーです。 残りは、実際には、より便利な作業のためのフリーダからの読み取り/書き込みのラッパーです。 次のように使用されます:



 Interceptor.attach(ptr("0x60CCC0"), { onEnter: function(args) { var dataptr = ptr(args[1]); var val = Memory.readS16(dataptr); var packetname=GamePGCommandEnum.enumName(val); if(packetname=="EM_PG_Move_CtoL_PlayerMoveObject"){ var Move_CtoL_PlayerMoveObject = new MakeStruct(Memory, dataptr, { command: {type: 'int'}, gitemid: {type: 'int'}, x: {type: 'float'}, y: {type: 'float'}, z: {type: 'float'}, dir: {type: 'float'}, type: {type: 'int'}, vX: {type: 'float'}, vY: {type: 'float'}, vZ: {type: 'float'}, attachObjID: {type: 'int'} }); Move_CtoL_PlayerMoveObject.x.value+=100; Move_CtoL_PlayerMoveObject.update(); Move_CtoL_PlayerMoveObject.print(); } } });
      
      







実行中にキャラクターをクールなテレポートにします(x座標は実際の座標と比較して100増加し、サーバーは何が起こったのか困惑します)。



さて、初心者向け-関数呼び出しの例。

FridaのNativeFunctionのラッパーを使用しています。



 var _sendToLocal=new NativeFunction(ptr("0x60CCC0"), 'void', ['int', 'pointer']); var _setpos=new NativeFunction(ptr("0x79AE70"), 'void', ['pointer']); function _Send(obj){ _sendToLocal(obj.sizeof(),obj.ptr); } class Structs{ _gmcommand(ptr){ var memalloc = ptr||Memory.alloc(4096); var v = ""; var Talk_CtoL_GMCommand = new MakeStruct(Memory, memalloc, { command: {type: 'int', value: 154}, gitemid: {type: 'int', value: 0}, contentsize: {type: 'int', value: v.length}, content: {type: 'string', typesize: 512, size: 'contentsize', value: v}, }); return Talk_CtoL_GMCommand; } _moveTest(ptr){ var memalloc = ptr||Memory.alloc(4096); var Move_CtoL_PlayerMoveObject = new MakeStruct(Memory, memalloc, { command: {type: 'int', value: 24}, gitemid: {type: 'int', value: 2045}, x: {type: 'float', value: 1000.0}, y: {type: 'float', value: 1000.0}, z: {type: 'float', value: 1000.0}, dir: {type: 'float', value: 154}, type: {type: 'int', value: 0}, vX: {type: 'float', value: 0.0}, vY: {type: 'float', value: 0.0}, vZ: {type: 'float', value: 0.0}, attachObjID: {type: 'int', value: 0} }); return Move_CtoL_PlayerMoveObject; } }; function _call(name, args, ptr){ var struct = new Structs(); var s=struct[name](ptr); for(var i in args){ s[i].value=args[i]; } s.update(); _Send(s); }
      
      







もう少し詳しく:class-js-prototypesのシュガー。この場合、evalまたは何時でも3時間で何の問題もなく、名前で関数を簡単に取得できます。

_callは次のように呼ばれます。



  var cmd="give 0x31194"; _call('_gmcommand', { contentsize: cmd.length, content: cmd });
      
      







この場合、チャット呼び出し/ gmと同等ですか? 0x31994を与えます(IDで物事を発行するチーム)。



_setposは、クライアントの実際のsemi-um関数への別のバインディングです。これにより、3つの座標を持つ行を引数として使用して、クライアントとサーバーでキャラクターの座標を変更できます。 精神の中で何かを充電する



  var memalloc = Memory.alloc(128); var q=-4050.7; setInterval(function(){ Memory.writeUtf8String(memalloc, q+++", 244.5, -8251.9"); _setpos(memalloc); }, 100);
      
      







せん妄状態のキャラクターを取得します(ちなみに弱いソーセージではありません)。



一般的に、現時点ではフリーダをより楽しく使用して脳を暖めるために使用しましたが、適切なアプローチを使用すると、さまざまなプロセスを研究するための非常に価値のあるツールになります。

ご清聴ありがとうございました。楽しんでいただけましたでしょうか。



そして最後に、材料:



フリーダのウェブサイト

フリーダJS API

フリーダノード

ECMAScript 6のクラス

ウマ



All Articles