ida_medigate IDA Pro 用 C++ プラグイン
[目次]
コンパイルされた C++ コードのリバース エンジニアリングは楽しいものではありません。コンパイルされた C++ コードの静的リバース エンジニアリングはイライラさせられます。これを難しくしている主な理由は、仮想関数です。コンパイルされた C コードとは対照的に、明確なコード フローはありません。コンパイルされた C コードのように関数をただ見るのではなく、次に呼び出される仮想関数が何であるかを理解しようとすると、多くの時間が費やされてしまいます。
仮想関数を調査するとき、その外部参照を見つけるためにどれだけの時間を費やす必要があるかは、意味がありません。
C++ RE プロジェクトが多すぎた後、私は諦めて、この種の研究には柔軟 (Python) で安定した (簡単にメンテナンスできる) ツールが必要だと判断しました。このプラグインの大部分は 2018 年 1 月に作成されましたが、最近、ゴミを取り除き、新しい IDA (7.2) クラスのサポートを追加することにしました。
このプラグインは、「すぐに使える」状態で常に動作することを目的としたものではなく、リバーサーのための別のツールとして機能することを目的としています。
プラグインは 2 つの部分で構成されます。
この最初の部分は 2 番目の部分に依存していないため、プラグインの API に基づいてこれらのクラスを手動で定義することで、プラグインを使用して RTTI を含まないバイナリをリバース エンジニアリングすることができます。
このプラグインをユニークなものにしているのは、研究者がすでに使い慣れている同じ環境を使用し、新しいメニューやオブジェクトを追加せず、既知の IDA ビルディング ブロック (構造体、共用体、構造体のメンバーの型など) に基づいているという事実です。 ) -これにより、プラグインは IDA がサポートするすべてのアーキテクチャの C++ 抽象化をサポートできるようになります。
注: RTTI パーサーは x86/x64 g++ RTTI を解析しますが、その構造により、より多くのアーキテクチャとコンパイラのサポートを簡単に追加できます。
plugins/
の下) medigate_cpp_plugin
plugins
ディレクトリにコピーし、ソース コードのパスをidapythonrc.py
ファイルに追加します。
plugins/ida-referee/referee.py
同じディレクトリにコピーします。
バイナリの元のソース コードが次のとおりであると仮定します ( examples/a.cpp
):
using namespace std ;
class A {
public:
int x_a;
virtual int f_a ()=0;
};
class B : public A {
public:
int x_b;
int f_a (){x_a = 0 ;}
virtual int f_b (){ this -> f_a ();}
};
class Z {
public:
virtual int f_z1 (){cout << " f_z1 " ;}
virtual int f_z2 (){cout << " f_z2 " ;}
};
class C : public B , public Z {
public:
int f_a (){x_a = 5 ;}
int x_c;
int f_c (){x_c = 0 ;}
virtual int f_z1 (){cout << " f_z3 " ;}
};
int main ()
{
C *c = new C ();
c-> f_a ();
c-> f_b ();
c-> f_z1 ();
c-> f_z2 ();
return 0 ;
}
バイナリは削除されますが、RTTI が含まれています。
バイナリをロードするだけの場合、 main
関数 (32 ビット バージョンではsub_84D
) は次のようになります。
以下を使用して、g++ RTTI パーサーを開始して実行します。
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
次に、構造体 C を更新し (「備考」セクションを参照)、 v0
C *
にキャストし、再度逆コンパイルします。
RTTI がない場合でも、当社のインフラストラクチャでは手動で C++ クラスを定義できます。同じ例 (examples/a32_stripped) では、構造体 B を手動で作成し、その仮想テーブルを選択して次のように入力します。
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
make_vtable
選択領域の代わりにvtable_ea
とvtable_ea_stop
取得することもできます。
次に、構造体 C を作成し、継承を適用します。
cpp_utils.add_baseclass("C", "B")
これで、クラス C vtable を選択して次のように入力して再構築できます。
cpp_utils.make_vtable("C")
構造体 Z を追加し、その vtable も再構築します。ここからがすごい部分です。
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
これは、オフセット 0x0c で Z の C 継承を適用し、構造体もリフレッシュします (注釈を参照)。
最後に残っているのは、C の 2 番目の vtable (Z のインターフェイスを実装する vtable) を更新することです。この vtable をマークして、次のように入力します。
cpp_utils.make_vtable("C", offset_in_class=0x0c)
ida_medigate は、この vtable がクラス Z の vtable であることを認識しており、結果は次のようになります。
最終結果は RTTI の場合と同じになります。
新しい vtable 構造体が (RTTI パーサーまたはユーザーによる手動で) 作成されると、まだ変更されていない各関数の名前が変更され、逆コンパイルされ、最初の引数がthis
に設定されます。
そのような関数に対応する構造体のメンバーをダブルクリックすると、その関数に移動するため、ユーザーは C++ コード フローを簡単に読むことができます。
vtable 内の関数またはそれに対応する関数ポインター メンバーの名前や型の変更はすべてフックされ、それらすべての間で同期されます。これは、たとえば、ユーザーが逆コンパイラ ウィンドウを通じて vtable メンバーの型を変更でき、この新しい型 (プロトタイプ) がターゲット関数にも適用されることを意味します。
前の画像の 15 行目に、B::sub_9A8 (ソース コードでは B::f_b) への呼び出しがあります。この関数の引数はB *
です。
ただし、この関数はC
インスタンス (アップキャスト) によって呼び出される場合もあります。そのインスタンスが呼び出す仮想関数を確認したいと考えています。潜在的な派生クラスが多数あるため、 this
C *
にキャストすることが常に可能であるとは限りません。そのため、異なる仮想テーブルを持つ子を持つ基本クラスごとにユニオンを実装します。 alt+y (別の共用体メンバーを選択するためのショートカット) をクリックすると、 B
の導関数の別の派生仮想テーブルを表示するように選択できます。
したがって、最終的には、特定の呼び出しのみを別の仮想関数に「キャスト」できます。
挫折した C++ リバース エンジニアの聖杯。仮想関数からそれらを表す vtable 構造体のメンバーまでの外部参照を維持します。
これをida-referee
と組み合わせると、仮想関数呼び出しのすべての外部参照を追跡できるようになります。
制限: 追跡できるのは、既に逆コンパイルされた仮想呼び出しのみです。幸いなことに、自動分析は関数間の引数の型を設定することを認識しているため、さらに多くの引数をキャストする -> 関連するすべての関数を逆コンパイルする -> コードをもう一度読み込んでさらに多くの引数をキャストする (...) という反復プロセスにより、この機能が実際に役に立ちます。強力な!
IDAPython で構造体のメンバーをサブクラスとしてマークする方法は、IDB にすぐには同期されません。私たちが行うハッキングは、同期がトリガーされるように構造を編集することです。を使用することもできます
utils.refresh_struct(struct_ptr)
これは、構造体の最後にダミーフィールドを追加し、それを未定義にします。