しかし、すべてがそれほど悪くはありません。 スクリプトサポートが無効(HXCPP_SCRIPTABLE)であっても、メソッドとフィールドの名前を含む行がファイル内にあります。 このボールを解く方法を分析し、メソッドの名前を仮想メソッドのテーブルのアドレスとオフセットと比較します。
理論のビット
C ++での変換後、すべてのhaXeクラスはhx.Object( hxcpp / include / hx / Object.hで定義されています)から継承されます。 特に興味深いのは次の方法です。
virtual Dynamic __Field(const String &inString, hx::PropertyAccess inCallProp); virtual Dynamic __SetField(const String &inField,const Dynamic &inValue, hx::PropertyAccess inCallProp);
これらのメソッドはC ++で翻訳されたクラスでオーバーライドされ、その実装はどこでも次のようになります。
src / openfl / geom / Matrix.cpp
Dynamic Matrix_obj::__Field(const ::String &inName,hx::PropertyAccess inCallProp) { switch(inName.length) { case 1: if (HX_FIELD_EQ(inName,"a") ) { return a; } if (HX_FIELD_EQ(inName,"b") ) { return b; } if (HX_FIELD_EQ(inName,"c") ) { return c; } if (HX_FIELD_EQ(inName,"d") ) { return d; } break; case 2: if (HX_FIELD_EQ(inName,"tx") ) { return tx; } if (HX_FIELD_EQ(inName,"ty") ) { return ty; } break; case 5: if (HX_FIELD_EQ(inName,"clone") ) { return clone_dyn(); } if (HX_FIELD_EQ(inName,"scale") ) { return scale_dyn(); } if (HX_FIELD_EQ(inName,"setTo") ) { return setTo_dyn(); } break; case 6: if (HX_FIELD_EQ(inName,"concat") ) { return concat_dyn(); } if (HX_FIELD_EQ(inName,"equals") ) { return equals_dyn(); } if (HX_FIELD_EQ(inName,"invert") ) { return invert_dyn(); } if (HX_FIELD_EQ(inName,"rotate") ) { return rotate_dyn(); } break; case 7: if (HX_FIELD_EQ(inName,"__array") ) { return __array; } if (HX_FIELD_EQ(inName,"toArray") ) { return toArray_dyn(); } break; case 8: if (HX_FIELD_EQ(inName,"copyFrom") ) { return copyFrom_dyn(); } if (HX_FIELD_EQ(inName,"identity") ) { return identity_dyn(); } if (HX_FIELD_EQ(inName,"toString") ) { return toString_dyn(); } break; case 9: if (HX_FIELD_EQ(inName,"copyRowTo") ) { return copyRowTo_dyn(); } if (HX_FIELD_EQ(inName,"createBox") ) { return createBox_dyn(); } if (HX_FIELD_EQ(inName,"translate") ) { return translate_dyn(); } break; case 10: if (HX_FIELD_EQ(inName,"to3DString") ) { return to3DString_dyn(); } break; case 11: if (HX_FIELD_EQ(inName,"copyRowFrom") ) { return copyRowFrom_dyn(); } if (HX_FIELD_EQ(inName,"setRotation") ) { return setRotation_dyn(); } if (HX_FIELD_EQ(inName,"toMozString") ) { return toMozString_dyn(); } if (HX_FIELD_EQ(inName,"__toMatrix3") ) { return __toMatrix3_dyn(); } break; case 12: if (HX_FIELD_EQ(inName,"copyColumnTo") ) { return copyColumnTo_dyn(); } if (HX_FIELD_EQ(inName,"__transformX") ) { return __transformX_dyn(); } if (HX_FIELD_EQ(inName,"__transformY") ) { return __transformY_dyn(); } break; case 13: if (HX_FIELD_EQ(inName,"__cleanValues") ) { return __cleanValues_dyn(); } break; case 14: if (HX_FIELD_EQ(inName,"copyColumnFrom") ) { return copyColumnFrom_dyn(); } if (HX_FIELD_EQ(inName,"transformPoint") ) { return transformPoint_dyn(); } break; case 16: if (HX_FIELD_EQ(inName,"__transformPoint") ) { return __transformPoint_dyn(); } break; case 17: if (HX_FIELD_EQ(inName,"createGradientBox") ) { return createGradientBox_dyn(); } break; case 19: if (HX_FIELD_EQ(inName,"deltaTransformPoint") ) { return deltaTransformPoint_dyn(); } if (HX_FIELD_EQ(inName,"__transformInverseX") ) { return __transformInverseX_dyn(); } if (HX_FIELD_EQ(inName,"__transformInverseY") ) { return __transformInverseY_dyn(); } break; case 22: if (HX_FIELD_EQ(inName,"__translateTransformed") ) { return __translateTransformed_dyn(); } break; case 23: if (HX_FIELD_EQ(inName,"__transformInversePoint") ) { return __transformInversePoint_dyn(); } } return super::__Field(inName,inCallProp); }
ご覧のとおり、通常はフィールドと見なされるものだけでなく、動的ラッパーのメソッドも抽出する必要がありますが、メソッドもHaKsトランスレーターの理解においてフィールドであると理解されます。
準備する
したがって、__ Fieldメソッドを見つけることから始める価値があります。 たとえば、メソッドの名前を持つ行へのリンクをたどることで、それにアクセスできます。 ファイル内の行を読み取ると、たとえば__ToStringまたはRTTIの後方リンクに到達できます。 これらのうち、戻るリンクに従ってVMTに移動します。 文字列がフィールドの名前である場合、__ Fieldの代わりに、同様の__SetFieldメソッドを使用できますが、メソッドの動的ラッパーへのリンクがないため、あまり適していません。 VMTで、オーバーライドされたメソッド(アドレスによって割り当てられた)を開き、それらのうち__Fieldに似ているものを探します(最初に大きなスイッチがあります)。
__Fieldを開始
.text:010B3DB8 var_30 = -0x30 .text:010B3DB8 var_2C = -0x2C .text:010B3DB8 var_28 = -0x28 .text:010B3DB8 var_20 = -0x20 .text:010B3DB8 .text:010B3DB8 PUSH.W {R4-R9,LR} .text:010B3DBC SUB SP, SP, #0x14 .text:010B3DBE MOV R7, R2 .text:010B3DC0 MOV R4, R0 .text:010B3DC2 LDR R0, [R7] .text:010B3DC4 MOV R9, R3 .text:010B3DC6 MOV R5, R1 .text:010B3DC8 SUBS R0, #4 ; switch 28 cases .text:010B3DCA CMP R0, #0x1B .text:010B3DCC BHI.W def_10B3DD0 ; jumptable 010B3DD0 default case .text:010B3DD0 TBH.W [PC,R0,LSL#1] ; switch jump .text:010B3DD0 ; --------------------------------------------------------------------------- .text:010B3DD4 jpt_10B3DD0 DCW 0x1C ; jump table for switch statement .text:010B3DD6 DCW 0x35
__SetFieldを開始
.text:010B48DC var_38 = -0x38 .text:010B48DC var_30 = -0x30 .text:010B48DC var_28 = -0x28 .text:010B48DC var_24 = -0x24 .text:010B48DC var_20 = -0x20 .text:010B48DC arg_0 = 0 .text:010B48DC .text:010B48DC PUSH.W {R4-R9,LR} .text:010B48E0 SUB SP, SP, #0x1C .text:010B48E2 MOV R7, R2 .text:010B48E4 MOV R8, R0 .text:010B48E6 LDR R0, [R7] .text:010B48E8 MOV R6, R3 .text:010B48EA LDR R5, [SP,#0x38+arg_0] .text:010B48EC MOV R9, R1 .text:010B48EE SUBS R0, #6 ; switch 13 cases .text:010B48F0 CMP R0, #0xC .text:010B48F2 BHI.W def_10B48F6 ; jumptable 010B48F6 default case .text:010B48F6 TBH.W [PC,R0,LSL#1] ; switch jump .text:010B48F6 ; --------------------------------------------------------------------------- .text:010B48FA jpt_10B48F6 DCW 0xD ; DATA XREF: .text:01329970↓o .text:010B48FA ; jump table for switch statement .text:010B48FC DCW 0x25
仮想メソッドテーブルの__Fieldは__SetFieldよりも前にあり、通常はオプションが少なくなっています。 この例では、13対28です。
最初の段階:動的ラッパーを探しています
両方のメソッドが見つかったら、__ Fieldに移動し、0 == memcmpの後の分岐の場所を見て、ラッパーに名前を付ける必要があります。 この場合、通常のフィールドとラッパーの両方に遭遇する可能性があります。 それらを区別する方法を学ぶのは簡単です、ここでは通常のフィールドの例、そしてメソッドの動的ラッパーです:
.text:010B44B0 loc_10B44B0 ; CODE XREF: __Field+16A↑j .text:010B44B0 LDR R0, [R5,#0x20] .text:010B44B2 B loc_10B4582 .text:010B44B4 ; --------------------------------------------------------------------------- .text:010B44B4 .text:010B44B4 loc_10B44B4 ; CODE XREF: __Field+1B0↑j .text:010B44B4 LDR R2, =(get_error_dyn+1 - 0x10B44BA) .text:010B44B6 ADD R2, PC ; get_error_dyn .text:010B44B8 B loc_10B44D2
このファイルにはないが、ラッパーへのポインターが認識されないという問題がありました。 オレンジ色の異常に大きな整数オペランドのように見えます。 Ctrl + Rを使用して、IDAでオフセットにする必要があります。
第二段階:最も単純なケース
まず、C ++での翻訳後、メソッドとラッパーがどの程度配置されているかを見てみましょう。
src / openfl / geom / Matrix.cpp
// … ::lime::math::Matrix3 Matrix_obj::__toMatrix3( ){ HX_STACK_FRAME("openfl.geom.Matrix","__toMatrix3",0xaf6ed17e,"openfl.geom.Matrix.__toMatrix3","openfl/geom/Matrix.hx",480,0xa0d54189) HX_STACK_THIS(this) HX_STACK_LINE(482) Float tmp = this->a; HX_STACK_VAR(tmp,"tmp"); HX_STACK_LINE(482) Float tmp1 = this->b; HX_STACK_VAR(tmp1,"tmp1"); HX_STACK_LINE(482) Float tmp2 = this->c; HX_STACK_VAR(tmp2,"tmp2"); HX_STACK_LINE(482) Float tmp3 = this->d; HX_STACK_VAR(tmp3,"tmp3"); HX_STACK_LINE(482) Float tmp4 = this->tx; HX_STACK_VAR(tmp4,"tmp4"); HX_STACK_LINE(482) Float tmp5 = this->ty; HX_STACK_VAR(tmp5,"tmp5"); HX_STACK_LINE(482) ::lime::math::Matrix3 tmp6 = ::lime::math::Matrix3_obj::__new(tmp,tmp1,tmp2,tmp3,tmp4,tmp5); HX_STACK_VAR(tmp6,"tmp6"); HX_STACK_LINE(482) return tmp6; } HX_DEFINE_DYNAMIC_FUNC0(Matrix_obj,__toMatrix3,return ) Void Matrix_obj::__transformInversePoint( ::openfl::geom::Point point){ { HX_STACK_FRAME("openfl.geom.Matrix","__transformInversePoint",0xde42fb73,"openfl.geom.Matrix.__transformInversePoint","openfl/geom/Matrix.hx",487,0xa0d54189) // …
メソッドの本体が最初に移動し、次にマクロで動的ラッパーが構築され、次に次のメソッド、その動的ラッパーなどが構築されることがわかります。 ラッパーには最初の段階で名前が付けられていますが、メソッド自体はまだではないため、名前付きルーチンに名前付きルーチンが散在している場合、IDAのルーチンのリストに「縞模様」の画像が表示されます。
これは完全に真実ではありませんが、この段階で処理する必要があるのは最も明白なケースのみです。動的ラッパー間にサブルーチンが1つだけあり、これがメソッドである可能性が高い場合です。 彼は、彼より低いラッパーから名前を与えられます。
注意 :IDAがメソッドの本体をサブルーチンとして認識しなかったが、メソッドの後に来る補助的なものを認識した場合、このファイルにはありませんでした。 このメソッドは、VMTからバックトラックされています。
第3段階:ラッパー間に2つのサブプログラムがある場合
動的ラッパーは、次のようなマクロで作成されます 。
#define HX_DEFINE_DYNAMIC_FUNC0(class,func,ret) \ ::Dynamic __##class##func(hx::Object *inObj) \ { \ ret reinterpret_cast<class *>(inObj)->func(); return ::Dynamic(); \ }; \ ::Dynamic class::func##_dyn() \ {\ return hx::CreateMemberFunction0(this,__##class##func); \ }
ご覧のとおり、ここでは、型付きと型なしの2つのラッパーが一度に作成されますが、通常、型付きはC ++トランスレーターによって不必要にスローされます。 動的ラッパーの間に一度に2つの名前のないサブプログラムがある場合、最初のメソッドが目的のメソッドであり、2番目が型付きラッパーである可能性があります。
3番目の段階の開始までに、ほとんどのメソッドにはすでに名前が付けられているはずです。したがって、VMTから見ると、これらは単一のスペースになり、この段階で削除されます。
第4段階:VMTの大きなギャップを埋める
2つ以上の方法で、VMTに大きなギャップがある場合があります。 繰り返しになりますが、VMTから見ると便利であることがわかります。 したがって、__ Fieldトラバーサル中に1つのメソッドを見逃すと、IDAルーチンのリストにある動的ラッパー間の3つの名前のないサブルーチンのように見えますが、ハックは他のニーズに合わせて追加のルーチンを生成でき、動的ラッパー間の3つの名前のないサブルーチンも取得できます。
VMTから、次のことがわかります。2つの要素のスペースがある場合、これは__Fieldで欠落している動的ラッパーです。 このギャップがあるルーチンのリストで、中間のルーチンに移動すると、ラッパーになります。 Xを使用して、バックリンクのリストを開きます。その中に__Fieldがあります。 そこに行き、ラッパーの名前を見つけ、サブプログラムのリスト内のスペースをストリップで「ドラッグ」し、次に説明したアルゴリズムに従ってメソッドの名前を配置します。
Hx.Objectのメソッド
完全を期すために、 hxcpp / include / hx / Object.hを開き、すべての仮想メソッドを順番に書き出して、VMTの先頭でメソッドを特定できます。
フィールドと引数のデータ型の定義
メソッド(すべての仮想メソッドなど)がフィールドと引数で呼び出される場合、どのVMTでそれらを検索するかを理解する必要があります。そのためには、一般にどのタイプであるかを理解する必要があります。 デバッガーを実行しない場合、動的ラッパーがこれを支援します。 これらは、入力として正式な型(動的、動的、動的など)の引数を受け取り、呼び出しを行うために、最初に動的をメソッドが期待する実際の型にキャストします。 この変換中に、まさにこれらの型を認識することができます。
たとえば、ラッパーの本文にある場合:
.text:010B3884 LDR R1, =(off_23DE1D4 - 0x10B388E) .text:010B3886 MOVS R3, #0 .text:010B3888 LDR R2, =(off_23E04A0 - 0x10B3890) .text:010B388A ADD R1, PC ; off_23DE1D4 .text:010B388C ADD R2, PC ; off_23E04A0 .text:010B388E LDR R1, [R1] ; hx_Object_ci .text:010B3890 LDR R2, [R2] ; off_22D9DE0 .text:010B3892 BLX.W __dynamic_cast
...次に、hx.Objectから他の何かへのキャストが行われていることがわかります。 hx_Obejct_ciがまだ特定されていない場合、両方のクラスは不明になりますが、これは解決できます。 ポインタが導くRTTIを調べ(この例ではoff_22D9DE0)、名前を付けて結論を出します。
同様に、__SetFieldはフィールドに便利です。これは、型をDynamicからフィールドの実際の型にキャストすることを強制されるため、ヒントを提供します。
静的フィールドとメソッド
クラスに静的要素がある場合、静的メソッド__GetStaticおよび/または__SetStaticをオーバーライドします。 VMTでは、明らかな理由で、それらは表示されませんが、クラスに静的要素と通常の要素の両方がある場合、翻訳されたコードは__Field、__ GetStatic、__ SetField、__ SetStaticの順序になるため、__ Fieldと__SetFieldを計算できる場所がわかりますおよびそれらの隣の__SetStatic。 また、文字列の長さに沿ってスイッチの先頭にあり、次に比較操作。
スクリーンキャスト
00:00 Find __Fieldおよび__SetField
03:00第1段階:動的ラッパーを探しています
21:30第2段階:最も単純なケース
30:48第3段階:ラッパー間に2つのサブプログラムがある場合
33:15第4段階:VMTの大きなギャップを埋める
49:00 hx.Objectメソッド