Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
zipdefrag — Presented at Recon Montreal 2018 | Kitploit
Tools/GitHubGitHub/nccgroup/zipdefrag
Embedded Systems SecurityMemory ForensicsReverse EngineeringData RecoveryDigital ForensicsFirmware Analysis
GitHubnccgroup/zipdefrag

zipdefrag

Presented at Recon Montreal 2018

Repository anzeigen
74vor 8 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Dieser Dump ist ein Puzzle

oder fortgeschrittenes Shotgun-Parsing für Narren und Rebellen

Es war einmal

Es war einmal, liebe Leserin, lieber Leser, in einer dunklen und stürmischen Nacht: Dein treuer Autor stieß auf eine verwirrende und geheimnisvolle Situation – ein System, in dem das Einzige, was die Chip-Off-Speicheranalyse und das Reverse Engineering verhinderte, ein unbekanntes, fragmentiertes proprietäres Dateisystem in Verbindung mit Kompression war, die durch den Einsatz von eingebettetem Java die Wirksamkeit vorhandener Werkzeuge zum File Carving verringerte.

Damals wurde vielleicht eine Ad-hoc-Lösung zusammengebastelt, indem man Chunks, die zusammenzupassen schienen, manuell aneinanderfügte – mit ein bisschen grauenhafter Python-Konsolenarbeit und hässlichem Ad-hoc-Bash-Scripting. Es war gut genug, aber zeitintensiv.

Während das Extrahieren von einfachen, unkomprimierten Daten bei der Chip-Off-Analyse im Alltag eines Hardware-Hackers eine ziemlich alltägliche Aufgabe ist, stellt Kompression erhebliche Probleme dar, wenn ihre Bruchstücke auf unvernünftige und unerfreuliche Weise überall verstreut sind, selbst wenn keine weiteren Schutzmaßnahmen gegen die Extraktion vorhanden sind.

Aber es muss doch einen besseren Weg geben?

Insbesondere bei Zip-Dateien gibt es einige interessante Dinge (Zip ist das grundlegende Format, das für JAR-Dateien verwendet wird). Als ernsthafter Anhänger des International Journal of PoC||GTFO seit geraumer Zeit und insbesondere als Verfolger der Arbeiten zum File-Format-Stunting von Ange Albertini, vermutete ich, dass eine Zip-Datei genügend Daten über sich selbst enthält, um sie wieder einigermaßen zusammensetzen zu können.

Nachdem wir die Referenzen hinter uns haben, kommen wir zu den technischen Details.

Das Wichtigste zuerst: Möglicherweise kennen wir die Besonderheiten des Dateisystems nicht (und aus der Perspektive meiner Forschung, unabhängig von dem System, auf dem ich auf das Problem gestoßen war, entschied ich, dass es schlicht besser ist, sich nicht darum zu kümmern). Aber wir wissen einiges darüber, wie die meisten Dateisysteme implementiert sind. Insbesondere wissen wir, dass sie tendenziell in Chunks geschrieben werden. Die Chunks haben eine Mindestgröße – bekannt als Seiten (Pages) – und wir können diese Seitengröße ermitteln, indem wir den Dump durchsuchen und die kleinste geschriebene Blockgröße identifizieren.

Einige davon liegen möglicherweise zusammenhängend, andere nicht, ohne ein klares Muster dafür, wann Blöcke zusammenhängend sind.

Das alles bedeutet: Unser Problem ist, wie wir die Datenseiten so anordnen, dass sie uns gültige (oder annähernd genaue) Images der Dateien liefern, die wir extrahieren wollen.

Zip-Dateien sind so geschrieben, dass sie eine Art umgekehrte Hierarchie implementieren. Zuerst kommen die komprimierten Dateidaten (verpackt in lokalen Dateiköpfen, die sie beschreiben). Dann folgt ein zentrales Verzeichnis (Central Directory), das die Offsets der lokalen Dateiköpfe auflistet, und dann ein End-of-Central-Directory-Datensatz (der unter anderem die Anzahl der in der Zip gespeicherten Dateien, den Offset, an dem das zentrale Verzeichnis beginnt, und die Größe des zentralen Verzeichnisses beschreibt).

Betrachten wir das rückwärts und gehen etwas mehr ins Detail:

  • Das End of Central Directory verrät uns:

    • Den genauen Ort des EOCD-Datensatzes innerhalb der Zip-Datei (der Offset des CD plus die Länge des CD, die dem EOCD vorausgeht)
    • Wie viele Dateien (und damit CD-Datensätze) wir suchen müssen.
    • Den genauen Ort des ersten CD-Datensatzes in der Zip-Datei.
  • Jeder Central-Directory-Datensatz verrät uns:

    • CRC32 der komprimierten Dateidaten
    • Zeitstempel
    • Jede Menge weiterer Metadaten (Kompressionsmethode, Flags, verwendete/benötigte OS-Version ...)
    • Einen Index innerhalb der Datei für den entsprechenden LF-Chunk
    • Entscheidend: genügend Daten, um ein Abbild des entsprechenden LF-Chunks zu konstruieren.
  • Jeder Local-File-Datensatz verrät uns:

    • Den Ort in unserem Dump, an dem eine Datei beginnt
    • Wenn die Datei klein genug ist, erhalten wir die gesamte Datei innerhalb derselben Seite – oder dank des nächsten Dateikopfs, der im nächsten Seiten-Chunk der Zip-Datei erscheint!
    • Wenn genügend kleine Dateien in genügend Seiten gepackt sind, können wir anhand der Position der Seiten und der bekannten Verzeichniswerte eine Reihenfolge für die Seiten erstellen (mit bekannten Lücken!)

All das führt dazu, dass wir die überwiegende Mehrheit der Datei rekonstruiert haben.

Plot Twist – Wir müssen realistischerweise mit mehr als einer JAR-Firmware umgehen!

Zunächst benötigen wir einen Schritt, um Daten verschiedener Firmwares voneinander zu unterscheiden. Der Grund dafür ist, dass alle Offsets nur innerhalb ihrer jeweiligen Zip-Dateien relevant sind – jeder Konflikt führt zu nicht zusammenpassenden Zip-Streams und Korruption, und wir möchten unbedingt so viele unverfälschte Daten wie möglich herausholen. Außerdem wollen wir die Gewissheit, dass beispielsweise etwaige Schwachstellen, die wir in der Ziel-Firmware diagnostizieren, diejenige betreffen, die wir normalerweise laufen sehen, und nicht eine andere Datei, die nur herumliegt.

Die benötigte Lösung ist der k-means-Algorithmus (auch bekannt als „Lloyd-Algorithmus“). Hier gibt es ein großartiges Video, das erklärt, wie er funktioniert. SciPy hatte eine gute Version parat, aber ich musste die einzige Clustering-/Analytics-Crate, die den Algorithmus implementiert, identifizieren/patchen, damit es mit der Rust-Implementierung funktioniert. Glücklicherweise musste ich es nicht von Grund auf neu schreiben.

Danach ist die Sache gut.

Wir können dafür eine Reihe von Merkmalen nutzen. Die Felder Flags, Method und Version variieren je nach dem Zip-Stack, der zum Komprimieren der Datei verwendet wurde. Außerdem besitzen die Header Zeitstempel, und es ist allgemein unwahrscheinlich, dass alle Firmwares exakt zur gleichen Zeit kompiliert und komprimiert wurden.

Übrigens ist erwähnenswert, dass Zip-Dateien Zeitstempel im MS-DOS-Format verwenden, die als bitgepackte Shorts Jahr-Monat-Tag und Stunde-Minute-zwei-Sekunden darstellen. Wenn diese nicht in einen absoluten Skalarwert umgewandelt werden, bevor man sie als Klassifikationsdaten verwendet, könnte man einem Jahr Unterschied genauso viel Gewicht beimessen wie einer Sekunde – und das ist überhaupt nicht gut!

Eine kurze Randnotiz zum Parsing

Obwohl ich das als ziemlich skizzenhaftes Python-Skript zum Prototyping dieser Methode geschrieben habe und mit dem PoC eine Wiederherstellungsrate des JAR-Inhalts von etwa 70–80 % erreicht habe, habe ich beschlossen, dort aufzuhören und zu einer schnellen Version in Rust überzugehen.

Rust hat eine Crate namens nom, die absolut fantastisch zum Schreiben von Parser-Verifizierern ist. Das war übrigens einer der Hauptgründe, es in Rust neu zu schreiben. Die Fähigkeit, klare und extrem strenge Parser zu schreiben, macht das in mancher Hinsicht viel einfacher, als alles in Python zu handhaben (das tendenziell viel toleranter ist – so sehr, dass es manchmal eine ziemliche Herausforderung ist, sicher zu sein, dass man nicht über fehlerhafte Fehlschläge hinweggeht, um einen Randfall zu erwischen).

Wenn großartige, schnelle, gut lesbare Parser interessant klingen, schau dir Folgendes an:

  • Writing Parsers like it's 2017
  • Nom Benchmarks – darin hat jemand einen HTTP-Parser von Grund auf in Rust geschrieben, geringfügig schneller als eine sehr schnelle C-Implementierung, ohne Pufferüberläufe.

Abgesehen davon ist die Durchführung solcher Analysen in Python von Natur aus eher langsam und war nie wirklich als mehr gedacht denn als Weg zu einem PoC, um die Machbarkeit dieses Ansatzes zu untersuchen.

Wie auch immer, genug davon ...

Weiter geht's

Ansätze zur Rekonstruktion der verbleibenden Chunks umfassen: zuerst die verbleibenden Seiten nach Kandidaten mit hoher Entropie zu filtern, zuerst kleinere Lücken zu füllen (wobei so viele Seiten wie möglich aus der Suchliste entfernt werden, da das Testen von Permutationen im schlimmsten Fall eine Aufgabe mit exponentieller Laufzeit ist – schnelles Lösen der einfacheren Fälle hat daher Priorität und vereinfacht unser Problem mit der Zeit exponentiell!).

Wir können auch schnell Erfolge erzielen, indem wir Fälle finden, in denen ein lokaler Dateikopf aufgrund einer Seitengrenze nicht parsbar ist (wir sollten ihn einem identisch ausgerichteten Gegenstück zuordnen können, zumindest in Fällen, in denen ähnliche Ausrichtungen eindeutig sind und nicht mit anderen Korruptionsartefakten kollidieren).

Wie prüfen wir Kandidaten für fehlende Seiten? Nun, wir haben unsere CRC32-Prüfsummen für die Dateien direkt in unserem Central Directory! Anstatt CRC32 über die Datei zu berechnen, ist der wahrscheinlich beste Weg, CRC32 über die Chunks zu berechnen, die wir bereits kennen (vorwärts von den Daten am Ende der Seite vor Beginn unserer Lücke und rückwärts von den Daten (oder dem DataDescriptor-Chunk nach dem Deflate-Stream)) und daraus abzuleiten, welches Zwischen-CRC32 wir für jeden Block fehlender Seiten erwarten sollten.

Im Grunde machen wir die schwierigeren Probleme erheblich einfacher, wann immer wir das einfachste/schnellste zu lösende Problem wählen, indem wir die Spreu entfernen. Genau das ist der Grund, warum wir die Shannon-Entropie nutzen, um leere oder nahezu leere Seiten einfach komplett zu verwerfen – es ist nicht garantiert, dass wir nur Zip-Seiten mit hoher Entropie haben, aber selbst wenn es Ausreißer gibt, ist es eine massive Beschleunigung, diese Komplikation von vornherein nicht behandeln zu müssen.

Ich wollte das verdammte Ding nur bauen – wo liegt das Problem?

Wenn du damit herumspielen möchtest, installiere Rust (empfohlen mit dem großartigen rustup nightly). Dann:

root@kitploit:~
$ git clone [repo]
...
$ cd zipdefrag
...
$ cargo build --release

Du kannst das Release-Flag weglassen, um das Debugging zu aktivieren.

Die Build-Artefakte befinden sich in /target/{debug,release}

Erstelle die Dokumentation mit cargo doc (Diese Crate ist stark dokumentiert. Ich schreibe gern.)

Derzeit gibt es für die Rust-Version standardmäßig keine Terminalausgabe. Wenn du die CLI-Testumgebung ausführen möchtest, musst du die Umgebungsvariable RUST_LOG=zipdefrag setzen, die eine ausführliche Terminalprotokollierung aktiviert und die bisherige Analyse veranschaulicht.

Weitere Pläne:

  • Eine schnelle portable native ausführbare Datei (mit Python-Hooks) zum Lösen von Puzzle-Zip-Dumps aus unbekannten Dateisystemen.

  • Ein Demonstrations-Dump

Bekannte Probleme

  • Die Performance der Rust-Implementierung ist derzeit stark beeinträchtigt, weil bei der Suche nach passenden LFH-Chunks verschwenderisch vorgegangen wird. Ich werde das beheben und meine Lektion daraus lernen.

  • Diese Technik funktioniert nicht gut, wenn viele Dateien im JAR deutlich größer als die Seitengröße sind. Da sie stark auf die in Zip-Dateien inhärente Struktur angewiesen ist, funktionieren datenlastige Dateien einfach nicht so gut.

Praktischerweise sind Klassendateien für J2ME-Midlets im Allgemeinen ziemlich klein, aber große gepackte Binärdateien sind wahrscheinlich nicht wiederherstellbar.

Außerdem enthält der Python-PoC eine Reihe arithmetischer Fehler.

Tool herunterladen