Schreiben eines Wasm Loader für Ghidra. Teil 1: Problemstellung und Einrichtung der Umgebung


Diese Woche machte die NSA ( National Security Agency ) plötzlich ein Geschenk an die Menschheit und öffnete Quellen für ihr Software-Reverse-Engineering-Framework. Die Community der Reverse Engineers und Sicherheitsexperten begann mit großer Begeisterung, das neue Spielzeug zu erkunden. Dem Feedback zufolge ist es ein wirklich erstaunliches Tool, das mit vorhandenen Lösungen wie IDA Pro, R2 und JEB konkurrieren kann. Das Tool heißt Ghidra und professionelle Ressourcen sind voller Eindrücke von Forschern. Eigentlich hatten sie einen guten Grund: Nicht jeden Tag bieten Regierungsorganisationen Zugang zu ihren internen Instrumenten. Ich selbst als professioneller Reverse Engineer und Malware-Analyst konnte nicht vorbeikommen. Ich beschloss, ein oder zwei Wochenenden zu verbringen und mir einen ersten Eindruck vom Tool zu verschaffen. Ich hatte ein bisschen mit der Demontage gespielt und beschlossen, die Erweiterbarkeit des Tools zu überprüfen. In dieser Artikelserie werde ich die Entwicklung des Ghidra-Add-Ons erläutern, das ein benutzerdefiniertes Format lädt, das zur Lösung der CTF-Aufgabe verwendet wird. Da es sich um ein großes Framework handelt und ich eine ziemlich komplizierte Aufgabe gewählt habe, werde ich den Artikel in mehrere Teile aufteilen.

Am Ende dieses Teils hoffe ich, die Entwicklungsumgebung einzurichten und ein minimales Modul zu erstellen, das das Format der WebAssembly-Datei erkennen und den richtigen Disassembler für die Verarbeitung vorschlagen kann.

Beginnen wir mit der Aufgabenbeschreibung. Letztes Jahr veranstaltete das Sicherheitsunternehmen FireEye einen CTF-Wettbewerb mit dem Namen Flare-On. Während des Wettbewerbs mussten die Forscher zwölf Aufgaben im Zusammenhang mit dem Reverse Engineering lösen. Eine der Aufgaben bestand darin, die mit WebAssembly erstellte Webanwendung zu untersuchen. Es ist ein relativ neues ausführbares Format, und soweit ich weiß, gibt es keine perfekten Werkzeuge, um damit umzugehen. Während der Herausforderung habe ich verschiedene Tools ausprobiert, um sie zu besiegen. Dies waren einfache Skripte von Github und bekannten Dekompilierern wie IDA Pro und JEB. Überraschenderweise habe ich mich für Chrome entschieden, das einen ziemlich guten Disassembler und Debugger für WebAssembly bietet. Mein Ziel ist es, die Herausforderung mit der Ghidra zu lösen. Ich werde die Studie so vollständig wie möglich beschreiben und alle möglichen Informationen geben, um meine Schritte zu reproduzieren. Vielleicht gehe ich als Person, die nicht viel Erfahrung mit dem Instrument hat, auf einige unnötige Details ein, aber so ist es.

Die Aufgabe, die ich für das Studium verwenden werde, kann von der flareon5-Herausforderungsseite heruntergeladen werden . Es gibt die Datei 05_web2point0.7z: Archiv, verschlüsselt mit einem unheimlichen Wort, das infiziert ist . Das Archiv enthält drei Dateien: index.html, main.js und test.wasm. Öffnen wir die Datei index.html in einem Browser und überprüfen das Ergebnis:



Nun, damit werde ich arbeiten. Beginnen wir mit dem HTML-Studium, zumal es der einfachste Teil der Herausforderung ist. Der HTML-Code enthält nichts außer dem Laden des Skripts main.js.

<!DOCTYPE html> <html> <body> <span id="container"></span> <script src="./main.js"></script> </body> </html> 

Das Skript macht auch nichts Kompliziertes, obwohl es etwas ausführlicher aussieht. Es lädt nur die Datei test.wasm und erstellt daraus eine WebAssembly-Instanz. Dann liest es den Parameter "q" aus der URL und übergibt ihn an die von der Instanz exportierte Methodenübereinstimmung. Wenn die Zeichenfolge im Parameter falsch ist, zeigt das Skript das oben gezeigte Bild in Bezug auf FireEye-Entwickler mit dem Namen "Pile of Poo".

  let b = new Uint8Array(new TextEncoder().encode(getParameterByName("q"))); let pa = wasm_alloc(instance, 0x200); wasm_write(instance, pa, a); let pb = wasm_alloc(instance, 0x200); wasm_write(instance, pb, b); if (instance.exports.Match(pa, a.byteLength, pb, b.byteLength) == 1) { // PARTY POPPER document.getElementById("container").innerText = "🎉"; } else { // PILE OF POO document.getElementById("container").innerText = "ðŸ'"; } 

Die Lösung der Aufgabe besteht darin, den Wert des Parameters q zu ermitteln, mit dem die Funktion "übereinstimmt" und "Wahr" zurückgibt. Dazu zerlege ich die Datei test.wasm und analysiere den Algorithmus der Funktion Match.

Es gibt keine Überraschungen und ich werde versuchen, es in Ghidra zu tun. Aber zuerst muss ich es installieren. Die Installation kann (und sollte) von https://ghidra-sre.org/ heruntergeladen werden. Da es in Java geschrieben ist, gibt es fast keine besonderen Anforderungen an die Installation, es erfordert keine besonderen Anstrengungen bei der Installation. Sie müssen lediglich das Archiv entpacken und die Anwendung ausführen. Sie müssen lediglich JDK und JRE auf Version 11 aktualisieren.

Erstellen wir ein neues Ghidra-Projekt ( Datei-> Neues Projekt ) und nennen es "wasm" /



Fügen Sie dann die Datei test.wasm ( Datei → Datei importieren) hinzu, um zu projizieren, und sehen Sie, wie ghidra damit umgehen kann



Nun, es kann nichts tun. Es erkennt kein Format und kann nichts zerlegen, daher ist es absolut machtlos, diese Aufgabe zu erledigen. Endlich sind wir zum Thema des Artikels gekommen. Es bleibt nichts anderes zu tun, als ein Modul zu schreiben, mit dem die WASM-Datei geladen, analysiert und der Code zerlegt werden kann.

Zunächst habe ich alle verfügbaren Dokumentationen studiert. Tatsächlich gibt es nur ein geeignetes Dokument, das den Prozess der Entwicklung von Add-Ons zeigt: Folien GhidraAdvancedDevelopment. Ich werde dem Dokument folgen und Schlag für Schlag beschreiben.

Leider erfordert die Entwicklung von Add-Ons die Verwendung von Eclipse. Alle meine Erfahrungen mit Eclipse sind die Entwicklung von zwei GDX-Spielen für Android im Jahr 2012. Es waren zwei Wochen voller Schmerzen und Leiden, nach denen ich sie aus meinem Kopf löschte. Hoffe nach 7 Jahren Entwicklung ist es besser als früher.

Lassen Sie uns Eclipse von der offiziellen Website herunterladen und installieren.

Installieren Sie dann die Erweiterung für die Ghidra-Entwicklung:

Gehen Sie zur Eclipse- Hilfe → Menü Neue Software installieren , klicken Sie auf die Schaltfläche Hinzufügen und wählen Sie GhidraDev.zip unter / Extensions / Eclipse / GhidraDev /. Installieren Sie es und starten Sie die Erweiterung neu. Die Erweiterung, die dem neuen Projektmenü Vorlagen hinzufügt, ermöglicht das Debuggen von Modulen aus Eclipse und das Kompilieren von Modulen in das Distributionspaket.

Wie aus den Entwicklerdokumenten hervorgeht, müssen die folgenden Schritte ausgeführt werden, um ein Modul für die Verarbeitung des neuen Binärformats hinzuzufügen:

  • Erstellen Sie Klassen, die Datenstrukturen beschreiben
  • Lader entwickeln. Der Loader sollte von der Klasse AbstractLibrarySupportLoader geerbt werden. Es liest alle erforderlichen Daten aus der Datei, überprüft die Datenintegrität und konvertiert die Binärdaten in eine interne Darstellung, um sie für die Analyse vorzubereiten
  • Analysator entwickeln. Analyzer wird von der Klasse AbstractAnalyzer geerbt. Es nimmt die vom Loader vorbereiteten Datenstrukturen und kommentiert sie (ich bin mir nicht sicher, was das bedeutet, aber ich hoffe, sie während der Entwicklung zu verstehen).
  • Prozessor hinzufügen. Ghidra hat eine Abstraktion: Prozessor. Es ist in interner deklarativer Sprache geschrieben und beschreibt den Befehlssatz, das Speicherlayout und andere architektonische Merkmale. Ich werde dieses Thema behandeln und den Disassembler schreiben.

Wenn wir nun alle notwendigen Theorien haben, ist es Zeit, das Modulprojekt zu erstellen. Dank der zuvor installierten Eclipse-Erweiterung GhidraDev haben wir die Modulvorlage direkt im Menü Datei-> Neues Projekt .



Der Assistent fragt, welche Komponenten erforderlich sind. Wie bereits beschrieben, benötigen wir zwei davon: Lader und Analysator.



Der Assistent erstellt ein Projektskelett mit allen erforderlichen Teilen: leerer Analysator in der Datei WasmAnalyzer.java, leerer Loader in der Datei WasmLoader.java und Sprachskelett in Verzeichnis / Daten / Sprachen.



Beginnen wir mit dem Lader. Wie bereits erwähnt, sollte es von der Klasse AbstractLibrarySupportLoader geerbt werden und drei Methoden zum Überladen haben:

  • getName - Diese Methode sollte den internen Namen des Loaders haben. Ghidra verwendet es an verschiedenen Stellen, um beispielsweise den Loader an den Prozessor zu binden
  • findSupportedLoadSpecs - Rückruf, ausgeführt, wenn der Benutzer die zu importierende Datei ausgewählt hat. In diesem Rückruf sollte der Loader entscheiden, ob er die Datei verarbeiten und die Instanz der Klasse LoadSpec zurückgeben kann, und dem Benutzer mitteilen, wie die Datei verarbeitet werden kann
  • load - Rückruf, ausgeführt, nachdem der Benutzer die Datei geladen hat. Bei dieser Methode analysiert der Loader die Dateistruktur und lädt sie in Ghidra. Wird im nächsten Artikel ausführlicher beschrieben

Die erste und einfachste Methode ist getName. Sie gibt nur den Namen des Loaders zurück

  public String getName() { return "WebAssembly"; } 

Die zweite zu implementierende Methode ist findSupportedLoadSpecs. Es wird vom Tool während des Imports der Datei aufgerufen und sollte überprüfen, ob der Loader die Datei verarbeiten kann. Wenn die Methode able in der Lage ist, ein Objekt der LoadSpec- Klasse zurückzugeben, wird angegeben, welches Objekt zum Laden der Datei verwendet wird und welcher Prozessor den Code zerlegt.

Die Methode beginnt mit der Formatüberprüfung. Wie aus der Spezifikation hervorgeht , sollten die ersten acht Bytes der WASM-Datei die Signatur "\ 0asm" und die Version sein.

Um den Header zu analysieren, habe ich die Klasse WasmHeader erstellt und die Schnittstelle StructConverter implementiert, die als Basisschnittstelle zur Beschreibung strukturierter Daten dient. Der Konstruktor des WasmHeader empfängt das Objekt BinaryReader - Abstraktion, mit dem Daten aus der zu analysierenden Binärquelle gelesen werden. Der Konstruktor verwendet es, um den Header der Eingabedatei zu lesen

  private byte[] magic; private byte [] version; public WasmHeader(BinaryReader reader) throws IOException { magic = reader.readNextByteArray(WASM_MAGIC_BASE.length()); version = reader.readNextByteArray(WASM_VERSION_LENGTH); } 

Loader verwendet dieses Objekt, um die Signatur der Datei zu überprüfen. Sucht dann im Erfolgsfall nach dem geeigneten Prozessor. Es ruft die Methodenabfrage der Klasse QueryOpinionService auf und übergibt ihr den Namen des Loaders ("Webassembly"). OpinionService sucht nach einem Prozessor, der diesem Loader zugeordnet ist, und gibt ihn zurück.

 List<QueryResult> queries = QueryOpinionService.query(getName(), MACHINE, null); 

Sicher, es gibt nichts zurück, weil Ghidra den Prozessor WebAssembly nicht kennt und ihn definieren muss. Wie bereits erwähnt, hat der Assistent das Sprachskelett in Verzeichnisdaten / -sprachen erstellt.



Derzeit gibt es zwei interessante Dateien: Webassembly.opinion und Wbassembly.ldefs. Die Datei .opinon legt die Entsprechung zwischen Loader und Prozessor fest.

 <opinions> <constraint loader="WebAssembly" compilerSpecID="default"> <constraint primary="1" processor="Webassembly" size="16" /> </constraint> </opinions> 

Es enthält einfache XML mit wenigen Attributen. Der Name des Loaders muss in das Attribut "Loader" und der Name des Prozessors in das Attribut "Prozessor" gesetzt werden. Beide sind "Webassembly". In diesem Schritt fülle ich andere Parameter mit den Zufallswerten. Sobald ich mehr über die Architektur des Webassembly-Prozessors weiß, werde ich sie in korrekte Werte ändern.

Datei .ldefs beschreibt Funktionen des Prozessors, die Code aus der Datei ausführen sollen.

 <language_definitions> <language processor="Webassembly" endian="little" size="16" variant="default" version="1.0" slafile="Webassembly.sla" processorspec="Webassembly.pspec" id="wasm:LE:16:default"> <description>Webassembly Language Module</description> <compiler name="default" spec="Webassembly.cspec" id="default"/> </language> </language_definitions> 

Das Attribut "Prozessor" sollte mit dem Attributprozessor aus der Datei .opinion identisch sein. Lassen wir andere Felder unberührt. Denken Sie jedoch beim nächsten Mal daran, dass es möglich ist, die Registrierungsbittness (Attribut "Größe"), die Datei, die die Architektur des Prozessors "Prozessorspezifikation" beschreibt, und die Datei festzulegen, die die Beschreibung des Codes in der speziellen deklarativen Sprache "slafile" enthält. Es ist praktisch, an der Demontage zu arbeiten.

Jetzt ist es Zeit, zum Lader zurückzukehren und die Spezifikation des Laders zurückzugeben.

Alles ist bereit für den Testlauf. Das Plugin für GhidraDev hat die Ausführungsoption " Ausführen → Ausführen als → Ghidra " zur Sonnenfinsternis hinzugefügt:



Es führt ghidra im Debug-Modus aus und stellt das dortige Modul bereit. Dies bietet eine hervorragende Möglichkeit, mit dem Tool zu arbeiten und gleichzeitig den Debugger zu verwenden, um Fehler im zu entwickelnden Modul zu beheben. In dieser einfachen Phase gibt es jedoch keinen Grund, einen Debugger zu verwenden. Nach wie vor werde ich ein neues Projekt erstellen, eine Datei importieren und sehen, ob sich meine Bemühungen gelohnt haben. Im Gegensatz zum letzten Mal wird die Datei als WebAssembly erkannt, und der Loader schlägt einen entsprechenden Prozessor dafür vor. Das heißt, alles funktioniert und mein Modul kann das Format erkennen.



Im nächsten Artikel werde ich den Loader erweitern und ihn nicht nur erkennen, sondern auch die Struktur der WASM-Datei beschreiben lassen. Ich denke, in diesem Stadium, nachdem die Umgebung eingerichtet wurde, wird es einfach sein, dies zu tun.

Der Code des Moduls ist im Github- Repository verfügbar.

Source: https://habr.com/ru/post/de443318/


All Articles