ida_medigate C++-Plugin für IDA Pro
[Inhaltsverzeichnis]
Reverse Engineering von kompiliertem C++-Code macht keinen Spaß. Statisches Reverse Engineering von kompiliertem C++-Code ist frustrierend. Der Hauptgrund, der es so schwierig macht, sind virtuelle Funktionen. Im Gegensatz zum kompilierten C-Code gibt es keinen klaren Codefluss. Zu oft kann man viel Zeit damit verbringen, zu verstehen, wie die nächste virtuelle Funktion aufgerufen wird, anstatt nur die Funktion wie im kompilierten C-Code zu sehen.
Wenn man eine virtuelle Funktion untersucht, macht es keinen Sinn, wie viel Zeit man aufwenden muss, um ihre XRef zu finden.
Nach zu vielen C++ RE-Projekten habe ich aufgegeben und beschlossen, dass ich für diese Art von Forschung ein flexibles (Python) und stabiles (das ich leicht warten kann) Tool benötige. Der größte Teil dieses Plugins wurde im Januar 2018 geschrieben, und vor kurzem habe ich beschlossen, den Staub zu beseitigen und die Unterstützung der neuen IDA-Klassen (7.2) zu unterstützen.
Dieses Plugin soll nicht immer „out of the box“ funktionieren, sondern ein weiteres Tool für den Reversierer sein.
Das Plugin besteht aus zwei Teilen:
Dieser erste Teil ist nicht vom zweiten Teil abhängig, daher ist es möglich, das Plugin zum Reverse Engineering einer Binärdatei zu verwenden, die kein RTTI enthält, indem diese Klassen manuell basierend auf der API des Plugins definiert werden.
Was das Plugin einzigartig macht, ist die Tatsache, dass es dieselbe Umgebung verwendet, mit der der Forscher bereits vertraut ist, kein neues Menü oder Objekt hinzufügt und auf den bekannten IDA-Bausteinen (Struktur, Union, Typ für Strukturmitglieder usw.) basiert ) – Dadurch kann das Plugin die C++-Abstraktion für jede von IDA unterstützte Architektur unterstützen .
Hinweis: Der RTTI-Parser analysiert x86/x64 g++ RTTI, seine Struktur ermöglicht jedoch das einfache Hinzufügen von Unterstützung für weitere Architekturen und Compiler.
plugins/
). Kopieren Sie medigate_cpp_plugin
in das plugins
Verzeichnis und fügen Sie den Quellcodepfad zu Ihrer Datei idapythonrc.py
hinzu
Kopieren Sie plugins/ida-referee/referee.py
in dasselbe Verzeichnis.
Angenommen, der binäre Originalquellcode ist der folgende ( 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 ;
}
Die Binärdatei ist entfernt, enthält aber RTTI.
Wenn wir nur die Binärdatei laden, sieht die main
( sub_84D
in der 32-Bit-Version) so aus:
Starten Sie den g++ RTTI-Parser und führen Sie ihn aus, indem Sie Folgendes verwenden:
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
Aktualisieren Sie nun die Struktur C (siehe Abschnitt „Bemerkungen“), wandeln Sie v0
in C *
um und dekompilieren Sie erneut:
Für Fälle, in denen kein RTTI vorhanden ist, ermöglicht unsere Infrastruktur weiterhin die manuelle Definition der C++-Klasse. Für dasselbe Beispiel (examples/a32_stripped) können Sie Struktur B manuell erstellen und dann die virtuelle Tabelle und den Typ auswählen
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
make_vtable
kann anstelle des ausgewählten Bereichs auch vtable_ea
und vtable_ea_stop
abrufen.
Erstellen Sie dann Struktur C und wenden Sie die Vererbung an:
cpp_utils.add_baseclass("C", "B")
Jetzt können Sie die vtable der Klasse C neu erstellen, indem Sie sie auswählen und Folgendes eingeben:
cpp_utils.make_vtable("C")
Fügen Sie die Struktur Z hinzu, erstellen Sie auch ihre vtable neu, und jetzt kommt der coole Teil:
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
die die C-Vererbung von Z am Offset 0x0c anwenden und auch die Struktur aktualisieren (siehe Anmerkungen).
Als letztes müssen Sie noch die zweite vtable von C aktualisieren, die die Schnittstelle von Z implementiert. Markieren Sie diese vtable und geben Sie Folgendes ein:
cpp_utils.make_vtable("C", offset_in_class=0x0c)
ida_medigate weiß, dass diese vtable die vtable der Klasse Z ist und das Ergebnis wird sein:
Das Endergebnis ist das gleiche wie im RTTI-Fall:
Wenn eine neue Vtable-Struktur erstellt wird (durch den RTTI-Parser oder manuell durch den Benutzer), wird jede Funktion, die sich noch nicht geändert hat, umbenannt, dekompiliert und ihr erstes Argument auf this
gesetzt.
Durch einen Doppelklick auf das Mitglied einer Struktur, das einer solchen Funktion entspricht, wird zu der Funktion navigiert, sodass der Benutzer den C++-Codefluss auf bequeme Weise lesen kann!
Jede Namens- oder Typänderung einer Funktion oder ihres entsprechenden Funktionszeigermitglieds in vtables wird zwischen allen verknüpft und synchronisiert. Das bedeutet zum Beispiel, dass der Benutzer den vtable-Mitgliedstyp über das Dekompilerfenster ändern könnte und dieser neue Typ (Prototyp) auch auf die Zielfunktion angewendet wird.
In Zeile 15 im vorherigen Bild gibt es einen Aufruf von B::sub_9A8 (B::f_b im Quellcode). Dieses Funktionsargument ist B *
:
Diese Funktion kann jedoch auch von einer C
-Instanz aufgerufen werden (Upcasting). Wir möchten die virtuelle Funktion sehen, die ihre Instanz aufrufen würde. Angenommen, es gibt viele potenziell abgeleitete Klassen, sodass this
Umwandlung in C *
nicht immer möglich ist. Aus diesem Grund implementieren wir eine Union für jede Basisklasse, deren Söhne eine andere virtuelle Tabelle haben. Durch Klicken auf Alt+Y (die Tastenkombination zur Auswahl eines anderen Gewerkschaftsmitglieds) kann man eine andere abgeleitete virtuelle Tabelle der B
-Derivate anzeigen:
Letztendlich können wir also nur bestimmte Aufrufe an verschiedene virtuelle Funktionen „umwandeln“:
Der heilige Gral frustrierter C++-Reverse-Engineerer. Wir verwalten XRefs von virtuellen Funktionen zu den Mitgliedern der vtable-Struktur, die sie darstellen!
Durch die Kombination mit ida-referee
können wir alle XRefs virtueller Funktionsaufrufe verfolgen!
Eine Einschränkung: Wir können nur virtuelle Aufrufe verfolgen, die bereits dekompiliert wurden. Glücklicherweise ist die automatische Analyse in der Lage, einen Argumenttyp zwischen Funktionen aufzufüllen. Mit dem iterativen Prozess des Umwandelns weiterer Argumente -> Dekompilieren aller relevanten Funktionen -> erneutes Lesen des Codes und Umwandeln weiterer Argumente (...) wird diese Fähigkeit wirklich kraftvoll!
Die Art und Weise, wie wir Strukturmitglieder in IDAPython als Unterklassen markieren, wird nicht sofort mit der IDB synchronisiert. Der Hack, den wir machen, besteht darin, die Struktur so zu bearbeiten, dass eine Synchronisierung ausgelöst wird. Sie können auch verwenden
utils.refresh_struct(struct_ptr)
Dadurch wird am Ende der Struktur ein Dummy-Feld hinzugefügt und die Definition aufgehoben.