- Diese Ghidra-Erweiterung exportiert Teile eines Programms als Objektdatei; die exportierten Dateien enthalten gültige Metadaten wie Symbole und Relocation-Tabellen und können von Toolchains weiterverarbeitet werden
- Die wichtigsten Einsatzbereiche sind fortgeschrittenes Binary Patching, Software-Portierungen, Konvertierung von Dateiformaten, Erstellung von Bibliotheken sowie das Aufteilen eines Programms in mehrere Objektdateien und dessen Neuimplementierung in Decompilation-Projekten
- Unterstützte Kombinationen sind COFF x86/x86_64, ELF x86/x86_64/MIPS und OMF x86; COFF MIPS, OMF x86_64 und OMF MIPS werden nicht unterstützt
- Nutzer wählen im Ghidra Listing den zu extrahierenden Adressbereich aus, führen den Analyzer Relocation table synthesizer aus und rufen anschließend über
File > Export Program…den Exporter für relocatable Objektdateien auf - Die Synthese der Relocation-Tabelle hängt von der Genauigkeit der Ghidra-Datenbank ab; falsche oder fehlende Informationen können zu defekten oder fehlenden Relocations führen, daher ist es sicherer, den Analyzer unmittelbar vor dem Export erneut auszuführen
Was macht diese Erweiterung?
- Object file exporter extension for Ghidra ist eine Ghidra-Erweiterung, mit der sich Teile eines Programms als Objektdatei exportieren lassen
- Die erzeugten Objektdateien enthalten gültige Metadaten wie Symbole und Relocation-Tabellen
- Dank dieser Metadaten können die exportierten Objektdateien direkt in einer Toolchain wiederverwendet und weiterverarbeitet werden
Einsatzszenarien
- Advanced binary patching
- Statt originale und geänderte Teile manuell aufeinander abzustimmen, kann man sie mit einem Linker zusammenfügen lassen
- Software ports
- Systemunabhängiger Code kann aus einem Programm herausgelöst und der Rest ersetzt werden
- Programme oder Objektdateien können von einem Dateiformat in ein anderes konvertiert werden
- Extraktion von Programmteilen und Erstellung von Bibliotheken
- Teile eines Programms können extrahiert und in einem anderen Kontext wiederverwendet werden
- In Decompilation-Projekten kann ein Programm in mehrere Objektdateien aufgeteilt und nach dem Ship-of-Theseus-Prinzip neu implementiert werden
Unterstützte Architekturen und Objektdateiformate
- Die Support-Matrix sieht wie folgt aus
- COFF: x86, x86_64 unterstützt / MIPS nicht unterstützt
- ELF: x86, x86_64, MIPS unterstützt
- OMF: x86 unterstützt / x86_64, MIPS nicht unterstützt
Build und Installation
- Build per CLI
- Repository clonen
- Die Umgebungsvariable
GHIDRA_INSTALL_DIRauf das Ghidra-Installationsverzeichnis setzen gradle buildExtensionausführen- Das erzeugte Ghidra-Erweiterungsarchiv wird im Verzeichnis
dist/erstellt
- Zum Herunterladen von Paketen aus dem GitHub-Maven-Repository ist Authentifizierung erforderlich
- Einen GitHub Classic Token mit der Berechtigung
read:packageserstellen undgithubToken=ghp_xxxzu${GRADLE_USER_HOME}/gradle.propertieshinzufügen - Alternativ
gradle installStandaloneDepsausführen, um die vendored dependencies des Submoduls zu bauen und zu installieren
- Einen GitHub Classic Token mit der Berechtigung
- Installation
- Die Erweiterung von der Release-Seite herunterladen oder lokal bauen
- In Ghidra die Erweiterung über File > Install Extensions… installieren
- Im CodeBrowser-Fenster unter File > Configure > Experimental das Plugin RelocationTableSynthesizedPlugin aktivieren
Nutzungsablauf und Hinweise
- Grundlegender Ablauf
- In der Listing-Ansicht die zu extrahierende Adressmenge auswählen
- Den im One-Shot-Modus bereitgestellten Analyzer Relocation table synthesizer ausführen
- Über File > Export Program… den Exporter für relocatable Objektdateien aufrufen
- Die rekonstruierten Relocations sind unter Window > Relocation table (synthesized) einsehbar
- Detaillierte Bewertungsreports lassen sich aktivieren, indem im Dialog Analysis > Auto Analyze... beim Analyzer Relocation table synthesizer die Option Evaluation report policy gesetzt wird
- Vor der Nutzung der Erweiterung muss nicht das gesamte Programm vorab vollständig reverse-engineered werden
- Erfolgreiches Delinking hängt in der Regel stark von den Metadaten des zu exportierenden Teils und der externen Referenzen ab
- Funktionen und Pointer, die als Relocation-Positionen verwendet werden
- Symbol-Footprints, die als Relocation-Ziele verwendet werden
- Referenzen zwischen beiden
- Der Analyzer Relocation table synthesizer hängt von der Genauigkeit der Ghidra-Datenbank ab
- Ungenaue oder fehlende Informationen können bei der Analyse zu defekten oder fehlenden Relocations führen
- Der Objektdatei-Exporter hängt von den Analyseergebnissen des Relocation table synthesizer ab
- Im Zweifel sollte der Analyzer unmittelbar vor dem Export der Objektdatei ausgeführt werden, um sicherzustellen, dass die Relocation-Tabelle aktuell ist
Funktionsweise
- Eine Objektdatei besteht aus drei Teilen
- Relocatable Section Bytes
-
Symboltabelle
- Relocation-Tabelle
- Aufgaben, die ein Linker ausführt, wenn er aus mehreren Objektdateien eine ausführbare Datei erstellt
- Abschnitte im Speicher platzieren
- Symboladressen im virtuellen Adressraum berechnen
- Relocations anhand der endgültigen Symboladressen auf Section Bytes anwenden
- Normalerweise wird die Relocation-Tabelle nach Abschluss dieses Vorgangs verworfen
- Wenn Debug-Symbole nicht erhalten bleiben, wird auch die Symboltabelle verworfen, sodass nur noch nicht relocatable Section Bytes übrig bleiben
- Diese Erweiterung kann diese Daten durch sorgfältige Analyse wiederherstellen und so ein Programm wieder zu Objektdateien delinken
1 Kommentare
Meinungen auf Hacker News
Schön, das hier zu sehen. Ich halte das für ein wirklich tolles Projekt und habe dabei geholfen, MS-COFF-Unterstützung hinzuzufügen.
Allerdings war mein erster PR im Vergleich zur bereits vorhandenen ELF-Unterstützung ziemlich unvollständig; wenn also Probleme auftreten, könnte das gut an mir liegen. Trotzdem sieht man, dass es besser wird.
Ich habe es noch nicht für größere Arbeiten genutzt, aber am spannendsten war bisher, eine mit Visual Studio 2003 kompilierte Hello-World-EXE zu delinken, sie unter Linux x86 mit GCC+glibc neu zu linken und anschließend noch einmal mit MinGW+msvcrt zu relinken.
Alles, was größer als Hello World ist, ist noch schwierig; vor allem kenne ich Ghidra nicht besonders gut und habe noch keine gute Methode gefunden, in großen Binärdateien den Bereich auszuwählen, der delinkt werden soll.
Passenderweise wurde heute das Nixpkgs-Derivatpaket für dieses Tool gemergt, sodass es unter NixOS unstable mit
ghidra.withExtensionsinstalliert werden kann. Es befindet sich unterghidra-extensions.ghidra-delinker-extension.Vor ein paar Tagen ist allerdings eine neue Version erschienen, und weil ich den PR nicht rebased habe, ist es derzeit noch die alte Version; ich werde demnächst ein Update einreichen.
Wenn man zum Beispiel ein Ghidra-Programm hat, in dem die Namen und Bereiche mehrerer Objektdateien bekannt sind, aus denen die ursprüngliche ausführbare Datei bestand, kann man den entsprechenden Ordner oder das Fragment per Rechtsklick > Select Addresses komplett auswählen.
Auch der Relocation-Synthesis-Analyzer und der Export lassen sich jeweils skripten oder über den Tree Manager des Programms automatisieren, wodurch man seltener den gewünschten Bereich von Hand auswählen und Analyzer sowie Export manuell ausführen muss.
Sieht ziemlich interessant aus und macht mir Lust, ein vor ein paar Jahren aufgegebenes Game-Reverse-Engineering-Projekt wieder hervorzuholen.
Es wäre schön, ein vollständiges Beispiel zu haben, das von Anfang bis Ende zeigt, wie man das verwendet und wie man die Ausgabe weiterverarbeitet.
https://github.com/boricj/ghidra-delinker-extension/blob/mas...
Ich frage mich, wie viel Arbeit es ist, herauszufinden, welche Sections einer ausführbaren Datei exportiert werden sollen.
Ist es realistisch, ein relativ modernes Win32-Spiel etwa aus den Jahren 2008–2015 in Objektdateien zu exportieren und es dann innerhalb weniger Stunden wieder zu einer vollständigen ausführbaren Datei zu kompilieren/linken?
Was exportiert werden soll, ist eine separate Frage und erfordert Wissen über das Programm. Mit Debug-Symbolen ist es deutlich einfacher; aber selbst ohne sie bekommt man, sobald man die Ghidra-Datenbank genau genug erstellt hat, meist ein Gefühl dafür, was wo liegt.
Im in der Einreichung genannten Anwendungsfall wurde das erste Issue Anfang Juli eröffnet, und Mitte August lag eine relinkte ausführbare Datei vor, die funktional identisch lief.
Allerdings gab es damals noch viele Bugs im COFF-Export zu beheben, und auch der i386-Analyzer brauchte Nacharbeit; daher hoffe ich, dass andere inzwischen auf weniger dieser Probleme stoßen.
Wie lange es dauert, weiß ich nicht, aber wenn man keine Debug-Symbole hat und nicht wirklich Glück hat, wird es wahrscheinlich eher länger als ein paar Stunden dauern. Ein erfahrener Reverse Engineer könnte in dieser Zeit vielleicht etwas zum Laufen bringen, aber es könnte auch mitten im ersten Ladebildschirm abstürzen; im Grunde ist es eine Arbeit, bei der man bis zum Schluss nicht weiß, wann sie fertig ist.
Sieht cool aus. Vielleicht kann es eines Tages dabei helfen, bestehende Programme leichter in Teile zu zerlegen und so zu nutzen, wie man es möchte.
Es gibt Forschung wie Metas LLM Compiler oder Decompilation mithilfe von LLMs; Programme aufzuteilen und Teile davon zu ersetzen, könnte ein datenreiches und interessantes Gebiet sein, das LLMs erkunden und selbst verbessern können.
Auch aus Lernperspektive scheinen darin viele interessante Tokens verborgen zu sein, und damit ließe sich offenbar einiges anstellen.
Ehrlich gesagt klingt das wie Magie. Ich muss erst verstehen, wie das möglich ist
Wenn der Linker aus mehreren Objektdateien eine ausführbare Datei erstellt, platziert er die Sections im Speicher, berechnet die Symboladressen im virtuellen Adressraum und wendet dann auf Basis der endgültigen Symboladressen Relocations auf die Section-Bytes an
Der Trick von Delink besteht darin, herauszufinden, wo diese Relocations angewendet wurden, sie rückgängig zu machen und so wieder relokierbare Bytes zu erhalten. Anschließend erstellt es auf Grundlage des Rückgängiggemachten eine Relocation-Tabelle und eine Symboltabelle, verpackt alles, und heraus kommt eine Objektdatei
Der wirklich schwierige Teil ist die Analyse, die Relocation-Stellen zu finden. Das meiste wird Ghidra überlassen, aber Referenzen müssen in Relocation-Stellen umgewandelt werden. Auf x86 ist das vergleichsweise einfach, auf MIPS ist es albtraumhaft schwierig. Diese Erweiterung wurde geschrieben, um die dafür nötigen Daten zu sammeln und das Serialisieren der Objektdatei zu automatisieren
Abgesehen von Microsoft-Plattformen ist das eigentliche Dateiformat oft sogar dasselbe, zum Beispiel bei ELF
Der große Unterschied sind Relocations. Objektdateien enthalten detaillierte Relocation-Informationen, ausführbare Dateien normalerweise nicht. In ausführbaren Images unter Windows gibt es nur minimale Relocations, die auf Code- und Datenadressen zeigen, die korrigiert werden müssen, wenn die ausführbare Datei verschoben wird; das Image selbst kann nur als Ganzes relativ zu seiner Image-Basisadresse relokiert werden
Objektdateien dagegen enthalten Relocations auf Symbolebene. Um diese Informationen korrekt zu rekonstruieren, muss man die Disassemblierung mit ziemlich genauen Informationen über Symbole versehen
Ein weiterer großer Unterschied ist, dass Objektdateien noch nicht gelinkt sind. Kein Symbol ist aufgelöst. Das ist eher leicht zu beheben: Wenn ein Symbol beim Delinken außerhalb des aktuellen Bereichs liegt, wandelt man es im Allgemeinen in ein unaufgelöstes Symbol um. Beim späteren erneuten Linken muss dann eine andere Objektdatei oder Bibliothek dieses Symbol bereitstellen, damit es wieder verbunden wird
Es gibt auch kleinere Unterschiede, etwa dass es keinen Einstiegspunkt zu geben scheint, aber das ist nicht besonders wichtig
Daher sind die Grenzen, an denen man aus einem ausführbaren Image oder Shared Object eine Objektdatei herausschneidet, im Grunde beliebig. Bei der ursprünglichen Kompilierung war es vermutlich in Übersetzungseinheiten aufgeteilt, aber beim Linken spielen diese Grenzen keine große Rolle. Wenn man allerdings eine übereinstimmende Dekompilierung möchte, kann eine falsche Objektdateigrenze es sehr schwierig machen; wenn möglich sollte man sie also herausfinden
Ich habe mich praktisch mit diesem Problem beschäftigt, kann mich bei Details aber leicht irren, also bitte nur als Orientierung verstehen. Ich wollte einmal einen Blogbeitrag über Objektdateien schreiben, und es gibt bereits einige gute Artikel dazu
Ich frage mich, ob dieser Prozess vollständig sicher ist. Ich würde gern wissen, ob der Erfolg immer garantiert ist oder ob die Analyse konservativ arbeitet
Scheitert Delink zum Beispiel, wenn im ELF irgendein Fragment, Daten oder Funktionalität fehlen?
Mein Analyzer stützt sich zumindest für den Teil, der exportiert werden soll, auf eine korrekte Ghidra-Datenbank. Ich habe mir ziemlich viel Mühe gegeben, diverse zu behebende Probleme zu protokollieren, aber was nicht existiert, kann man nicht sehen
Insbesondere fehlende Referenzen und abgeschnittene Variablen werden nicht erkannt und können zu merkwürdigem undefiniertem Verhalten führen
Es gibt Möglichkeiten, einige dieser Probleme aufzuspüren. Die beste Methode, die ich bisher gefunden habe, ist, die ausführbare Datei mit einer anderen Basisadresse neu zu linken und den Adressbereich des ursprünglichen Programms nicht zu mappen. Dann gibt es an übersehenen absoluten Relocation-Stellen einen Segmentation Fault, den man debuggen kann. Das funktioniert allerdings nur, wenn das Ziel eine MMU hat
Abgeschnittene Variablen sind besonders schwer aufzuspüren, wenn man sie nicht ohnehin vermutet. Denn beschädigt wird der Speicher hinter der abgeschnittenen Variablen. Auch Fälle, in denen eine Ganzzahl fälschlich für einen Pointer gehalten wurde, sind schwer nachzuverfolgen. Der Integer-Wert hängt dann von der Adresse ab, an der das Zielsymbol platziert wurde, wodurch sich das Programmverhalten sprunghaft ändert; besonders problematisch ist das bei Programmen, die sehr weit unten im Adressraum geladen werden
Wenn die Ghidra-Datenbank aber hinreichend korrekt ist und man wieder in dasselbe Objektdateiformat exportiert, das ursprünglich verwendet wurde, und dieselbe Plattform sowie dieselbe Toolchain nutzt, dann lassen sich Programmcode und Daten im Megabyte-Bereich erfolgreich delinken. Was der Linker getan hat, sollte man meiner Ansicht nach auch rückgängig machen können
Anders sieht es aus, wenn man mit Cross-Delinking beginnt, das nicht zur Plattform und Toolchain des ursprünglichen Programms passt, etwa indem man aus einer Linux-i386-ELF-Datei eine COFF-Objektdatei delinkt, um sie mit einer i386-Windows-Toolchain zu verwenden. Wenn der Export die Relocations ausdrücken kann, erhält man vielleicht eine funktionierende relokierbare Objektdatei, aber man muss zusätzlich mit ABI-Inkompatibilitäten kämpfen. Möglich ist es, aber ich würde es nicht als erstes Projekt empfehlen
Kurz gesagt reicht die Spanne je nachdem, was man macht und wie korrekt die Ghidra-Datenbank ist, von „funktioniert einfach“ bis „Cthulhu um Gnade anflehen“
Man kann sich zum Beispiel eine Funktion wie
int getSpecialArrayElement(char *array, uint64 key) { i = computeOffset(key); return array[i]; }vorstellencomputeOffsetkann beliebig komplex sein und, wenn man Obfuskation möchte, absichtlich schwer analysierbar gemacht werden. Nichts hindert diese Funktion daran, auf beliebige Speicherstellen zuzugreifenSolange man nicht alle möglichen Eingaben ausprobiert, kann man nicht wissen, ob sie auf ein Symbol zugreift, das der Linker bereits im Speicher definiert hat, und
computeOffsetkann auch Turing-Fallen enthalten, die solche Versuche verhindernEs wäre interessant, das mit einer Idee zu verbinden, die ich früher nur durchgespielt, aber nie tatsächlich umgesetzt habe: Header-Dateien aus Debug-Informationen zu erzeugen und sie bei Bedarf von einem LLM aufräumen zu lassen.
https://github.com/wbenny/pdbex
Was den Teil mit dem Aufräumen per LLM angeht, scheint es bisher nicht viele große Erfolge beim Einsatz von LLM-Modellen im Reverse Engineering zu geben. Vielleicht ist dieser Bereich auch einer, in dem die Grenzen der LLM-Architektur sichtbar werden
Ich bin kein Experte, aber wenn ich wetten müsste, wären für viele Reverse-Engineering-Anwendungsfälle Diffusionsmodelle wohl interessanter
Nicht ganz dasselbe, aber Binary Ninja hat eine Funktion namens Sidekick, die versucht, Disassemblies mit einem LLM aufzuräumen. Persönlich fand ich sie nicht besonders beeindruckend, aber für manche könnte sie nützlich sein
paholekann aus ELF-DWARF-Informationen kompilierbare C-Header-Dateien erzeugenHier scheint ein LLM nicht besonders relevant zu sein. Entweder enthält die Header-Datei alle vom Executable exportierten Typen korrekt und mit ihren ursprünglichen Werten und ist damit verwendbar, oder sie ist falsch beziehungsweise unvollständig. Es hilft nicht, wenn ein LLM noch etwas dazuerfindet
Auch Ghidra hat eine eingebaute Funktion zum Exportieren von Datenstrukturen und kann sie aus DWARF-Strukturen erzeugen. Rechtsklick -> Export to C header
Header-Erzeugung und anschließend auch die Erzeugung von Mutatoren, die sich gemäß den Typeinschränkungen anpassen lassen, waren Teil davon
Wenn man LLMs ergänzt, könnte man damit vermutlich anonymen Structs Namen geben, aber ich weiß nicht, ob das eine gute Idee ist. Interessanter wäre vielleicht ein Versuch, LLMs bekannte Typeinschränkungen zu Dokumentationszwecken in Worten zusammenfassen zu lassen
Der Grund, warum ich es noch nicht implementiert habe, ist, dass ich bisher auch ohne ausgekommen bin. Außerdem klingt es nach einem ziemlich tiefen Kaninchenbau, und der Kaninchenbau, in dem ich gerade stecke, ist schon groß genug
Das sieht wirklich cool aus und hängt auch mit einer Game-Modding-Idee zusammen, die ich früher im Kopf hatte. Die Blogserie zur Tenchu-Dekompilierung war ebenfalls gut
Für meine aktuelle Arbeit habe ich im Moment keinen direkten Anwendungsfall, aber es wirkt wie ein Tool, das früher für mich wirklich nützlich gewesen wäre
Ich hoffe, dass ich bald Zeit oder eine Gelegenheit finde, es auszuprobieren