逆コンパイラ:よくある質問
まず、トラブルシューティングのページをお読みください。 ほとんどの逆コンパイルの問題に対処する方法を説明しています。以下は、該当しなかった場合です。
逆コンパイラが過度に攻撃的で、揮発性メモリへの参照を最適化する場合があります。
次のような典型的な状況:
- device_ready DCD ? ; VOLATILE!
- MOV R0, =device_ready
- LDR R1, [R0]
- LOOP:
- LDR R2, [R0]
- SUB R2, R1
- BEQ LOOP
下記に逆コンパイルできます。
- while ( 1 )
- ;
逆コンパイラーは、変数がそれ自体では値を変更できないと推定し、r0がループ中の同じ場所を指し続けていることを証明します。
変数を揮発性としてマークする必要があります。現在、逆コンパイラは、メモリがIO、IOPORTS、PORTS、 VOLATILEのいずれかの名前のセグメントに属している場合、メモリが揮発性であると見なされます。大文字と小文字は重要ではありません。
逆コンパイラは、変数が値を変更する可能性があると推定しているため、コードを十分に最適化しない場合があります。例えば次のコードです。
- LDR R1, =off_45934
- MOV R2, #0
- ADD R3, SP, #0x14+var_C
- LDR R1, [R1]
- LDR R1, [R1] ; int
- BL _IOServiceOpen
下記に逆コンパイルできます。
- IOServiceOpen(r0_1, *off_45934, 0)
下記のコードはさらに優れたコードです。
- IOServiceOpen(r0_1, mach_task_self, 0)
なぜなら
- off_45934 DCD _mach_task_self
上記は一定のメモリに存在し、その値を変更することのないポインタです。
次の条件のいずれかが当てはまる場合、逆コンパイラはメモリが一定であると見なします。
- セグメントにはアクセス許可が定義されていますが、書き込み許可はリストにありません。 (セグメント許可を変更するには、“Edit, Segments, Edit Segment"メニュー項目またはset_segm_attr組み込み関数を使用します)
- セグメントタイプはCODEです。
- セグメント名は次のいずれかです(リストは将来変更される可能性があります):. .text, .rdata, .got, .got.plt, .rodata,
__text, __const, __const_coal, __cstring, __cfstring, __literal4, __literal8, __pointers, __nl_symbol_ptr, __la_symbol_ptr,
__objc_catlist, __objc_classlist, __objc_classname, __objc_classrefs, __objc_const, __objc_data, __objc_imageinfo,
__objc_ivar, __objc_methname, __objc_methtype, __objc_protolist, __objc_protorefs, __objc_selrefs, __objc_superrefs,
__message_refs, __cls_refs, __inst_meth, __cat_inst_meth, __cat_cls_meth, __OBJC_RO
デコンパイラーは、次のセグメントへの参照を完全に削除し、それらを定数に置き換えようとします: .got, .got.plt, __pointers, __nl_symbol_ptr, __la_symbol_ptr, __objc_ivar, __message_refs, __cls_refs
volatileまたはconst修飾子 を使用してタイプを指定することにより、個々のアイテムの定数をオーバーライドすることができます。
x86デコンパイラの現在のリリースでは、浮動小数点命令をサポートしています。 すべてが自動的に機能しますが、次の点に注意してください。
- 浮動小数点のサポートには、IDAv5.5以降が必要です。 以前のバージョンには必要な機能がなく、逆コンパイラはインラインアセンブラーステートメントを使用してfpu命令を表します。
- 逆コンパイラは、float、double、long double、および_TBYTEを含むすべての浮動小数点型を認識しています。 sizeof(long double)はsizeof(tbyte)と異なることが多いため、_TBYTEを導入しました。 long doubleのサイズは構成できますが(コンパイラーの設定時に暗黙的に妥当な値に設定されます)、 tbyteのサイズは常に10バイトと等しくなります。
- 整数型から浮動小数点型へのキャスト、およびその逆のキャストは、出力が同じ意味を持っている場合でも、常にリストに表示されます。
- デコンパイラーは 、IDAによって実行されるシンプレックス法と同様のfpuスタック分析を 実行します。 失敗した場合、逆コンパイラはインラインアセンブラステートメントを使用してfpu命令を表します。 この場合、逆コンパイラは、スタックポインター値の横に、逆アセンブリリストともう1つのプレフィックス列を追加します。 この列は、fpuスタックの計算された状態を示し、fpuスタックトレースがどこで失敗したかを正確に判断するのに役立ちます。
- 逆コンパイラは、浮動小数点制御ワードによるすべての操作を無視します。 実際には、これは異常な丸めモードを見逃す可能性があることを意味します。 将来、この問題を処理するための堅牢な方法が見つかったらすぐに対処します。
- SSE浮動小数点命令は、 組み込み関数で表されます。 ただし、スカラーSSE命令は、擬似コードの浮動小数点演算に直接マップされます。
- Send databaseコマンドを使用して、浮動小数点サポートに関するすべてのエラーと問題を自由に報告してください。 これは、逆コンパイラを改善し、より堅牢にするのに役立ちます。
逆コンパイラの現在のリリースは、組み込み関数をサポートしています。 高水準言語に直接マッピングできない命令は、多くの場合、特別な組み込み関数で表すことができます 。 一部の例外を除いて、SSE4aまでのすべてのMicrosoftおよびIntelの単純な組み込み機能がサポートされています。 すべてが自動的に機能しますが、次の点に注意してください。
- SSE組み込み関数には、IDAv5.6以降が必要です。古いバージョンのIDAには、必要な機能とレジスタ定義がありません。
- 一部の組み込み関数は、XMM定数値(16バイト長)で機能します。 最新のコンパイラはまだ16バイトの定数を受け入れることが出来ませんが、逆コンパイラは必要に応じてそれらを生成する場合があります。
- 組み込み関数ではなく、インラインアセンブリを使用してSSEコードを表す方がよい場合があります。 逆コンパイラが現在の関数でSSE命令を検出すると、ポップアップメニューにもう1つの項目が追加されます。 この項目により、ユーザーはデータベース全体のSSE組み込み関数を有効または無効にできます。 この設定はデータベースに記憶されています。また、新しいデータベースの構成ファイルで変更することもできます。
- 逆コンパイラは、すべてのMMX / XMM組み込み型について認識しています。 現在のデータベースでこれらのタイプが定義されていない場合、SSE命令が逆コンパイルされるとすぐに、ローカルタイプに自動的に追加されます。
- スカラーSSE命令は、組み込み関数に変換されることはありません。代わりに、浮動小数点演算に直接マップされます。 これは通常、(特にMac OS Xバイナリの場合)はるかに優れた出力を生成します。
- 単純な浮動小数点演算(sqrtssなど)にマップできないスカラーSSE命令は、math.hから単純な関数にマップされます。
- 逆コンパイラは、MicrosoftおよびIntelによって定義された組み込み関数名を使用します。
- 逆コンパイラは、x87およびmmxレジスターの状態を追跡しません。 コンパイラが生成したコードは、x87レジスターとmmxレジスターの間の遷移を正しく処理すると想定されています。
- 一部の組み込み関数は、プロトタイプのためにサポートされていません。 たとえば、 __ cpuid(int a [4]、int b)関数は、4つの整数の配列を必要とするため、処理されません。 ほとんどのcpuid命令は配列なしで使用されると想定しているため、このような組み込み関数を追加すると、 コードが読みやすくなるのではなく、わかりにくくなります。
- Send databaseコマンドを使用して、組み込み関数に関するすべての異常と問題を自由に報告してください。 これは、逆コンパイラを改善し、より堅牢にするのに役立ちます。
場合によっては、変数の割り当てが失敗したために、逆コンパイラが適切な出力を生成できないことがあります。
これは、入力に重複する変数が含まれているために発生します
(または、逆コンパイラが誤ってメモリの読み取りと書き込みをひとまとめにします)。
重複する変数は赤で表示されるため、目立つように表示されます。いくつかの典型的な状況を考えてみましょう。
2つ以上の変数を含む読み取り/書き込みアクセスがある場合
たとえば、次の出力について考えてみます。
v1への最後の割り当ては、v1の境界を超えて読み取ります。実際、v2も読み取ります 。アセンブリコードを参照してください。
残念ながら、逆コンパイラはこのケースを処理できず、重複した変数を報告します。
配列関数の引数がある場合
配列を値で関数に渡すことはできないため、警告が出ます。配列を取り除くのみです。 (たとえば、構造体タイプに埋め込みます)
関数の引数が多すぎる場合
デコンパイラーは、最大64個の関数引数を処理できます。引数の数が多い関数に遭遇する可能性はほとんどありません。 もしそうなら、それらのいくつかを値によって渡される構造に埋め込むだけです。
是正措置には以下が含まれます。
- スタック変数を確認し、必要に応じて修正してください。変数が間違っていると、lvarの割り当てに失敗する可能性があります。
- スタックフレーム全体またはその一部をカバーする大きな構造を定義します。 このような大きな変数は、基本的に変数の一括処理をオフにします (コンパイラの専門用語に精通している場合、逆コンパイラはlvarの割り当て中にlvarのWebを構築し、 一部のWeb要素が大きくなりすぎるため、変数の割り当てが失敗します)。 代わりに、すべての参照は構造フィールドを使用して行われます。
- スタックフレームの関数の引数領域を確認し、間違った変数を修正してください。 たとえば、この領域には配列を含めることはできません(Cでは配列を値で渡すことはできません)。 構造体を値で渡すことは問題ありません。逆コンパイラはそれを受け入れることが出来ます。
逆コンパイラはCONTAINING_RECORDマクロを認識しており、それを出力で使用しようとします。 ただし、ほとんどの場合、含まれているレコードに関する情報が利用できないため、このマクロを自動的に作成することはできません。 逆コンパイラは、次の3つの情報ソースを使用して、CONTAINING_RECORDを使用する必要があるかどうかを判断します。
1.このような割り当てがある場合:
- v1 = (structype *)((char *)v2 - num);
下記に変換することができます。
- v1 = CONTAINING_RECORD(v2, structype, fieldname);
v1とv2のタイプを確認するだけです。
注:変数タイプは明示的に指定する必要があります。
タイプが正しいと表示されている場合でも、ユーザーはYキーを押してからEnterキーを押して、変数タイプを確認する必要があります。
2.逆アセンブリリストの数値に適用される構造体オフセットは、CONTAINING_RECORDを作成するためのヒントとして使用されます。 たとえば、構造体オフセットを0x41Cに適用すると
- sub eax、41Ch
前のポイントと同じ効果があります。前に説明したように、変数タイプを確認することは理にかなっています。
3. 逆コンパイラ出力の数値に適用される構造体オフセット。 たとえば、次のコードで_DEVICE_INFO構造体オフセットを-131に適用します。
- deviceInfo = (_DEVICE_INFO *)((char *)&thisEntry[-131] - 4);
次のように変換します:
- deviceInfo = CONTAINING_RECORD(thisEntry, _DEVICE_INFO, ListEntry);
前に説明したように、変数タイプを確認することは理にかなっています。
ほとんどの場合、シフトされたポインターが使用されている場合、CONTAING_RECORDマクロはより短くより適切な式に置き換えることができます。 この場合、ポインターをシフトポインターとして宣言するだけで十分であり、逆コンパイラはそれが使用されるすべての式を変換します。
間接呼び出しの引数は変数を定義する前に収集されるため、 関数ポインターを保持する変数の型を指定するだけでは不十分な場合があります。 この場合、ユーザーは他の方法を使用して関数タイプを指定する必要があります。次の方法があります(優先順に)。
1.次の形式の間接呼び出しの場合:
- call ds:funcptr
funcptrが静的に初期化され、有効な関数を指している場合は、正しい関数プロトタイプを確認してください。 逆コンパイラはそれを使用します。
2.次の形式の間接呼び出しの場合:
- call [reg+offset]
regが関数ポインタであるメンバーを持つ構造体を指している場合は、オペランドを構造体オフセットに変換するだけです(ホットキーT)。
- call [reg+mystruct.funcptr]
mystruct :: funcptrの型が目的の型の関数へのポインタであることを確認してください。
3.Edit, Operand type, Set operand typeの設定を使用して、呼び出される関数のタイプを指定します。 最初の2つの方法を適用できない場合は、これが推奨される方法です。オペランドタイプの優先度が最も高く、よく使用されます。
4.呼び出された関数のアドレスがわかっている場合は、Edit, Plugins, Change the callee address(ホットキーAlt-F11)を使用します。 逆コンパイラは、指定された呼び出し先のタイプを使用します。このメソッドは、x86でのみ使用できます。 他のプロセッサの場合、コール命令からコール先にコード相互参照を追加すると役立ちます。