QuarantineEventsV2: den macOS-Downloadverlauf auswerten
Was die macOS-Datenbank QuarantineEventsV2 speichert: LSQuarantineEvent-Schema, Mac Absolute Time, Typnummern, Verknüpfung per xattr-UUID und Grenzen.
TL;DR. QuarantineEventsV2 ist eine SQLite-Datenbank pro Benutzer in ~/Library/Preferences/, in der macOS Downloads von Apps festhält, die an der Quarantäne teilnehmen, etwa Safari, Chrome, Mail, Nachrichten und AirDrop. Ihre einzige Tabelle, LSQuarantineEvent, verrät wann (in Mac Absolute Time), welche App, von welcher URL und liefert eine Ereignis-UUID, die auch am Ende des Attributs com.apple.quarantine der heruntergeladenen Datei steht. Den Dateinamen speichert sie nicht, und was curl, wget oder git geholt haben, sieht sie nie.
Was QuarantineEventsV2 ist
Wenn eine App, die an der Quarantäne teilnimmt, etwas aus dem Netz speichert, versieht macOS die Datei mit dem Quarantäne-Attribut und schreibt bei vielen dieser Downloads ein Ereignis in die Datenbank QuarantineEventsV2 des Benutzers. Die Datei liegt hier, eine pro Benutzerkonto:
~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2
Zeilen überleben in der Regel das Löschen der heruntergeladenen Datei, und eine automatische Bereinigung ist nicht dokumentiert. Die Datenbank beschreibt daher oft Downloads, deren Dateien längst verschwunden sind; genau das macht sie in der Praxis zu einem Downloadverlauf. Zeilen lassen sich trotzdem mit sqlite3 löschen, und die ganze Datei kann entfernt werden.
Das Schema von LSQuarantineEvent
Dies ist das Schema, das auf einem System mit macOS 26.6 beobachtet wurde. Apple dokumentiert die Spalten nicht; die Beschreibungen unten stützen sich auf die Spaltennamen und die beobachteten Daten.
| Spalte | Typ | Inhalt |
|---|---|---|
LSQuarantineEventIdentifier | TEXT, Primärschlüssel, nicht null | Ereignis-UUID; zugleich letztes Feld des Quarantäne-Attributs der Datei |
LSQuarantineTimeStamp | REAL | Zeitpunkt des Ereignisses in Mac Absolute Time |
LSQuarantineAgentBundleIdentifier | TEXT | Bundle-Identifier der herunterladenden App |
LSQuarantineAgentName | TEXT | Name der herunterladenden App |
LSQuarantineDataURLString | TEXT | URL der heruntergeladenen Daten |
LSQuarantineSenderName | TEXT | Name des Absenders, wenn das Objekt von einer Person stammt |
LSQuarantineSenderAddress | TEXT | Adresse des Absenders, im selben Fall |
LSQuarantineTypeNumber | INTEGER | Typnummer des Ereignisses (siehe unten) |
LSQuarantineOriginTitle | TEXT | Titel der Ursprungsseite |
LSQuarantineOriginURLString | TEXT | Seite oder Ort, von dem der Download ausging |
LSQuarantineOriginAlias | BLOB | Binäre Alias-Daten |
Es gibt zwei Indizes: LSQuarantineEventIndex auf dem Identifier und LSQuarantineTimeStampIndex auf dem Zeitstempel. Auf jenem System nutzte die Datenbank den Rollback-Journal-Modus (journal_mode=delete) mit einer Seitengröße von 4096 Byte. Rechnen Sie also eher mit einer -journal- als mit einer -wal-Datei, sichern Sie aber beide.
Zeitstempel: Mac Absolute Time
LSQuarantineTimeStamp zählt die Sekunden seit 2001-01-01 00:00:00 UTC, also Mac Absolute Time. Addieren Sie 978307200, um einen Unix-Zeitstempel zu erhalten. Die Spalte ist als REAL deklariert, doch SQLite kann einen REAL-Wert, der eine ganze Zahl ist, auf der Festplatte als Integer speichern; das ist eine dokumentierte Speicheroptimierung. Ein Parser, der nur 8-Byte-Gleitkommazahlen akzeptiert, liest solche Zeilen falsch oder überspringt sie; er muss beide Kodierungen verstehen.
LSQuarantineTypeNumber
Apples öffentlicher Header LSQuarantine.h führt sechs Typkonstanten in dieser Reihenfolge auf: WebDownload, OtherDownload, EmailAttachment, InstantMessageAttachment, CalendarEventAttachment, OtherAttachment. Sie in dieser Reihenfolge den Werten 0 bis 5 zuzuordnen, ist die gängige, in Quellen berichtete Deutung; von Apple dokumentiert ist sie nicht.
| Wert | Berichtete Bedeutung | Beobachtet unter macOS 26.6 |
|---|---|---|
| 0 | WebDownload | Downloads über Chrome und Homebrew Cask |
| 1 | OtherDownload | |
| 2 | EmailAttachment | |
| 3 | InstantMessageAttachment | Nachrichten |
| 4 | CalendarEventAttachment | |
| 5 | OtherAttachment | |
| 6 | Nicht dokumentiert | sharingd (AirDrop) |
| 7 | Nicht dokumentiert | Eine Safari-Zeile |
Behandeln Sie die Nummer als Hinweis und verlassen Sie sich auf die Agent-Spalten, um zu sehen, welche App beteiligt war.
Beobachtungen auf einem echten System mit macOS 26.6
Die folgenden Punkte stammen von einem einzigen Testsystem. Es sind Beobachtungen, keine Regeln, aber sie erklären viele verwirrende Ergebnisse:
- Viele Zeilen, vor allem von Chrome und Nachrichten, hatten überhaupt keine URL-Spalte gefüllt.
- Safari-Downloads trugen eine UUID in ihrem Quarantäne-Attribut, hatten in den letzten Monaten aber keine passende Datenbankzeile.
- Manche Attributwerte lauteten
0081;00000000;;: keine Zeit, kein Agent, keine UUID. - Im Attribut werden Leerzeichen in Agent-Namen als
\x20maskiert, etwaMicrosoft\x20Word. - Einige UUIDs waren kleingeschrieben.
Die Lehre daraus: Eine UUID ohne Treffer oder eine leere URL ist häufig und für sich genommen nicht verdächtig.
Was eine Zeile beweist und was nicht
Eine aktive Zeile zeigt, dass eine an der Quarantäne teilnehmende App für dieses Benutzerkonto zu diesem Zeitpunkt ein Download- oder Anhangsereignis mit diesen URLs und diesem Agent registriert hat. Zusammen mit dem Attribut einer Datei verknüpft sie eine Datei auf dem Datenträger mit einer Quelle.
Sie zeigt nicht, dass die Datei noch existiert, dass sie geöffnet oder ausgeführt wurde oder wer an der Tastatur saß: Eine gestohlene Sitzung erzeugt dieselben Zeilen wie der rechtmäßige Benutzer. Sie nennt die lokale Datei nicht. Und das Fehlen einer Zeile ist ein schwaches Indiz: Downloads mit Werkzeugen, die nicht teilnehmen (curl, wget, scp, rsync, git), tauchen nie auf, Zeilen lassen sich mit sqlite3 löschen, und die Datei selbst kann gelöscht werden.
Abfrage mit sqlite3
Arbeiten Sie mit einer Kopie, nie mit dem Original (schon das Öffnen kann eine Datenbank verändern; siehe gelöschte Einträge wiederherstellen). Diese Abfrage listet die Ereignisse chronologisch mit UTC-Datum auf:
SELECT datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch') AS event_utc,
LSQuarantineAgentBundleIdentifier, LSQuarantineDataURLString,
LSQuarantineOriginURLString, LSQuarantineEventIdentifier
FROM LSQuarantineEvent ORDER BY LSQuarantineTimeStamp;
Ereignisse und Dateien über die xattr-UUID verknüpfen
Die Datenbank kennt URLs, aber keine Dateien; die Datei kennt ihr Ereignis, aber nicht immer ihre URL. Das Bindeglied ist die UUID. Ein Quarantäne-Attribut sieht so aus:
0083;66f7c2a1;Safari;6E4C2B1A-...
Das letzte Feld entspricht LSQuarantineEventIdentifier. Vergleichen Sie ohne Beachtung der Groß- und Kleinschreibung, da manche UUIDs kleingeschrieben gespeichert sind. Ein Ereignis kann zu mehreren Dateien gehören: Das Archivierungsprogramm überträgt den Quarantäne-Wert des Archivs samt UUID auf jede Datei, die es entpackt. Das Flag-Feld und die übrigen Teile des Werts behandelt com.apple.quarantine-Flags, das von Browsern gesetzte URL-Attribut kMDItemWhereFroms und Quarantäne.
Wie Quarantine Parser die Datenbank liest
Quarantine Parser läuft vollständig in Ihrem Browser (Rust, kompiliert zu WebAssembly in einem Web Worker); nichts wird hochgeladen. Er verwendet einen eigenen, rein lesenden SQLite-Reader statt einer SQLite-Bibliothek, und das zählt in dreierlei Hinsicht:
- WAL. Eine
-wal-Datei wird so angewendet, wie SQLite es tut: Header- und Frame-Prüfsummen sowie Salts werden geprüft, und nur festgeschriebene Frames werden verwendet. Eine begleitende-journal-Datei wird ebenfalls gelesen. - Unvollständige Kopien. Abgeschnittene, unvollständige oder gesperrte Kopien werden so weit wie möglich gelesen, statt beim ersten Fehler abzubrechen.
- Wiederherstellung. Nicht mehr aktive Zeilen werden per Carving aus dem freien Speicher von Tabellenseiten, aus Freelist-Seiten, älteren Seitenkopien, überholten oder nicht festgeschriebenen WAL-Frames und Rollback-Journal-Seiten gewonnen. Siehe gelöschte QuarantineEventsV2-Einträge wiederherstellen.
Sie können die Datenbanken mehrerer Benutzer gleichzeitig ablegen, auch die lsquarantine_<user>-Kopien von Aftermath, zusammen mit xattr-Listings. Die Ansichten Zeitleiste, Agents, Domains und Dateien zeigen dann jedes Ereignis mit den Dateien, die seine UUID tragen, und die Ansicht Befunde listet Ansatzpunkte auf, keine Urteile.
Weiterlesen
- Dateien sichern: UAC, Aftermath und Velociraptor
- Ein vollständiger Fall: Schritt für Schritt durch eine macOS-Download-Untersuchung
- Was passiert, wenn Gatekeeper ein Objekt ablehnt: .LastGKReject als Gatekeeper-Beweismittel
- Ein einseitiger Spickzettel: Quarantäne-Ereignisse auf macforensics.app
Häufige Fragen
Wo liegt die Datenbank QuarantineEventsV2 unter macOS?
Jeder Benutzer hat eine unter ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2. Es ist eine SQLite-Datenbank mit einer einzigen Tabelle, LSQuarantineEvent. Kopieren Sie sie zusammen mit einer daneben liegenden -wal- oder -journal-Datei.
Speichert QuarantineEventsV2 den Namen der heruntergeladenen Datei?
Nein. Sie speichert URLs, die herunterladende App, einen Zeitstempel und eine Ereignis-UUID, aber weder den lokalen Pfad noch den Dateinamen. Um ein Ereignis einer Datei zuzuordnen, gleichen Sie die UUID am Ende des Attributs com.apple.quarantine der Datei ab.
Wie wandle ich LSQuarantineTimeStamp in ein Datum um?
Es handelt sich um Mac Absolute Time: Sekunden seit 2001-01-01 00:00:00 UTC. Addieren Sie 978307200, um Unix-Zeit zu erhalten, zum Beispiel mit datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch') in sqlite3.
Bedeutet eine leere QuarantineEventsV2, dass nichts heruntergeladen wurde?
Nein. Werkzeuge wie curl, wget, scp, rsync und git nehmen nicht an der Quarantäne teil, Zeilen lassen sich mit sqlite3 löschen und die ganze Datei kann entfernt werden. Fehlende Einträge sind ein schwaches Indiz.