
Plugin zur Visualisierung der Binärcode-Abdeckung für Ghidra
Dragon Dance ist ein Plugin für Ghidra, mit dem Binärcode-Coverage-Daten visualisiert und manipuliert werden können. Coverage-Daten können aus mehreren Coverage-Quellen importiert werden. Derzeit unterstützt das Plugin die binären Instrumentierungswerkzeuge Dynamorio und Intel Pin. Dynamorio hat ein eigenes Coverage-Erfassungsmodul namens "drcov". Intel Pin bietet kein integriertes Coverage-Erfassungsmodul. Um diese Situation zu bewältigen, musste ich ein eigenes Coverage-Erfassungsmodul für Intel Pin schreiben. Also habe ich ein Coverage-Erfassungsmodul für Intel Pin namens ddph (Dragon Dance Pin Helper) geschrieben. Sie können das verwenden. Den Quellcode von ddph finden Sie unter diesem Link. Wenn Sie zu faul sind, selbst zu kompilieren, können Sie die von mir bereitgestellten kompilierten Binärdateien für Windows, macOS und Linux verwenden.

Dragon Dance kann mehrere Coverage-Daten in derselben Sitzung importieren und verwenden. (Es unterstützt auch mehrere Sitzungen, aber derzeit ist das über die GUI nicht nutzbar). Sie können zwischen ihnen wechseln oder schnell Schnittmenge, Differenz, Unterscheidung oder Summenoperationen miteinander durchführen.
Dragon Dance ermöglicht es Ihnen, die Intensität der ausgeführten Anweisungen anzuzeigen. So erhalten Sie einen Hinweis darauf, welche Anweisungen wie oft ausgeführt werden. Sie können die Coverage-Visualisierung auch im Funktionsgraphenfenster anzeigen.

Dragon Dance unterstützt auch ein eigenes Skriptsystem.

Es bietet Ihnen eine flexible Möglichkeit, mit den Coverage-Daten zu spielen. Sie können sie laden, löschen, anzeigen, Schnittmenge, Differenz, Unterscheidung und Summenoperationen darauf anwenden. Der folgende Abschnitt enthält das Skriptsystem und die API. Drücken Sie die Tasten Alt + Enter, um das Skript auszuführen.
Eingebaute Funktionen sind Implementierungen der internen Coverage-Operationen, um eine Schnittstelle zum Skriptsystem bereitzustellen. Eine eingebaute Funktion kann ein Coverage-Objektvariable oder nichts zurückgeben. Eingebaute Funktionen können Aliase haben.
Sie akzeptieren Built-in Arg als Parameter. Parameter können eine variable Länge haben.
Built-in Arg ist ein Verweis, um verschiedene Werttypen zu halten. Built-in Args werden in der Reihenfolge von links nach rechts übergeben. Built-in Arg kann die folgenden Werttypen halten:
Variablen sind dafür zuständig, nur das Coverage-Objekt zu halten. Sie können durch eingebaute Funktionen geladen werden. Sie können als Parameter (Built-in Arg) an die eingebauten Funktionen übergeben werden.
Es gibt zwei Arten von Coverage-Objekten.
Physikalisches Coverage-Objekt und Logisches Coverage-Objekt
Physikalisches Coverage-Objekt zeigt auf ein Coverage-Objekt, das direkt aus der Coverage-Datei geladen wurde. Sie sind in der Coverage-Tabelle sichtbar, die sich in der GUI befindet. Sie können also über die GUI-Operationen mit ihnen interagieren.
Logisches Coverage-Objekt zeigt auf ein Coverage-Objekt, das in einer eingebauten Funktion verarbeitet und als Ergebnis daraus zurückgegeben wurde. Sie sind nicht in der GUI sichtbar, können aber in einer Variable leben, bis sie zerstört werden.
Das Coverage-Objekt wird automatisch vom Variable-Objekt für beide Arten von Coverage-Objekten verwaltet. Zum Beispiel;``` cov1 = load("firstcoverage.out") cov2 = load("secondcov.out")
cov1 = diff(cov1,cov2)
In diesem Beispiel sind cov1 und cov2 Variablen. Und beide Variablen haben physische Coverage-Objekte. Die eingebaute diff-Funktion nimmt beide Variablen und setzt den Rückgabewert auf die Variable cov1. Diese Überschreibungsoperation setzt das Ergebnis-Coverage-Objekt auf die Variable, löscht jedoch nicht das Coverage-Objekt, da es sich um ein physisches Coverage-Objekt handelt. Diese Coverage-Daten bleiben in der Sitzung und auch in der GUI-Tabelle erhalten.
Betrachten wir das vorherige Beispiel wie folgt;```
cov1 = load("first.out")
cov2 = load("second.out")
cov3 = load("third.out")
rvar = sum(cov1,cov2,cov3)
rvar = diff(rvar, cov2)
In diesem Beispiel fließen drei physikalische Coverage-Variablen in eine Summenoperation ein, und die Summenoperation gibt ein logisches Coverage-Ergebnisobjekt zurück. Dann nimmt die Diff-Operation eine logische und eine physikalische Variable auf und überschreibt die Variable namens rvar.
In diesem Fall wird das Ergebnis auf rvar gesetzt und der vorherige Coverage-Wert sofort zerstört. Da es sich um ein logisches Objekt handelte, sollte es gelöscht werden, um Objektlecks zu vermeiden. Wenn Sie eine Variable zerstören möchten, die ein physikalisches Coverage-Objekt enthält, müssen Sie den eingebauten Befehl discard aufrufen. Alle integrierten Funktionen werden unten detailliert beschrieben.
Sie können komplexe Skripte mit verschachtelten Aufrufen integrierter Funktionen schreiben, etwa so:
cres = diff(intersect(a, load("another.log"), c, d), sum(e,f) )
Sie müssen die Logik nicht Zeile für Zeile schreiben.
Die folgenden API-Dokumentationen und ihr Verhalten können sich bis zur endgültigen Version ändern.
clear()
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | None |
| Minimale Parameteranzahl | 0 |
| Maximale Parameteranzahl |
cwd( String : workingDirectory )
diff( Variable : var1, var2, ..... varN )
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | Variable |
| Minimale Parameteranzahl | 2 |
discard( Variable : var1, var2, ..... varN )
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | None |
distinct( Variable : var1, var2, ..... varN )
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | Variable |
| Minimale Parameteranzahl | 2 |
goto( Integer : offset )
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | None |
| Minimale Parameteranzahl |
import( String : filePathOrCoverageName )
intersect( Variable : var1, var2, ..... varN )
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | Variable |
| Minimale Parameteranzahl | 2 |
show( Variable : var )
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | None |
sum( Variable : var1, var2, ..... varN )
| Eigenschaft | Beschreibung |
|---|---|
| Rückgabewert | Variable |
| Minimale Parameteranzahl | 1 |
Fehlerbehebungen
Dragon Dance kann versuchen, eine fehlanalysierte Situation in Ghidra während des Imports der Coverage-Datendatei zu beheben. Bei einigen Binärdateien dekompiliert Ghidra die Anweisungen einer Funktion aufgrund unerwarteter Codegenerierung durch den Compiler nicht. Dragon Dance überprüft das geladene Image und die Integrität der Coverage-Daten. Wenn sie zueinander gültig sind und die Adresse zu einem ausführbaren Abschnitt gehört, aber keine Dekompilierung der Anweisungen stattfindet, fordert das Plugin zur Behebung auf. Dann wird versucht, den rohen Abschnitt zu dekompilieren.

In zukünftigen Versionen des Plugins kann es weitere Fehlerbehebungen oder Workarounds für das Image enthalten.
Die Installation ist recht einfach.
Starten Sie Ghidra.
Klicken Sie auf das Menü "File" und wählen Sie dann "Install Extensions.."
Klicken Sie auf das grüne Plus-Symbol oben rechts im Fenster
Wählen Sie das Plugin-Zip-Paket aus und klicken Sie auf Ok.
Wählen Sie dragondance aus der Liste
Klicken Sie auf Ok und starten Sie Ghidra neu
Beim ersten Laden einer Binärdatei in Ghidra nach der Plugin-Installation sollte Ghidra fragen, ob Sie das neu installierte Plugin konfigurieren möchten.
Wenn Sie auf die Schaltfläche Ja klicken, sollte das DragonDance-Plugin sofort erscheinen.
Wenn Sie auf die Schaltfläche Nein klicken, müssen Sie es manuell selbst aktivieren.
Um es manuell zu aktivieren:
Klicken Sie auf das Menü "File" und wählen Sie dann Configure aus dem Disassembly-Fenster (CodeBrowser)
Klicken Sie oben rechts im Fenster "Configure Tool" auf das kleine Stecker-Symbol
Finden Sie den DragonDance-Eintrag in der Plugin-Liste und aktivieren Sie das entsprechende Kontrollkästchen, dann klicken Sie auf Ok.
Das Dragon Dance-Fenster sollte erscheinen.
Nach der Aktivierung sollten Sie den Eintrag Dragon Dance im Menü "Window" sehen.
Wie bereits beschrieben, kann Dragon Dance Coverage-Daten von Dynamorio und Intel Pin importieren (derzeit). Tatsächlich sind dies generische Werkzeuge zur Binärinstrumentierung. Sie müssen ein geeignetes Modul mit ihnen verwenden, um Coverage-Daten zu sammeln. Dynamorio hat ein eigenes Coverage-Modul namens drcov. Sie können dieses integrierte Modul verwenden, um Coverage zu sammeln.
Verwendung von Dynamorio
Sie können Coverage-Daten von Dynamorio mit folgendem Befehl sammeln:
drrun -t drcov -logdir [COVERAGE_OUTPUT_DIRECTORY_PATH] -- [EXECUTABLE_PATH_TO_EXAMINE] [EXECUTABLE_ARGUMENTS]
Die Ausgabe wird im angegebenen Verzeichnis im Format drcov.[EXECUTABLE_NAME].[ID].proc.log abgelegt.
Verwendung von Intel Pin
Wie bereits erwähnt, bietet Intel Pin kein integriertes Coverage-Sammelmodul. Sie müssen ein benutzerdefiniertes Pin-Modul verwenden. Glücklicherweise habe ich mein eigenes entwickelt, um Coverage von Pin zu sammeln. Das bringt uns einige Vorteile. Ich kann es bei Bedarf erweitern oder zusätzliche Funktionen und Optionen hinzufügen.
Obwohl spätere Versionen möglicherweise funktionieren, wird nur Intel PIN 3.7 unterstützt. Diese werden nicht sofort auf der Intel PIN-Seite angeboten, daher hier direkte Download-Links:
Sie können auf den Quellcode von ddph hier zugreifen. Ich werde Binärdateien für Windows, macOS und Linux bereitstellen. Oder Sie können Ihr eigenes Binärprogramm mit dem Build-Shell-Skript erstellen.
Um Coverage-Daten von Intel Pin zu sammeln, verwenden Sie den folgenden Befehl:
pin -t ddph.[so,dylib,dll] [ddph options] -- [EXECUTABLE_PATH_TO_EXAMINE] [EXECUTABLE_ARGUMENTS]
ddph hat einige Optionen für die Sammlung.
-o: Mit dieser Option können Sie den Ausgabe-Dateinamen für die Coverage angeben. (Standard: ddph.out)
-l: Sie können den Protokolldateinamen für den Betrieb angeben. Wenn Sie "no" an diese Option übergeben, führt ddph keine Protokollierung durch. (Standard: ddph.log)
-p: Erfassungsdetailstufe. Diese Option kann reduced oder high sein. Die hohe Stufe erfasst alle Anweisungen einzeln und baut Vorverarbeitungs-Ausführungsblöcke. Dies liefert eine intensivere Coverage-Ausgabe, ist aber langsamer als reduced. Die reduzierte Stufe verwendet Pin's Trace-Blöcke, was wesentlich schneller ist als die hohe Stufe. Dies wird jedoch keine großen Unterschiede in der Coverage-Ausgabe auf verschiedenen Stufen verursachen. Wenn Sie keine spezifischen Dinge tun möchten, sollten Sie die reduzierte Stufe verwenden. (Standard: reduced)
Für macOS-Benutzer: Ab macOS 10.11 (OS X El Capitan) enthält das Betriebssystem eine Sicherheitsebene namens System Integrity Protection SIP. Diese verhindert, dass Benutzermodus-Prozesse, die versuchen, eine Injektion oder Modifikation an einem anderen Prozess vorzunehmen, selbst wenn sie mit Root-Rechten ausgeführt werden.
Um diese Einschränkung zu umgehen, müssen Sie sie deaktivieren. Führen Sie dazu die folgenden Schritte aus.
Starten Sie macOS neu.
Drücken Sie während des Bootvorgangs die Tasten Command + R und halten Sie sie gedrückt.
Das Betriebssystem wechselt schließlich in den Wiederherstellungsmodus.
Öffnen Sie ein Terminal im Bereich "Dienstprogramme".
Geben Sie csrutil status ein und drücken Sie die Eingabetaste. Sie sollten sehen, dass SIP aktiviert ist.
Geben Sie csrutil disable ein und drücken Sie die Eingabetaste.
Geben Sie csrutil status erneut ein, um sicherzustellen, dass es deaktiviert ist oder nicht. Starten Sie dann das Betriebssystem neu und lassen Sie es normal hochfahren. Jetzt können Sie die Binärinstrumentierungswerkzeuge verwenden.



Laden Sie zuerst Ghidra (neueste Version, derzeit 9.1.2) und dragondance herunter.``` $ wget https://ghidra-sre.org/ghidra_9.1.2_PUBLIC_20200212.zip $ wget https://github.com/0ffffffffh/dragondance/archive/master.zip $ unzip ghidra_9.1.2_PUBLIC_20200212.zip $ unzip master.zip
Als nächstes installieren Sie gradle und jdk```
$ sudo apt install openjdk-11-jdk
$ wget https://services.gradle.org/distributions/gradle-5.2.1-bin.zip
$ sudo unzip -d /opt/gradle gradle-5.2.1-bin.zip
Neue Profildatei erstellen``` $ sudo vi /etc/profile.d/gradle.sh
und fügen Sie Folgendes hinzu, um Gradle bei jedem weiteren Login zum PATH hinzuzufügen.```
export GRADLE_HOME=/opt/gradle/gradle-5.2.1
export PATH=${GRADLE_HOME}/bin:${PATH}
um es sofort zu tun, ohne sich abmelden zu müssen``` $ source /etc/profile.d/gradle.sh
Wir können nun damit fortfahren, dragondance zu bauen.```
$ cd dragondance-master/
$ gradle -PGHIDRA_INSTALL_DIR=/home/ubuntu/ghidra_9.1.2_PUBLIC
> Task :buildExtension
Created ghidra_9.1.2_PUBLIC_20200506_dragondance-master.zip in /home/ubuntu/dragondance-master/dist
BUILD SUCCESSFUL in 29s
5 actionable tasks: 5 executed
ubuntu@ubuntu:~/dragondance-master$
wo Sie möglicherweise den Pfad anpassen müssen, zu dem Sie Ghidra heruntergeladen haben. Die erstellte Erweiterung finden Sie nun in dragondance-master/dist
Skriptausführung im Befehlszeilenstil Zeile für Zeile.
Kontextwechsel-Erkennung
Funktionsbasierte (Routinebasierte) Abdeckungsvisualisierung
Ausführungsfluss-Erkennung
Mehr integrierte Funktionen für die Skripterstellung
Ein eigenes Abdeckungsdatenbankformat, um schneller zu speichern und zu laden und die neuesten Änderungen in der Sitzung zu behalten.
Pseudocode-Markierung. (Ghidra bietet keine API dafür. Daher muss ich den Quellcode von Ghidra studieren, um einen Weg oder eine Problemumgehung zu finden, um dies zu erreichen.)
Verbesserungen der Benutzeroberfläche
Oğuz Kartal (@0ffffffffh)
| 0 |
| Beschreibung | Diese eingebaute Funktion löscht die aktuell angezeigte Coverage und setzt die aktive Coverage auf null. |
| Aliase | None |
| Eigenschaft |
|---|
| Beschreibung |
|---|
| Rückgabewert | None |
| Minimale Parameteranzahl | 1 |
| Maximale Parameteranzahl | 1 |
| Beschreibung | Setzt das aktuelle Arbeitsverzeichnis auf den angegebenen Pfad. Alle Importaufrufe ohne absoluten Pfad nach dem cwd suchen die Coverage-Dateien im aktiven Arbeitsverzeichnis. |
| Aliase | None |
| Maximale Parameteranzahl | Unbegrenzt |
| Beschreibung | Wendet die Differenzoperation auf die angegebenen Variablen beliebiger Länge an. Gibt die resultierende Coverage-Variable zurück. |
| Aliase | None |
| Minimale Parameteranzahl | 1 |
| Maximale Parameteranzahl | Unbegrenzt |
| Beschreibung | Zerstört Variablen, egal ob physikalisch oder logisch. Es zerstört zuerst das Coverage-Objekt und meldet dann den Variablennamen aus der Variablenliste ab. Nach diesem Aufruf werden alle angegebenen Variablen undefiniert. |
| Aliase | del |
| Maximale Parameteranzahl | Unbegrenzt |
| Beschreibung | Wendet die Distinct-Operation (XOR) auf die angegebenen Variablen beliebiger Länge an. Gibt die resultierende Coverage-Variable zurück. |
| Aliase | xor |
| 1 |
| Maximale Parameteranzahl | 1 |
| Beschreibung | Lokalisiert die aktuelle Adressauswahl um den angegebenen Offset. Die tatsächliche Adresse wird berechnet, indem der Offset zum Basiswert des Images addiert wird. |
| Aliase | None |
| Eigenschaft | Beschreibung |
|---|
| Rückgabewert | Variable |
| Minimale Parameteranzahl | 1 |
| Maximale Parameteranzahl | 1 |
| Beschreibung | Importiert Coverage-Daten aus der physikalischen Coverage-Datei. Nimmt einen relativen oder absoluten Pfad. Oder einen Coverage-Namen, der zuvor physikalisch geladen wurde. Wenn der angegebene Pfad ein absoluter Pfad ist, lädt import direkt von diesem Pfad. Andernfalls sucht es im aktuellen Arbeitsverzeichnis. In beiden Fällen prüft import, ob die Coverage-Daten bereits geladen wurden (anhand des Pfads). Wenn bereits geladen, wird die gecachte Coverage-Variable zurückgegeben. Wenn der angegebene Wert ein Name einer physikalischen Coverage ist, wird in der Session nach der Coverage-Karte gesucht und das Coverage-Objekt zurückgegeben, falls vorhanden. |
| Aliase | get, load |
| Maximale Parameteranzahl | Unbegrenzt |
| Beschreibung | Wendet die Schnittmengenoperation auf die angegebenen Variablen beliebiger Länge an. Gibt die resultierende Coverage-Variable zurück. |
| Aliase | and |
| Minimale Parameteranzahl | 1 |
| Maximale Parameteranzahl | 1 |
| Beschreibung | Visualisiert die angegebene Coverage-Variable. Falls ein aktiv visualisiertes Coverage-Objekt existiert und dieses ein logisches ist, zerstört die Funktion das vorherige Coverage-Objekt sofort und zeigt das angegebene an. |
| Aliase | None |
| Maximale Parameteranzahl | Unbegrenzt |
| Beschreibung | Wendet die Summenoperation auf die angegebenen Variablen beliebiger Länge an. Gibt die resultierende Coverage-Variable zurück. |
| Aliase | or, union |