Einführung
Diese Veröffentlichung zielt darauf ab, einige Reverse Engineering-Techniken zu untersuchen. Alle Materialien werden nur zu Informationszwecken präsentiert und sind nicht für persönliche Zwecke bestimmt.Empfohlene Lektüre nach dem
ersten TeilWenn einem Chirurgen beigebracht wird, wie eine Person arbeitet, und ihm ein Skalpell gibt, bedeutet dies nicht, dass er dieses Wissen zum Nachteil einer Person nutzt, und ein sachkundiger Assembler träumt nicht davon, einen Supervirus zu schreiben.
In diesen Lektionen sollten Sie also nicht nach Hinweisen auf Risse und Hacks suchen.
Forschungsgegenstand
Wir untersuchen weiterhin den Plug-In-Code für die Visual Studio Atomineer Pro-Dokumentation (im Folgenden: APD). Machen wir uns mit dem Tool und seinen Funktionen vertraut.
Nehmen wir also an, wir haben eine Klasse in C ++.
class ClassForReadFile { public: ClassForReadFile(); };
Konfigurieren Sie die APD so, dass die Kommentare im Doxygen-Stil vorliegen. Wir bewegen den Cursor auf die
Klasse und drücken
STRG + UMSCHALT + D. Wir bekommen folgendes:
class ClassForReadFile { public: ClassForReadFile(); };
Das Plugin hat eine schöne Beschreibung der Klasse hinzugefügt. Alles in Ordnung ist! Wir gehen weiter. Angenommen, eine Klasse gehört zu einer Bibliothek und wir müssen sie exportieren. Fügen Sie ein Makro hinzu und ändern Sie die Klassendefinition
#ifdef DLL_EXPORTS #define DATA_READER_DLL_EXPORTS __declspec(dllexport) #else #define DATA_READER_DLL_EXPORTS __declspec(dllimport) #endif class DATA_READER_DLL_EXPORTS ClassForReadFile { public: ClassForReadFile(); };
Für C ++ (Windows OS) ist die Situation Standard. Schauen Sie sich unser Plugin an.
Drücken Sie STRG + UMSCHALT + D und erhalten Sie überhaupt nicht das, was wir erwartet haben
class DATA_READER_DLL_EXPORTS ClassForReadFile { };
Der Name der
Definition DATA_READER_DLL_EXPORTS
wurde als Name der Klasse anstelle von ClassForReadFile definiert, und die Klassenbeschreibung wurde aus diesem Namen generiert. Das heißt, im Plugin-Code wird diese Situation, nämlich der Export der Klasse, entweder nicht oder fehlerhaft verarbeitet. Dies werden wir versuchen zu korrigieren.
Schritt 1
Wir werden nach Hinweisen suchen. Erstens, da der Export von Funktionen und Klassen aus C / C ++ eine Standardsituation ist, versuchen wir immer noch, das Plugin korrekt zu "erzwingen". Fügen Sie anstelle der
Definition von DATA_READER_DLL_EXPORTS die Anweisung
__declspec selbst ein und generieren Sie die Dokumentation
class __declspec(dllexport) ClassForReadFile { };
Und siehe da, sie haben die richtige Klassenbeschreibung! Wir schließen daraus, dass es in APD einen Code gibt, der das Vorhandensein der Zeichenfolge "__declspec" in der Klassenbeschreibung überprüft und den weiteren Algorithmus zum Erstellen der Dokumentation ignoriert.
Wir dekompilieren die Bibliothek mit der Standarddatei ildasm.exe aus den Microsoft SDKs. Suchen Sie die Zeile "__declspec". Es wird in 2 Methoden CmdDocComment :: a und CmdDocComment :: b gefunden. Klasse eins. Wir werden es weiter untersuchen.
Schritt 2
Ich muss sofort sagen, dass das, wonach wir suchen, in der CmdDocComment :: a-Methode enthalten ist
Hier trifft sich
__declspec . Es werden nur die interessantesten Zeilen angezeigt.
Zeichenfolge a (CmdDocComment.GeneratorInfo A_0) Wir sind zu dem Schluss gekommen, dass nach Überprüfung
list [num3] == "__declspec"
Löschmethode wird aufgerufen
list.RemoveAt (num3);
Argumentation (laut denken):
- Die CmdDocComment :: a-Methode verfügt über eine lokale Variable, die ein Array von Zeichenfolgen enthält
List<string> list = A_0.e;
- Das erste Element dieses Arrays speichert den Anfang der Beschreibung der Funktion, Struktur, Klasse usw., dh des Schlüsselworts "Klasse", "Struktur", "Vereinigung".
list[0] == "struct"
- Jedes Element des Arrays enthält ein separates Wort. In unserem Fall ist es {"class", "DATA_READER_DLL_EXPORTS", "ClassForReadFile"}
- Es gibt eine Schleife, die alle Elemente des Arrays "e" umgeht, nach dem Element "__declspec" sucht und es und alles, was in Klammern steht, entfernt
- Es gibt eine zusätzliche Bedingung zum Verlassen des Zyklus. Dies ist die Position der Wörter "wo" oder ":". Das offizielle Wort "wo" ist, um ehrlich zu sein, nicht vertraut, aber ":" wird beim Erben von Klassen verwendet
Definieren Sie einen neuen Algorithmus und den Zweck der Änderungen:
1. Änderungen sollten den Rest der Funktionalität nicht beeinträchtigen
2. Wir werden die Elemente des "list" -Arrays gemäß dem Algorithmus löschen
- das erste Element überspringen;
- Wenn das nächste Element nicht ":" und nicht "where" und nicht das Ende des Arrays ist, löschen Sie es.
Schreiben Sie den gewünschten Zyklus
Es bleibt zu programmieren.
Schritt 3
Laut programmieren. Programmieren im allgemeinen Fall ist das Schreiben von Quellcodes, Kompilieren, Verknüpfen. Aber der Verschleierer hat uns eine solche Gelegenheit genommen. Wir werden das empfohlene
dnSpy-Tool verwenden . Wir werden die HEX-Codes von CIL-Befehlen direkt in der Bibliothek ändern, was, wie sich herausstellte, sehr aufregend und informativ ist! Fangen wir an. Öffnen Sie dnSpy und laden Sie die Bibliothek.
Finden Sie unsere Methode Wählen Sie while und ändern Sie die Ansicht in IL
Ich werde auch eine Auflistung geben, obwohl es ziemlich sperrig ist
Jetzt haben wir ein Fenster in CIL-Befehlen, deren HEX-Darstellung, Dateiversatz und Beschreibung. Alles an einem Ort. Sehr praktisch (danke
CrazyAlex25 ).
Achten wir auf den Block, in dem "__declspec" erwähnt wird. Blockoffset 0x0001675A. Dies wird der Beginn unserer Änderungen sein. Suchen Sie als Nächstes die RemoveAt-Methode. Es wird uns unverändert nützlich sein. Das letzte Byte des Blocks ist 0x000167BF. Gehen Sie zum HEX-Editor
Strg + X und schreiben Sie 0x00 in diesen Bereich. Wir werden speichern und prüfen, wozu die Änderungen geführt haben.
leere Schleife while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } list.RemoveAt(num3); num3--; num3++; }
Jetzt werden wir die neue Logik implementieren. Fügen Sie zunächst die Bedingung hinzu
if (num3 != 0 && num3 < list.Count - 1)
Die Tabelle zeigt die neuen Befehle und ihre Beschreibung.
1119 | ldloc.s | Lädt eine lokale Variable mit dem angegebenen Index auf den Berechnungsstapel (Kurzform). |
---|
2C61 | brfalse.s | Übergibt die Steuerung an die endgültige Anweisung, wenn der Wert false, eine Nullreferenz oder Null ist. Hinweis : Wenn num3 == 0 ist, fahren Sie mit dem Erhöhen des Schleifeniterators fort. Der Wert 0x64 ist der Adressoffset zur Anweisung 0x000167BF (siehe Auflistung) |
---|
1119 | ldloc.s | Lädt eine lokale Variable mit dem angegebenen Index auf den Berechnungsstapel (Kurzform) |
---|
07 | ldloc.1 | Lädt eine lokale Variable mit Index 1 auf den Berechnungsstapel |
---|
6FF700000A | callvirt | get_Count () - Ruft eine spät gebundene Objektmethode auf und verschiebt den Rückgabewert auf den Berechnungsstapel |
---|
17 | ldc.i4.1 | Schiebt den ganzzahligen Wert 1 auf dem Berechnungsstapel als int32 |
---|
59 | sub | Subtrahiert einen Wert von einem anderen und schiebt das Ergebnis auf den Berechnungsstapel. |
---|
2F55 | bge.s. | Es überträgt die Kontrolle auf die endgültige Anweisung (Kurzform), wenn der erste Wert größer oder gleich dem zweiten ist. Hinweis : Wenn num3> list.Count - 1 ist, fahren Sie mit dem Schritt des Erhöhens des Schleifeniterators fort. Der Wert 0x55 ist der Adressoffset vor dem Befehl 0x000167BF |
---|
Wir schreiben diese Bytes ab dem Offset 0x0001675A. Speichern und erneut dekompilieren
Erste Bedingung while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; }
Jetzt fügen wir die Zeichenfolgenprüfung "where" und ":" hinzu. Ich zitiere den folgenden HEX-Code ohne zusätzliche Kommentare:
07 11 19 17 58 6F F9 00 00 0A 72 A3 1D 00 70 28 70 00 00 0A 2D 3F 07 11 19 17 58 6F F9 00 00 0A 72 92 5E 00 70 28 70 00 00 0A 2D 29
dekompilieren und bekommen, was Sie geplant haben
Neuer Zyklus while (num3 < list.Count && !(list[num3] == "where") && !(list[num3] == ":")) { if (list[num3] == A_0.b && num2 < 0) { num2 = num3; } if (num3 != 0 && num3 < list.Count - 1 && !(list[num3 + 1] == ":") && !(list[num3 + 1] == "where")) { list.RemoveAt(num3); num3--; } num3++; }
Mit diesen Änderungen generiert das Plugin die folgende Dokumentation des Codes:
class DATA_READER_DLL_EXPORTS ClassForReadFile { };
Fazit
In dieser Lektion haben wir gelernt, wie wir unser Wissen anwenden können, um Fehler zu beheben. Natürlich spiegelt dieses Beispiel nicht die gesamte Vielfalt der Fehler und ihre Behandlung wider, aber dies ist kein „banaler Riss“. Wir haben den offensichtlichen Fehler behoben, ohne den Quellcode zu haben und ohne die Anwendung neu zu erstellen.