So verwenden Sie dynamisches Reverse Engineering für eingebettete Geräte

Blog

HeimHeim / Blog / So verwenden Sie dynamisches Reverse Engineering für eingebettete Geräte

May 21, 2024

So verwenden Sie dynamisches Reverse Engineering für eingebettete Geräte

Die Verbreitung des IoT geht mit einer Zunahme von Sicherheitslücken einher. Wenn sie nicht kontrolliert werden, können böswillige Angreifer diese Schwachstellen ausnutzen, um in die Systeme von Unternehmen einzudringen. Regulär

Die Verbreitung des IoT geht mit einer Zunahme von Sicherheitslücken einher. Wenn sie nicht kontrolliert werden, können böswillige Angreifer diese Schwachstellen ausnutzen, um in die Systeme von Unternehmen einzudringen.

Regelmäßige Penetrationstests, die seit langem als bewährte Sicherheitsmethode gelten, helfen Sicherheitsteams dabei, Schwachstellen und Schwachstellen in eingebetteten Geräten zu erkennen und zu beheben. Viele Organisationen beschränken Pen-Tests jedoch auf die Untersuchung von Netzwerken und Infrastruktur – IoT-Geräte werden oft übersehen.

Um Sicherheitsteams mit Pen-Tests für eingebettete Geräte vertraut zu machen, hat Jean-Georges Valle, Senior Vice President bei Kroll, einem Beratungsunternehmen für Cyberrisiken und Finanzdienstleistungen, das Buch „Practical Hardware Pentesting: Lernen Sie Angriffs- und Verteidigungstechniken für eingebettete Systeme in IoT und anderen Geräten“ geschrieben .

Im folgenden Auszug aus Kapitel 10 beschreibt Valle, wie Penetrationstester dynamisches Reverse Engineering nutzen können, um zu sehen, wie sich Code während der Ausführung auf eingebetteten Geräten verhält. Valle liefert ein Beispiel für dynamisches Reverse Engineering, um Pentestern die Herausforderungen zu zeigen, die bei der Beobachtung des Codeverhaltens auftreten können.

Lesen Sie ein Interview mit Valle über eingebettete Penetrationstests, einschließlich gängiger Testschritte, die er verwendet, der Schwierigkeiten eingebetteter Penetrationstests und seiner Meinung dazu, wie gut Unternehmen heutzutage eingebettete Geräte sichern.

Anmerkung des Herausgebers: Der folgende Auszug stammt aus einer Early-Access-Version von Practical Hardware Pentesting, Second Edition und kann sich ändern.

Ich habe eine Variante des vorherigen Beispiels vorbereitet, die uns vor einige Herausforderungen stellen wird. Ich zeige Ihnen, wie Sie diese Herausforderungen sowohl statisch als auch dynamisch bewältigen können, damit Sie den Aufwand in beiden Fällen vergleichen können.

Als Faustregel beim Vergleich dynamischer und statischer Ansätze gilt, dass dynamische Ansätze in 99 % der Fälle einfach einfacher sind und nach Möglichkeit Vorrang haben sollten (vergessen Sie nicht, dass Sie möglicherweise keinen Zugriff auf JTAG/SWD oder andere haben). On-Chip-Debugging-Protokolle).

In diesem Abschnitt erfahren wir auch, wie man an der gewünschten Stelle Pausen macht, den Speicher mit GDB überprüft und all diese guten Dinge!

Das Zielprogramm befindet sich hier im Ordner, den Sie geklont haben, im Ordner ch12.

Beginnen wir zunächst damit, es in Ghidra zu laden und es oberflächlich zu untersuchen. Achten Sie darauf, die richtige Architektur und Basisadresse im Ladefenster von Ghidra einzustellen (lesen Sie das vorherige Kapitel, wenn Sie sich nicht erinnern, wie das geht oder welchen Wert die Basisadresse hat).

Auf den ersten Blick sieht die Hauptfunktion der Hauptfunktion im vorherigen Kapitel sehr ähnlich. Wir können den Verweis auf die Hauptfunktion finden, indem wir wie im vorherigen Kapitel eine PASSWORD-Zeichenfolge durchsuchen und uns mit der Analyse ihrer Struktur befassen.

Ich lasse Sie an den Fähigkeiten arbeiten, die Sie im vorherigen Kapitel erworben haben, um die verschiedenen Funktionen zu finden. In dieser ausführbaren Datei finden Sie erneut Folgendes:

Die Ähnlichkeit der Struktur ist beabsichtigt, da dies Ihr erstes Mal ist. Wenn ich genau die gleichen Schritte wie im vorherigen Kapitel wiederholen würde, gäbe es für Sie nichts Neues zu lernen, oder?

Lassen Sie uns nun mehrere Methoden zur Umgehung dieser Passwortvalidierung durch dynamische Interaktion mit dem System durchgehen. Wir gehen vom Komplexesten zum Einfachsten über, damit Sie fokussiert bleiben und sich Know-how aneignen können (wenn Sie so sind wie ich, wenn es einen einfachen Weg gibt, etwas zu umgehen, warum sollten Sie dann den schwierigen Weg wählen?).

Als Erstes versuchen wir herauszufinden, wie das Passwort validiert wird, um zu verstehen, wie man ein Passwort generiert, das die Tests besteht.

Werfen wir einen Blick auf den der Validierungsfunktion entsprechenden C-Code, der von Ghidra ausgegeben wird:

Humm... das hat nichts direkt mit den Parametern zu tun. Dabei wird der Inhalt eines 0x47 (71) langen statischen Byte-Arrays in den RAM kopiert (und NICHT gespeichert) und dann als Funktion aufgerufen.

Das ist merkwürdig.

Oder ist es?

Dies ist eine sehr verbreitete Technik zur Tarnung von Code (natürlich eine sehr einfache Version davon). Wenn in der .bin-Datei (und damit nicht im Flash der MCU) keine klare Version des Opcodes vorhanden ist, kann ein Reverse-Engineering-Tool wie Ghidra nicht erkennen, dass es sich um Code handelt! Hier haben wir zwei mögliche Ansätze:

Die erste Lösung überlasse ich Ihnen zur Umsetzung als Übung. Für eine solch einfache Aufgabe sollten etwa 10 Zeilen Python- oder C-Code erforderlich sein! Du willst Hacker werden? Hack weg!

Mich? Ich bin ein fauler Kerl. Wenn ein Computer für mich funktionieren kann, dann... So sei es! Ich werde mich für die zweite Lösung entscheiden.

Starten wir zunächst eine Bildschirmsitzung in einem Terminal, damit wir Passwörter eingeben und sehen können, wie es reagiert:

Lassen Sie uns OpenOCD und GDB in einem zweiten Terminal starten, wie wir es zu Beginn des Kapitels getan haben, und stöbern wir herum:

Und... und verdammt! Es gibt mir nicht die Kontrolle zurück! Kein Problem, wenn Ihnen das passiert – ein wenig Strg + C gibt Ihnen sofort die Kontrolle zurück:

Nach unserem Strg + C (^c) teilt uns gdb mit, dass die Ausführung an der Adresse 0x080003aa in einer unbekannten Funktion (??) gestoppt wird.

Abhängig von Ihrem Bundesstaat können Sie an einer anderen Adresse anhalten.

Keine Panik – setzen Sie Ihren Denkhut auf und nehmen Sie (immer) Ihr Handtuch mit.

Das ist kein Problem. Die Wahrscheinlichkeit ist groß, dass Sie in unmittelbarer Nähe dieser Adresse abbrechen, da sie sich in der Warteschleife befindet, in der die LED blinkt und auf den Empfang eines Passworts auf der seriellen Schnittstelle wartet.

Werfen wir zunächst einen Blick auf unsere Register:

Wir sehen, dass der PC tatsächlich dort ist, wo er sein soll, alles sieht gut und gut aus. Versuchen wir nun, ein Passwort einzugeben.

Und... im Fenster der seriellen Schnittstelle funktioniert nichts! Denken Sie darüber nach ... GDB blockiert tatsächlich die Ausführung des Codes. die serielle Schnittstelle reagiert nicht auf Ihre Eingaben. Das ist normal.

Lassen wir es also weiterlaufen (continue oder c im GDB-Fenster) und prüfen wir, ob die Seriennummer jetzt funktioniert. Ja tut es. Lassen Sie uns es noch einmal unterbrechen und einen Haltepunkt für die Adresse der Passwort-Validierungsfunktion setzen, oder?

In Ghidra können wir sehen, dass die Adresse der ersten Anweisung der Funktion 0x080002b0 lautet:

Setzen wir dort einen Haltepunkt, lassen Sie gdb die Ausführung fortsetzen und geben Sie ein Dummy-Passwort ein:

Lassen Sie uns das analysieren:

Okay, was können wir jetzt damit machen?

Das Wichtigste zuerst: Wenn Sie sich an den Code der Validierungsfunktion erinnern, wurden ihre Argumente direkt an den dekodierten Code übergeben. Werfen wir einen Blick darauf, was sie sein können (denken Sie an die Aufrufkonvention für Funktionen: Argumente sind in r0-3):

Das erste Argument ist etwas im RAM und das zweite ist ein Wert. (Das ist der umgewandelte UUID-Wert für Ihren Chip, den Sie notiert haben, oder?)

Was ist nun unter dieser ersten Adresse gespeichert? Lass es uns untersuchen:

Ah! Ah! Ah! (Sehen Sie, was ich dort gemacht habe?) Das ist unser Passwort. Bitte beachten Sie die Verwendung des Formatmodifikators für den x-Befehl.

Das ist also zu erwarten.

Schauen wir uns nun den entschlüsselten Code an.

Ghidra teilt uns mit, dass die Anweisung, die den Decodierungsschleifen folgt, bei 0x080002f0 liegt. Lassen Sie uns dort brechen:

Die Adresse des entschlüsselten Codes befindet sich also in r3. Wir haben gesehen, dass der Puffer 0x47 (71) lang war. Wir sind im Daumenmodus (also Anleitung für Größe 2). Dies sollte 47/2 sein: etwa 35 Anweisungen. Das letzte Bit der Adresse ist für den Modus; wir können das loswerden:

Das ist eher so! Wir sehen einen normalen Funktionsauftakt (Speichern von funktionsinternen Registern im Stapel), einige Verarbeitungsvorgänge und eine Funktionsrückgabe. Aber GDB warnt uns vor illegalen Befehlsparametern (0x2000016c).

Wenn wir uns die Auflistung ansehen, sehen wir, dass GDB die Nutzung eines PC-relativen Datenelements angibt:

Dies wird sehr häufig zum Speichern von Daten in einem Assemblerprogramm verwendet. adr ist eine Pseudoanweisung, die dem Assembler mitteilt, dass er den Offset zu einer Beschriftung (einer benannten Position) im Code hinzufügen soll.

Schauen wir uns an, was dort gespeichert ist:

Dies ist tatsächlich eine Zeichenfolge, die im Prozess irgendwie verwendet wird.

Lassen Sie uns die ersten Anweisungen Schritt für Schritt durchgehen, als Beispiel dafür, wie man einem Ausführungsablauf folgt. Zuerst richten wir gdb so ein, dass es uns bei jedem Schritt die interessanten Register und den Inhalt anzeigt:

Jetzt können wir Stepi (Schrittanweisung) verwenden, um zu sehen, was vor sich geht:

Dadurch werden r4, r3 und r5 auf Null gesetzt (x^x = 0):

Dadurch wird das erste Zeichen der Passwortzeichenfolge in r5 geladen (r1 ist die Adresse und r4 ist an dieser Stelle auf Null gesetzt) ​​und nach r8 und r6 kopiert:

Dies verschiebt r6 um 4 Bits nach rechts, r5 um 4 Bits nach links und fügt ihren ODER-Wert in r4 ein. Anschließend maskiert es das ODER-Ergebnis mit 0xff, wobei im Grunde die 4 niedrigeren und 4 höheren Bits des Passwortzeichens ausgetauscht und die überschüssigen Bits gelöscht werden!

Dies verschiebt 15 in r6, kopiert r4 in r8 und r7 und maskiert r7 mit 15. Aber warum? Zu diesem Zeitpunkt ist r4 0! Dies kann später verwendet werden – da wir gesehen haben, dass r4 als Offset beim Laden des Passwortzeichens verwendet wurde, ist r4 wahrscheinlich ein Zähler! Wenn das der Fall ist, kann diese Maskierung als eine Art Modulo verwendet werden... (es ist sehr üblich, die Maskierung für Modulo einer Zweierpotenz -1 zu verwenden):

Dies lädt das erste Zeichen der Zeichenfolge, die in r6 ausgeblendet wurde, und verwendet r7 und einen Offset! r4 ist hier definitiv ein Zähler und r7 eine modulierte Version davon. Dies ist eine sehr typische Programmiermethode, um dies zu erreichen:

Dies ist eine XOR-Verknüpfung des Werts des bitvertauschten Passwortzeichens mit den aktuellen Rängen der seltsamen Zeichenfolge, die Addition zu r0 und die Erhöhung des Zählers r4:

Dadurch wird ein neues Passwortzeichen mit dem neuen Offset r5 geladen. r3 ist 0, also prüft der cmp r5-r3 und ... Warte … bgt.n? Was ist das? Erinnern Sie sich, was zu tun ist, wenn Sie Zweifel haben? Lesen Sie die Dokumentation hier: https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/condition-codes-1-condition-flags-and-codes.

Es springt also, wenn r5 > r3. Und r3 ist 0, also? Dies ist ein Test für eine mit 0 terminierte Zeichenfolge!

Dies ist die Hauptlogikschleife der Validierung!

Sobald dies erledigt ist, geschieht Folgendes:

Abhängig vom berechneten Wert wird diese Summe mit der UUID XOR-verknüpft, die Aufruferregisterwerte wiederhergestellt und dieser Wert zurückgegeben. Der C-Code prüft dann, ob dieser Wert null ist, um tatsächlich die Gewinnerzeichenfolge anzuzeigen. Wir müssen es dann nur noch so anordnen, dass unsere Summe dem UUID-abhängigen Wert entspricht, damit das XOR null ist!

Wir haben die ganze Logik!