Skip to content

Gelöschte QuarantineEventsV2-Einträge wiederherstellen

Wie gelöschte QuarantineEventsV2-Zeilen in SQLite-Freeblocks, Freelist, WAL-Frames und Journalen überleben, wie man sie sichert und was ihr Fund beweist.

Veröffentlicht am 7 Min. Lesezeit

TL;DR. Wer eine Zeile mit sqlite3 aus QuarantineEventsV2 löscht, vernichtet sie damit nicht. Der Eintrag bleibt meist als freier Speicher in der Tabellenseite in der Datei, und ältere Seitenversionen können außerdem in Freelist-Seiten, im WAL und im Rollback-Journal liegen. Quarantine Parser gewinnt LSQuarantineEvent-Einträge per Carving aus all diesen Quellen, indem er sich an den Serial Types des Eintrags orientiert; diese überleben, wenn die ersten 4 Bytes einer freigegebenen Zelle überschrieben werden. Die Wiederherstellung hängt von secure_delete und späteren Schreibvorgängen ab, und das Fehlen wiederhergestellter Zeilen beweist nichts.

Wie Quarantäne-Zeilen verschwinden

Für die Datenbank QuarantineEventsV2 ist keine automatische Bereinigung dokumentiert; Zeilen überleben in der Regel die Dateien, die sie beschreiben. Sie verschwinden auf zwei Wegen:

  • Löschen von Zeilen. Wer Zugriff auf die Dateien des Benutzers hat, kann mit sqlite3 ein DELETE auf LSQuarantineEvent ausführen und eine belastende Zeile oder den gesamten Verlauf entfernen. Auch ein routinemäßiges Leeren des Downloadverlaufs kann Zeilen entfernen.
  • Entfernen der Datei. Die ganze Datenbank samt Begleitdateien kann gelöscht werden. Was dann bleibt, ist Sache der Wiederherstellung auf Dateisystemebene, nicht des SQLite-Carvings.

Im ersten Fall hilft Carving, denn standardmäßig löscht SQLite gelöschte Inhalte nicht physisch.

Wo gelöschte Einträge überleben

Freier Speicher in Tabellenseiten

SQLite speichert Zeilen als Zellen in Seiten fester Größe (4096 Byte auf dem beobachteten System mit macOS 26.6). Wird eine Zeile gelöscht, wird ihre Zelle standardmäßig nicht genullt: Der Platz wird Teil einer Kette von Freeblocks in der Seite, und die alten Bytes bleiben erhalten, bis ein späteres Insert sie wiederverwendet. Hier findet man ein gelöschtes Quarantäne-Ereignis am häufigsten.

Freelist-Seiten

Enthält eine Seite keine aktive Zelle mehr, setzt SQLite sie zur Wiederverwendung auf die Freelist. Ihr alter Inhalt kann erhalten bleiben, bis die Seite neu vergeben wird.

Ältere Seitenkopien: Hauptdatei oder WAL

Mit einem Write-Ahead-Log landen neue Seitenversionen zuerst in der -wal-Datei und gelangen erst bei einem Checkpoint in die Hauptdatei. Bis dahin enthält die Hauptdatei die ältere Version jeder geänderten Seite, das WAL die neuere. Eine in der WAL-Version gelöschte Zeile kann in der Kopie der Hauptdatei also noch aktiv sein, und nach einem Checkpoint umgekehrt.

Überholte und nicht festgeschriebene WAL-Frames

Ein WAL kann mehrere Frames für dieselbe Seite enthalten, jeder eine spätere Version. Überholte Frames bewahren frühere Zustände der Seite, und Frames, die nach dem letzten Commit geschrieben wurden, sind nie festgeschrieben worden. SQLite ignoriert beide beim Lesen der Datenbank; ein Carving-Werkzeug muss das nicht.

Rollback-Journal-Seiten

Im Rollback-Journal-Modus, den das beobachtete System mit macOS 26.6 nutzte (journal_mode=delete), kopiert SQLite die Originalversion jeder Seite in die -journal-Datei, bevor es sie ändert. Im DELETE-Modus wird das Journal beim Commit entfernt; eine -journal-Datei neben der Datenbank bedeutet daher meist, dass beim Kopieren eine Transaktion lief oder unterbrochen wurde. Ihre Seiten können die Zeilen im Zustand vor der Änderung enthalten. Im PERSIST-Modus behält SQLite die Journaldatei und macht nur ihren Header ungültig, sodass auch dort ältere Seitenabbilder liegen bleiben können.

secure_delete und spätere Schreibvorgänge

SQLite kennt die Einstellung secure_delete, die gelöschte Inhalte überschreibt. Ist sie aktiv, werden freigegebene Zellen genullt, und Carving im freien Speicher findet nichts. Wie Apple sie für diese Datenbank konfiguriert, ist nicht dokumentiert; gehen Sie also von nichts aus. Unabhängig davon kann jedes spätere Insert freigegebenen Platz wiederverwenden, und eine stark genutzte Datenbank recycelt Platz schneller. Beides erklärt, warum Wiederherstellung vom Zufall abhängt.

Warum sich das Carving an den Serial Types orientiert

Ein SQLite-Record beginnt mit seiner Payload-Länge und seiner Rowid, gefolgt von einem Header, der je Spalte einen Serial Type auflistet, und danach den Spaltenwerten. Wird eine Zelle freigegeben, schreibt SQLite den Freeblock-Header (den Offset des nächsten Freeblocks und die Größe dieses Blocks) über die ersten 4 Bytes der Zelle. Damit ist der Anfang der Zelle zerstört, die Serial Types aber überleben.

Bei LSQuarantineEvent bilden diese Serial Types ein erkennbares Muster: elf Spalten, überwiegend Text, ein REAL für den Zeitstempel (oder ein Integer, wenn der Wert ganzzahlig ist, eine Speicheroptimierung von SQLite), ein Integer für die Typnummer und ein Blob oder NULL für den Alias. Quarantine Parser sucht dieses Muster im freien Speicher und dekodiert die darauf folgenden Spaltenwerte, um die Zeile zu rekonstruieren.

Unvollständige Einträge

Ein per Carving gewonnener Eintrag kann unvollständig sein: Spätere Schreibvorgänge haben vielleicht einen Teil der freigegebenen Zelle überschrieben, oder die Bytes für die letzten Spalten sind verloren. Halten Sie ein Feld nur dann für verlässlich, wenn es sich sauber dekodieren lässt. Der Identifier und eine URL genügen oft, um zu verknüpfen und weiterzuermitteln. Vergleichen Sie wiederhergestellte Einträge mit den aktiven: Auch eine ältere Seitenkopie kann eine noch aktive Zeile enthalten, prüfen Sie also auf Duplikate, bevor Sie Löschungen zählen.

Was wiederhergestellte Zeilen beweisen und was nicht

Eine wiederhergestellte Zeile zeigt, dass dieses Ereignis in der Datenbank dieses Benutzers existierte und nicht mehr aktiv ist. Das ist ein Ansatzpunkt, kein Urteil. Sie sagt weder, wann die Zeile gelöscht wurde, noch von wem; SQLite hält das nicht fest. Sie unterscheidet nicht zwischen einem absichtlichen DELETE und routinemäßigem Leeren. Deshalb formuliert die Ansicht Befunde das als „mögliches Löschen oder Leeren“. Der Kontext entscheidet: Eine einzelne fehlende Zeile in einem gleichmäßigen Verlauf, direkt nach einem verdächtigen Download, liest sich anders als ein komplett geleerter Verlauf.

Und umgekehrt: Wird nichts wiederhergestellt, ist nichts bewiesen. Der freigegebene Platz kann wiederverwendet oder von secure_delete genullt worden sein.

Beweismittel sichern

  • Kopieren Sie die Datenbank mit ihren -wal- und -journal-Dateien (cp -p mit abschließendem *, wie im Sicherungsleitfaden gezeigt).
  • Bilden Sie Hashes der Kopien vor jeder Analyse.
  • Öffnen Sie das Original nicht mit sqlite3. Das Öffnen einer Datenbank kann das WAL per Checkpoint in die Hauptdatei übernehmen oder ein Hot Journal zurückrollen und dabei genau die Seiten überschreiben, die Sie auswerten wollen. Selbst bei einer Kopie sollten Sie sqlite3 nur auf eine zweite Kopie anwenden.
  • Ziehen Sie Rohdateien ausgewerteten Ergebnissen vor: Ein ausgewerteter Export enthält nur die aktiven Zeilen, die die Abfrage geliefert hat.

Quarantine Parser schreibt nie in die Dateien: Er nutzt seinen eigenen, rein lesenden SQLite-Reader in Ihrem Browser, wendet festgeschriebene WAL-Frames so an, wie SQLite es tut, und gewinnt den Rest per Carving.

Beispiel: FIN-MBP-03

Der synthetische Fall hinter „Try a sample“ auf der Startseite zeigt die ganze Kette. Auf dem Mac-Laptop FIN-MBP-03, Benutzer dana.whitlock, am 2026-09-14:

  • Um 10:07:12 lädt Safari https://files.example/s/8f3k2/tools.zip herunter (Ursprung https://files.example/s/8f3k2). Das Archivierungsprogramm entpackt tools/README.txt, tools/updater.sh und tools/com.example.updater.plist, jeweils mit dem Quarantäne-Wert und der UUID des Archivs.
  • Eine Kopie von com.example.updater.plist liegt in ~/Library/LaunchAgents/ und trägt diesen Wert noch immer.
  • Gegen 10:48 wird die files.example-Zeile mit sqlite3 aus der Datenbank gelöscht.

In der aktiven Tabelle gibt es kein files.example-Ereignis mehr, und mehrere Attribut-UUIDs haben keine Datenbankzeile. Quarantine Parser stellt die Zeile aus dem freien Speicher der Tabellenseite wieder her und verknüpft sie mit den fünf Dateien, die ihre UUID tragen, die Kopie in LaunchAgents eingeschlossen. Die Uhrzeit stammt aus dem Szenario: In einem echten Fall würde die Datenbank Ihnen nicht verraten, wann gelöscht wurde, und auch nicht die Terminal-Sitzung zeigen, in der sqlite3 lief. Dafür sind Unified Logs, TCC.db und FSEvents die richtigen Stellen.

Zur Attributseite der Verknüpfung siehe com.apple.quarantine-Flags, zur vollständigen Untersuchung Schritt für Schritt durch eine macOS-Download-Untersuchung, für eine Referenzkarte macforensics.app.

Häufige Fragen

Lassen sich gelöschte QuarantineEventsV2-Einträge wiederherstellen?

Oft, aber nicht immer. Eine gelöschte Zeile kann im freien Speicher von Tabellenseiten, in Freelist-Seiten, in älteren Seitenkopien im WAL oder in der Hauptdatei und in Rollback-Journal-Seiten überleben. Die SQLite-Einstellung secure_delete und spätere Schreibvorgänge können sie zerstören.

Beweist ein wiederhergestellter Eintrag, dass jemand die Datenbank manipuliert hat?

Nein. Er beweist, dass die Zeile in dieser Datenbank existierte und nicht mehr aktiv ist. Absichtliches Löschen mit sqlite3 und routinemäßiges Leeren können gleich aussehen; zeitlicher Ablauf, andere Artefakte und die Dateien mit der UUID helfen bei der Einordnung.

Wenn nichts wiederhergestellt wird, wurden dann keine Zeilen gelöscht?

Nein. Freigegebener Speicher wird von späteren Schreibvorgängen wiederverwendet, und secure_delete überschreibt gelöschte Inhalte; das Fehlen wiederhergestellter Zeilen beweist daher nichts.

Wie vermeide ich, gelöschte Einträge bei der Sicherung zu zerstören?

Kopieren Sie die Datenbank zusammen mit ihren -wal- und -journal-Dateien, bilden Sie Hashes der Kopien und arbeiten Sie mit einer Kopie der Kopie. Öffnen Sie das Original nicht mit sqlite3: Das Öffnen kann einen WAL-Checkpoint oder den Rollback eines Journals auslösen und freien Speicher überschreiben.

Verwandte Artikel