Wer hat schon einmal Software or Hardware Reverse Engineering betrieben?
Wer kennt Java Bytecode?
Wer hat Erfahrung mit Python?
Reverse Engineering
Reverse Engineering ist die Analyse von Systemen mit dem Ziel, ihren Aufbau und ihre Funktionsweise zu verstehen.
Typische Anwendungsfälle:
die Rekonstruktion (von Teilen) des Quellcodes von Programmen, die nur als Binärabbild vorliegen.
die Analyse von Kommunikationsprotokollen proprietärer Software
Vom Reverse Engineering ist das Reengineering zu unterscheiden. Im Fall von letzteren geht es „nur“ darum die Funktionalität eines bestehenden Systems mit neuen Techniken wiederherzustellen.
Zweck von Reverse Engineering
Herstellung von Interoperabilität
Untersuchung auf Schwachstellen
Untersuchung auf Copyrightverletzungen
Untersuchung auf Backdoors
Analyse von Viren, Würmern etc.
Umgehung von ungerechtfertigten(?) Schutzmaßnahmen (z. B. bei Malware)
Die Schwachstelle verschafft Angreifern Zugang zu einer der höchsten Privilegienstufen heutiger PC-Systeme. Schadsoftware entzieht sich damit jeglicher Erkennung.
[...] Gegenüber Wired erklärten die Forscher, per Sinkclose ließen sich etwa Bootkits installieren, die für das Betriebssystem und gängige Antivirensoftware unsichtbar seien, während Angreifer einen Vollzugriff auf das Zielsystem erhielten.
Das Verhalten von SSH bei der Authentifikation so zu verändern, dass es dem Angreifer Zugang zum System erlaubt.
Zur Absicherung der Backdoor ist diese über ein Zertifikat abgesichert.
Verbreitung des Schadcode?
Die Bibliothek liblzma wurde so angepasst, dass diese eine Backdoor in SSH einbaut.
Der Schadcode ist nur in den Tarballs zu finden - nicht im SourceCode im GIT. Der eigentliche Schadcode wurde versteckt in Testfixtures.
Der Code wurde so entworfen, dass bekannte Werkzeuge (Valgrind) keine Probleme erkennen.
Die Bibliothek wurde nur in bestimmten Situationen von OpenSSH verwendet.
Bewertung
CVSS Base Score: 10.0 (kritisch)
Entstandener Schaden: vermutlich gering, da (gerade noch) keine offiziellen Releases (von Debian, Ubuntu, etc.) betroffen waren.
Dem Angriff ging ein sehr langer Social Engineering Angriff voraus, weswegen mit höherer Wahrscheinlichkeit ein „State-sponsored Actor“ dahintersteckt.
Angreifer können aus dem lokalen Netzwerk heraus den Telnet-Dienst betroffener D-Link-Router durch Angabe einer bestimmten Ziel URL aktivieren.
Die Admin-Zugangsdaten sind in der Firmware hinterlegt.
Vermutlich ursprünglich für werksseitige Tests.
CVSS Base Score: 8.8 (hoch)
Reverse Engineering - grundlegende Schritte
Informationsgewinnung zur Gewinnung aller relevanten Informationen über das Produkt.
Modellierung mit dem Ziel der (Wieder-)Gewinnung eines (abstrakten) Modells der relevanten Funktionalität.
Überprüfung (review) des Modells auf seine Richtigkeit und Vollständigkeit.
Informationsgewinnung - Beispiel
Gegeben sei eine App zum Ver- und Entschlüsseln von Dateien sowie ein paar verschlüsselte Dateien. Mögliche erste Schritte vor der Analyse von Binärcode:
Die ausführbare Datei ggf. mit file (oder sogar mit binwalk) überprüfen (z. B. wie wurde die Datei kompiliert und für welches Betriebssystem und Architektur)
Die Dateien mit einem (guten) Hexeditor auf Auffälligkeiten untersuchen.
Die Datei auf bekannte Viren und Malware überprüfen.
Eine Datei mit einem bekannten Inhalt verschlüsseln und danach vergleichen.
Ist die Datei gleich groß?
Falls ja, dann werden keine Metainformationen gespeichert und das Passwort kann (ggf.) nicht (leicht) verifiziert werden.
(Es kann zumindest nicht direkt in der Datei gespeichert sein.)
Eine Datei mit verschiedenen Passworten verschlüsseln.
Sind die Dateien gleich?
Falls ja, dann wäre die Verschlüsselung komplett nutzlos und es gilt nur noch den konstanten Schlüssel zu finden.
Gibt es Gemeinsamkeiten?
Falls ja, dann wäre es möglich, dass das Passwort (gehasht) in der Datei gespeichert wird.
Eine Datei mit einem wohldefinierten Muster verschlüsseln, um ggf. den „Mode of Operation“ (insbesondere ECB) zu identifizieren.
Mehrere verschiedene Dateien mit dem gleichen Passwort verschlüsseln
Gibt es Gemeinsamkeiten?
Falls ja, dann wäre es möglich, dass die entsprechenden Teile direkt vom Passwort abgeleitet werden/damit verschlüsselt werden.
...
Reverse Engineering der App durchführen.
Rechtliche Aspekte des Reverse Engineering
Die Rechtslage hat sich in Deutschland mehrfach geändert.
Umgehung von Kopierschutzmechanismen ist im Allgemeinen verboten.
Lizenzen verbieten das Reverse Engineering häufig; es stellt sich aber die Frage nach der Rechtmäßigkeit der Klauseln.
Software Reverse Engineering
Ansätze
statische Analyse:
Studieren des Programms ohne es auszuführen; typischerweise mittels eines Disassemblers oder eines Decompilers.
dynamische Analyse:
Ausführen des Programms; typischerweise unter Verwendung eines Debuggers oder eines instrumentations Frameworks (z. B. Frida).
hybride Analyse:
Kombination aus statischer und dynamischer Analyse.
Ansätze wie Unicorn, welches auf QEmu aufbaut, erlaubt zum Beispiel die Ausführung von (Teilen von) Binärcode auf einer anderen Architektur als der des Hosts.
Ein Beispiel wäre die Ausführung einer Methode, die im Code verschlüsselte hinterlegte Strings entschlüsselt (deobfuscation), um die Analyse zu vereinfachen.
Ggf. müssen für Teile des Codes, die die Hostfunktionalität nutzen, Stubs/Mocks bereitgestellt werden.
Disassembler
Überführt (maschinenlesbaren) Binärcode in Assemblercode
Kommandozeilenwerkzeuge (exemplarisch):
objdump -d
gdb
radare
javap (für Java)
Decompiler
Überführt (maschinenlesbarem) Binärcode bestmöglich in Hochsprache (meist C ähnlich oder Java). Eine kleine Auswahl von verfügbaren Werkzeugen:
Hex-Rays IDAPro (kommerziell)
Ghidra (unterstützt fast jede Platform; die Ergebnisse sind sehr unterschiedlich.)
JadX (Androids .dex Format)
CFR (Java .class Dateien)
IntelliJ
Mittels Decompiler ist es ggf. möglich Code, der zum Beispiel ursprünglich in Kotlin oder Scala geschrieben und für die JVM kompiliert wurde, als Java Code zurückzubekommen.
Die Ergebnisse sind für Analysezwecke zwar häufig ausreichend gut – von funktionierendem Code jedoch ggf. (sehr) weit entfernt.
decompiler.com unterstützt eine große Anzahl ausführbaren Dateien.
cfr Decompiler
JD Decompiler
Beispiel fehlgeschlagener Dekompilierung
JDec Decompiler
Debugger
Dient der schrittweisen Ausführung des zu analysierenden Codes oder Hardware; ermöglichen zum Beispiel Speicherinspektion und Manipulation.
gdb
lldb
x64dbg (Windows, Open-Source)
jdb (Java Debugger)
Hardware Debugger
Für das Debuggen von Hardware gibt es entsprechende Werkzeuge, z. B.
Lauterbach Hardware Debugger (kommerziell und sehr teuer).
Mittels solcher Werkzeuge ist es möglich die Ausführung von Hardware Schritt für Schritt (single step mode`) zu verfolgen und den Zustand der Hardware (Speicher und Register) zu inspizieren. Dies erfordert jedoch häufig eine JTAG Schnittstelle oder etwas vergleichbares.
Erschwerung des Reverse Engineering
Obfuscation (Verschleierung)
Obfuscation → für Menschen unverständlich Code
Techniken, die dazu dienen das Reverse Engineering zu erschweren.
Häufig eingesetzt ...
von Malware
Adware (im Kontext von Android ein häufig beobachtetes Phänomen)
zum Schutz geistigen Eigentums
für DRM / Durchsetzung von Kopierrechten
zur Prävention von „Cheating“ (insbesondere im Umfeld von Online Games)
Wenn das Programm als Source Code vertrieben wird bzw. vertrieben werden muss (JavaScript)
Arbeiten auf Quellcode oder Maschinencode Ebene
Grenze zwischen Code Minimization, Code Optimization und Code Obfuscation ist fließend.
Mögliche Werkzeuge (ohne Wertung der Qualität/Effektivität):
Gerade im Umfeld von klassischen Binaries für Windows, Mac und Linux erhöhen Compiler Optimierungen, z. B. von C/C++ und Rust Compilern (-O2 / -O3), bereits den Aufwand, der notwendig ist den Code zu verstehen, erheblich.
Obfuscation - Techniken (Auszug)
entfernen aller Debug-Informationen
Das Kürzen aller möglichen Namen (insbesondere Methoden und Klassennamen).
Das Verschleiern von Konstanten durch den Einsatz vermeintlich komplexer Berechnungen zu deren Initialisierung.
Das Umstellen von Instruktionen erschwert die Analyse, da viele Werkzeuge zum Dekompilieren auf die Erkennung von bestimmten Mustern im Code angewiesen sind und ansonsten nur sehr generischen (Spagetti Code) oder gar unsinnigen Code zurückgeben.
Verschleierung von Strings
Das Verschleiern von Strings kann insbesondere das Reversen von Binärcode erschweren, da ein Angreifer häufig „nur“ an einer ganz bestimmten Funktionalität interessiert ist und dann Strings ggf. einen sehr guten Einstiegspunkt für die weitergehende Analyse bieten.
Stellen Sie sich eine komplexe Java Anwendung vor, in der alle Namen von Klassen, Methoden und Attributen durch einzelne oder kurze Sequenzen von Buchstaben ersetzt wurden und sie suchen danach wie von der Anwendung Passworte verarbeitet werden. Handelt es sich um eine GUI Anwendung, dann wäre zum Beispiel die Suche nach Text, der in den Dialogen vorkommt (z. B. "Password") z. B. ein sehr guter Einstiegspunkt.
Verschlüsselung von Bytecode und Java Class Loader
ClassLoader
ClassLoader dienen dazu Klassen dynamisch zu laden. D. h. eine Klasse wird erst dann von der JVM geladen, wenn sie benötigt bzw. angefordert wird.
Jeder ClassLoader spannt seinen eigenen Namensraum auf.
Zwei Instanzen der gleichen Klasse (d. h. mit dem selben Bytecode) sind nicht gleich (Referenzgleichheit), wenn zwei verschiedene ClassLoader genutzt wurden.
ClassLoader stehen in einer Hierarchie.
ClassLoader können genutzt werden, um:
ein Programm dynamisch zu erweitern (Plug-ins
um Klassen zu laden, die zur Laufzeit generiert wurden
um den Bytecode zu manipulieren, bevor er von der JVM ausgeführt wird.
Ein eigener ClassLoader
staticclassMyClassLoaderextendsClassLoader{publicMyClassLoader(ClassLoaderparent){super(parent);}@OverrideprotectedClass<?>findClass(finalStringname)throwsClassNotFoundException{try(finalvarin=super.getResourceAsStream(name)){finalvarclassBytes=newbyte[in.available()];finalvarreadBytes=in.read(classBytes);if(readBytes!=classBytes.length){thrownewIOException("failed reading class file: "+name);}returndefineClass(name,classBytes,0,classBytes.length);}catch(IOExceptionioe){thrownewClassNotFoundException("failed loading "+name,ioe);}}}
Java Bytecode ist die Sprache, in der Java (oder Scala, Kotlin, ...) Programme auf der Java Virtual Machine (JVM) [4] ausgeführt werden.
In den meisten Fällen arbeiten Java Decompiler so gut, dass ein tiefgehendes Verständnis von Java Bytecode selten notwendig ist.
Java Bytecode kann — muss aber nicht — interpretiert werden. (Z. B. können „virtuelle Methodenaufrufe“ in Java schneller sein als in C++.)
Java Bytecode - stackbasierte virtuelle Maschine
Die JVM ist eine stackbasierte virtuelle Maschine.
Die getypten Operanden eines Befehls werden auf einem Stack abgelegt und die Operationen arbeiten auf den obersten Elementen des Stacks. Jeder Thread hat seinen eigenen Stack.
Instruktionen
nopbipush100→intbipush50→intiadd←2⨉int→int
Veränderung des Stacks
└─────┘│100│└─────┘│50││100│└─────┘│150│└─────┘
Eine Methode muss einen Stack begrenzter Höhe aufweisen. Code, für den die Stackhöhe nicht berechenbar ist, wird vom Compiler abgelehnt. (Zum Beispiel ein bipush in einer Endlosschleife.)
Die benötigte Höhe des Stacks wird vom Compiler berechnet und von der JVM überprüft.
Java Bytecode - Methodenaufrufe und lokale Variablen
Die Java Virtual Machine verwendet lokale Variablen zur Übergabe von Parametern beim Methodenaufruf.
Beim Aufruf von Klassenmethoden (static) werden alle Parameter in aufeinanderfolgenden lokalen Variablen übergeben, beginnend mit der lokalen Variable 0.
D. h. in der aufrufenden Methode werden die Parameter vom Stack geholt und in lokalen Variablen gespeichert.
Beim Aufruf von Instanzmethoden wird die lokale Variable 0 dazu verwendet, um die Referenz (this) auf das Objekt zu übergeben, auf dem die Instanzmethode aufgerufen wird.
Anschließend werden alle Parameter in aufeinanderfolgenden lokalen Variablen übergeben, beginnend mit der lokalen Variable 1.
Die Anzahl der benötigten lokalen Variablen wird vom Compiler berechnet und von der JVM überprüft.
Beispiel: Default Constructor In Java Bytecode
Ein Constructor welcher keine expliziten Parameter hat und nur den super Konstruktor aufruft.
Es gibt weitere Metainformationen, die „nur“ für Debugging-Zwecke benötigt werden, z. B. Informationen über die ursprünglich Quelle des Codes oder die sogenannte "Local Variable Type Table" in Hinblick auf generische Typinformationen. Solche Informationen werden häufig vor Auslieferung entfernt bzw. nicht hineinkompiliert.
In einigen Anwendungsgebieten ist es möglich auf das explizite Speichern von Passwörtern ganz zu verzichten[*].
Stattdessen wird z. B. einfach versucht das Ziel zu entschlüsseln und danach evaluiert ob das Passwort (vermutlich) das Richtige war.
Kann darauf verzichtet werden zu überprüfen ob das Passwort korrekt war, dann sind keine Metainformationen notwendig und die verschlüsselte Datei kann genau so groß sein wie die unverschlüsselte Datei.
Schematische Darstellung der Verschlüsselung von Containern (z. B. Veracrypt)
Generische Dateiverschlüsselung ohne explizite Speicherung des Passworts