Dekompilierer - Decompiler

Ein Decompiler ist ein Computerprogramm , das eine ausführbare Datei in eine High-Level- Quelldatei übersetzt, die erfolgreich neu kompiliert werden kann . Es ist daher das Gegenteil eines Compilers , der eine Quelldatei in eine ausführbare Datei übersetzt. Decompiler sind normalerweise nicht in der Lage, den ursprünglichen Quellcode perfekt zu rekonstruieren, und produzieren daher häufig verschleierten Code . Dennoch bleiben Decompiler ein wichtiges Werkzeug beim Reverse Engineering von Computersoftware .

Einführung

Der Begriff decompiler wird am häufigsten zu einem Programm angewandt , die übersetzt ausführbare Programme (von dem Ausgang Compiler ) in Quellcode in einer (relativ) höheren Sprache , die, wenn kompilierte, wird ein ausführbare dessen Verhalten herzustellen , ist das gleiche wie die ursprünglichen ausführbaren Programm. Zum Vergleich : ein Disassembler übersetzt ein ausführbares Programm in Assemblersprache (und ein Assembler könnte es verwendet werden , für die Montage in ein ausführbares Programm zurück).

Dekompilierung ist die Verwendung eines Decompilers, obwohl der Begriff sich auch auf die Ausgabe eines Decompilers beziehen kann. Es kann für die Wiederherstellung von verlorenem Quellcode verwendet werden und ist in einigen Fällen auch für die Computersicherheit , Interoperabilität und Fehlerkorrektur nützlich . Der Erfolg der Dekompilierung hängt von der Menge an Informationen ab, die in dem zu dekompilierenden Code vorhanden sind, und von der Komplexität der darauf durchgeführten Analyse. Die von vielen virtuellen Maschinen verwendeten Bytecode-Formate (wie die Java Virtual Machine oder die .NET Framework Common Language Runtime ) enthalten oft umfangreiche Metadaten und High-Level-Features, die eine Dekompilierung durchaus möglich machen. Die Verwendung von Debug-Daten , dh Debug-Symbolen, kann es ermöglichen, die ursprünglichen Namen von Variablen und Strukturen und sogar die Zeilennummern wiederzugeben. Maschinensprache ohne solche Metadaten oder Debug-Daten ist viel schwieriger zu dekompilieren.

Einige Compiler und Tools nach der Kompilierung erzeugen verschleierten Code ( dh sie versuchen, eine Ausgabe zu erzeugen, die sehr schwer zu dekompilieren ist oder die zu einer verwirrenden Ausgabe dekompiliert wird). Dies geschieht, um das Reverse Engineering der ausführbaren Datei zu erschweren .

Während Decompiler normalerweise verwendet werden, um Quellcode aus binären ausführbaren Dateien (neu) zu erstellen, gibt es auch Decompiler, um bestimmte binäre Datendateien in menschenlesbare und bearbeitbare Quellen umzuwandeln.

Entwurf

Dekompilierer kann man sich als aus einer Reihe von Phasen zusammengesetzt vorstellen, von denen jede zu spezifischen Aspekten des gesamten Dekompilierungsprozesses beiträgt.

Lader

Die erste Dekompilierungsphase lädt und analysiert den eingegebenen Maschinencode oder das binäre Dateiformat des Zwischensprachenprogramms . Es sollte in der Lage sein, grundlegende Fakten über das Eingabeprogramm wie die Architektur (Pentium, PowerPC usw.) und den Einstiegspunkt zu ermitteln. In vielen Fällen sollte es in der Lage sein, das Äquivalent der Funktion eines C- Programms zu finden, das den Anfang des vom Benutzer geschriebenen Codes darstellt. Davon ausgenommen ist der Laufzeit-Initialisierungscode, der nach Möglichkeit nicht dekompiliert werden sollte. Falls vorhanden, werden auch die Symboltabellen und Debug-Daten geladen. Das Frontend kann die verwendeten Bibliotheken möglicherweise auch dann identifizieren, wenn sie mit dem Code verknüpft sind, dies bietet Bibliotheksschnittstellen. Wenn es den verwendeten Compiler oder die verwendeten Compiler bestimmen kann, kann es nützliche Informationen beim Identifizieren von Code-Idiomen liefern. main

Demontage

Die nächste logische Phase ist die Zerlegung von Maschinencodebefehlen in eine maschinenunabhängige Zwischendarstellung (IR). Zum Beispiel die Pentium- Maschinen-Anweisung

mov    eax, [ebx+0x04]

könnte in die IR übersetzt werden

eax  := m[ebx+4];

Redewendungen

Idiomatische Maschinencodesequenzen sind Codesequenzen, deren kombinierte Semantik aus der individuellen Semantik der Anweisungen nicht sofort ersichtlich ist. Entweder als Teil der Zerlegungsphase oder als Teil späterer Analysen müssen diese idiomatischen Sequenzen in bekannte äquivalente IR übersetzt werden. Zum Beispiel der x86-Assembly-Code :

    cdq    eax             ; edx is set to the sign-extension≠edi,edi +(tex)push
    xor    eax, edx
    sub    eax, edx

könnte übersetzt werden in

eax  := abs(eax);

Einige idiomatische Sequenzen sind maschinenunabhängig; einige beinhalten nur eine Anweisung. Löscht beispielsweise das Register (setzt es auf Null). Dies kann mit einer maschinenunabhängigen Vereinfachungsregel, wie z . xor eax, eaxeaxa = 0

Im Allgemeinen ist es am besten, die Erkennung idiomatischer Sequenzen nach Möglichkeit auf spätere Stufen zu verschieben, die weniger von der Befehlsreihenfolge beeinflusst werden. Zum Beispiel kann die Befehlsplanungsphase eines Compilers andere Befehle in eine idiomatische Sequenz einfügen oder die Reihenfolge von Befehlen in der Sequenz ändern. Ein Mustervergleichsprozess in der Demontagephase würde das veränderte Muster wahrscheinlich nicht erkennen. Spätere Phasen gruppieren Befehlsausdrücke in komplexere Ausdrücke und modifizieren sie in eine kanonische (standardisierte) Form, wodurch es wahrscheinlicher wird, dass sogar das geänderte Idiom später in der Dekompilierung einem Muster einer höheren Ebene entspricht.

Es ist besonders wichtig, die Compiler-Idiome für Subroutinenaufrufe , Ausnahmebehandlung und switch-Anweisungen zu kennen . Einige Sprachen bieten auch umfangreiche Unterstützung für Strings oder lange Ganzzahlen .

Programmanalyse

Auf die IR können verschiedene Programmanalysen angewendet werden. Insbesondere kombiniert die Ausdrucksweitergabe die Semantik mehrerer Anweisungen zu komplexeren Ausdrücken. Zum Beispiel,

    mov   eax,[ebx+0x04]
    add   eax,[ebx+0x08]
    sub   [ebx+0x0C],eax

könnte nach Expressionsvermehrung zu folgendem IR führen:

m[ebx+12]  := m[ebx+12] - (m[ebx+4] + m[ebx+8]);

Der resultierende Ausdruck ähnelt eher einer Hochsprache und hat auch die Verwendung des Maschinenregisters eliminiert eax. Spätere Analysen können das ebxRegister eliminieren .

Datenflussanalyse

Die Stellen, an denen Registerinhalte definiert und verwendet werden, müssen mittels Datenflussanalyse nachvollzogen werden . Dieselbe Analyse kann auf Standorte angewendet werden, die für temporäre und lokale Daten verwendet werden. Für jede solche verbundene Menge von Wertdefinitionen und Verwendungen kann dann ein anderer Name gebildet werden. Es ist möglich, dass dieselbe lokale Variablenposition für mehr als eine Variable in verschiedenen Teilen des Originalprogramms verwendet wurde. Schlimmer noch, es ist möglich, dass die Datenflussanalyse einen Pfad identifiziert, auf dem ein Wert zwischen zwei solchen Verwendungen fließen kann, obwohl dies in der Realität nie passieren oder von Bedeutung wäre. Dies kann in ungünstigen Fällen dazu führen, dass ein Standort als Vereinigung von Typen definiert werden muss. Der Decompiler kann es dem Benutzer ermöglichen, solche unnatürlichen Abhängigkeiten explizit zu durchbrechen, was zu klarerem Code führt. Dies bedeutet natürlich, dass eine Variable möglicherweise ohne Initialisierung verwendet wird und weist somit auf ein Problem im ursprünglichen Programm hin.

Typanalyse

Ein guter Maschinencode-Decompiler führt die Typanalyse durch. Hier führt die Art und Weise, wie Register oder Speicherplätze verwendet werden, zu Einschränkungen hinsichtlich des möglichen Typs des Speicherplatzes. Eine andAnweisung impliziert beispielsweise, dass der Operand eine ganze Zahl ist; Programme verwenden eine solche Operation nicht für Gleitkommawerte (außer in speziellem Bibliothekscode) oder für Zeiger . Eine addAnweisung führt zu drei Einschränkungen, da die Operanden sowohl ganzzahlig sein können als auch eine ganze Zahl und ein Zeiger (bei ganzzahligen bzw. Zeigerergebnissen; die dritte Einschränkung ergibt sich aus der Reihenfolge der beiden Operanden, wenn die Typen unterschiedlich sind).

Es können verschiedene High-Level-Ausdrücke erkannt werden, die die Erkennung von Strukturen oder Arrays auslösen. Es ist jedoch schwierig, viele der Möglichkeiten zu unterscheiden, aufgrund der Freiheit, die Maschinencode oder sogar einige Hochsprachen wie C mit Umwandlungen und Zeigerarithmetik ermöglichen.

Das Beispiel aus dem vorherigen Abschnitt könnte zu folgendem High-Level-Code führen:

struct T1 *ebx;
    struct T1 {
        int v0004;
        int v0008;
        int v000C;
    };
ebx->v000C -= ebx->v0004 + ebx->v0008;

Strukturierung

Die vorletzte Dekompilierungsphase beinhaltet die Strukturierung des IR in Konstrukte höherer Ebene wie whileSchleifen und if/then/elsebedingte Anweisungen. Zum Beispiel der Maschinencode

    xor eax, eax
l0002:
    or  ebx, ebx
    jge l0003
    add eax,[ebx]
    mov ebx,[ebx+0x4]
    jmp l0002
l0003:
    mov [0x10040000],eax

könnte übersetzt werden in:

eax = 0;
while (ebx < 0) {
    eax += ebx->v0000;
    ebx = ebx->v0004;
}
v10040000 = eax;

Unstrukturierter Code ist schwieriger in strukturierten Code zu übersetzen als bereits strukturierter Code. Zu den Lösungen gehören das Replizieren von Code oder das Hinzufügen von booleschen Variablen.

Codegenerierung

Die letzte Phase ist die Generierung des High-Level-Codes im Backend des Decompilers. So wie ein Compiler mehrere Back-Ends zum Erzeugen von Maschinencode für verschiedene Architekturen haben kann, kann ein Decompiler mehrere Back-Ends zum Erzeugen von High-Level-Code in verschiedenen High-Level-Sprachen haben.

Unmittelbar vor der Codegenerierung kann es wünschenswert sein, eine interaktive Bearbeitung des IR zu ermöglichen, möglicherweise unter Verwendung einer Form einer grafischen Benutzeroberfläche . Dies würde es dem Benutzer ermöglichen, Kommentare und nicht generische Variablen- und Funktionsnamen einzugeben. Diese werden jedoch fast genauso einfach in einem Post-Dekompilierungs-Edit eingegeben. Der Benutzer möchte möglicherweise strukturelle Aspekte ändern, z. B. eine whileSchleife in eine forSchleife umwandeln . Diese lassen sich mit einem einfachen Texteditor weniger leicht ändern, obwohl Quellcode-Refactoring- Tools bei diesem Prozess hilfreich sein können. Der Benutzer muss möglicherweise Informationen eingeben, die während der Typanalysephase nicht identifiziert werden konnten, z. B. das Modifizieren eines Speicherausdrucks in einen Array- oder Strukturausdruck. Schließlich müssen möglicherweise falsche IR korrigiert oder Änderungen vorgenommen werden, um den Ausgabecode besser lesbar zu machen.

Rechtmäßigkeit

Die meisten Computerprogramme unterliegen dem Urheberrecht . Obwohl der genaue Umfang des Urheberrechts von Region zu Region unterschiedlich ist, gewährt das Urheberrecht im Allgemeinen dem Autor (dem/den Programmierern) oder dem Arbeitgeber eine Sammlung exklusiver Rechte an dem Programm. Zu diesen Rechten gehört das Recht, Kopien anzufertigen, einschließlich Kopien in den Arbeitsspeicher des Computers (es sei denn, die Erstellung einer solchen Kopie ist für die Nutzung des Programms erforderlich). Da bei der Dekompilierung mehrere solcher Kopien angefertigt werden, ist dies im Allgemeinen ohne Genehmigung des Urheberrechtsinhabers verboten. Da die Dekompilierung jedoch häufig ein notwendiger Schritt zum Erreichen der Software- Interoperabilität ist , erlauben die Urheberrechtsgesetze sowohl in den Vereinigten Staaten als auch in Europa die Dekompilierung in begrenztem Umfang.

In den Vereinigten Staaten wurde in Dekompilierungsfällen erfolgreich die Einrede der fairen Verwendung des Urheberrechts geltend gemacht. In der Rechtssache Sega v. Accolade beispielsweise entschied das Gericht, dass Accolade rechtmäßig eine Dekompilierung durchführen kann, um den von den Spielkonsolen von Sega verwendeten Software-Sperrmechanismus zu umgehen. Darüber hinaus enthält der Digital Millennium Copyright Act (PUBLIC LAW 105-304) geeignete Ausnahmen für Sicherheitstests und -bewertungen in §1201(i) und Reverse Engineering in §1201(f).

In Europa sieht die Softwarerichtlinie von 1991 ausdrücklich ein Recht auf Dekompilierung vor, um Interoperabilität zu erreichen. Als Ergebnis einer hitzigen Debatte zwischen Software-Protektionisten einerseits und Wissenschaftlern sowie unabhängigen Software-Entwicklern andererseits erlaubt Artikel 6 eine Dekompilierung nur, wenn eine Reihe von Bedingungen erfüllt sind:

  • Zunächst muss eine natürliche oder juristische Person über eine Lizenz verfügen , um das zu dekompilierende Programm zu verwenden.
  • Zweitens muss eine Dekompilierung erforderlich sein, um eine Interoperabilität mit dem Zielprogramm oder anderen Programmen zu erreichen. Interoperabilitätsinformationen sollten daher nicht ohne weiteres verfügbar sein, beispielsweise durch Handbücher oder API- Dokumentation. Dies ist eine wichtige Einschränkung. Die Notwendigkeit muss durch den Decompiler nachgewiesen werden. Der Zweck dieser wichtigen Einschränkung besteht in erster Linie darin, Entwicklern einen Anreiz zu bieten, die Interoperabilitätsinformationen ihrer Produkte zu dokumentieren und offenzulegen.
  • Drittens muss sich der Dekompilierungsprozess nach Möglichkeit auf die interoperabilitätsrelevanten Teile des Zielprogramms beschränken. Da einer der Zwecke der Dekompilierung darin besteht, die Programmstruktur zu verstehen, kann es schwierig sein, diese dritte Einschränkung zu erfüllen. Auch hier liegt die Beweislast beim Decompiler.

Darüber hinaus schreibt Artikel 6 vor, dass die durch die Dekompilierung gewonnenen Informationen nicht für andere Zwecke verwendet und nicht an Dritte weitergegeben werden dürfen.

Insgesamt kodifiziert das in Artikel 6 vorgesehene Dekompilierungsrecht die angeblich gängige Praxis in der Softwarebranche. Es sind nur wenige europäische Klagen bekannt, die aus dem Dekompilierungsrecht hervorgegangen sind. Dies könnte so interpretiert werden, dass es eines von drei Dingen bedeutet:

  1. ) das Dekompilierungsrecht nicht häufig genutzt wird und das Dekompilierungsrecht daher möglicherweise unnötig war,
  2. ) das Dekompilierungsrecht gut funktioniert und ausreichende Rechtssicherheit bietet, um keine Rechtsstreitigkeiten auszulösen oder
  3. ) bleibt die illegale Dekompilierung weitgehend unentdeckt.

In einem Bericht aus dem Jahr 2000 über die Umsetzung der Softwarerichtlinie durch die europäischen Mitgliedsstaaten schien die Europäische Kommission die zweite Interpretation zu unterstützen.

Werkzeuge

Decompiler zielen normalerweise auf ein bestimmtes Binärformat ab. Einige sind native Befehlssätze (zB Intel x86, ARM, MIPS), andere sind Bytecode für virtuelle Maschinen (Dalvik, Java-Klassendateien, WebAssembly, Ethereum).

Aufgrund von Informationsverlusten während der Kompilierung ist die Dekompilierung fast nie perfekt, und nicht alle Dekompilierer funktionieren für ein bestimmtes Binärformat gleich gut. Es gibt Studien, die die Leistung verschiedener Decompiler vergleichen.

Siehe auch

Java-Decompiler

Andere Decompiler

Verweise

Externe Links