ActivitiesCache.db-wal: Warum die WAL-Datei zählt
Wie das SQLite-Write-Ahead-Log die jüngste Windows-Timeline-Aktivität in ActivitiesCache.db-wal verbirgt, wie Checkpoints arbeiten und wie man es auswertet.
Kurz gesagt. ActivitiesCache.db läuft im WAL-Modus von SQLite: Bestätigte Änderungen landen in ActivitiesCache.db-wal und erreichen die Hauptdatei erst bei einem Checkpoint (standardmäßig, wenn das WAL 1.000 Seiten erreicht). Auf einem laufenden oder kürzlich genutzten System steht die jüngste Aktivität oft nur im WAL. Sichern Sie beide Dateien zum selben Zeitpunkt, öffnen Sie die Originale nie mit einem normalen SQLite-Client und verwenden Sie einen Parser, der bestätigte Frames einspielt und anzeigt, welche Zeilen aus dem WAL stammen.
Wenn Sie aus dieser Serie nur eines mitnehmen, dann das: Eine Windows Timeline ohne ihr WAL ist eine Timeline, die zu früh endet. Und „zu früh“ heißt meist „kurz vor dem Vorfall“, weil der Vorfall jung ist.
Wie Write-Ahead-Logging funktioniert
Das Write-Ahead-Log von SQLite kehrt das klassische Journal um. Statt Originalseiten beiseitezulegen und Änderungen in die Datenbank zu schreiben, lässt SQLite die Datenbank unberührt und hängt geänderte Seiten an eine separate -wal-Datei an. Eine Transaktion gilt als bestätigt, sobald ihr Commit-Datensatz im WAL steht (sqlite.org/wal.html).
Leser suchen jede Seite zuerst im WAL und greifen sonst auf die Datenbankdatei zurück. Das Zurückschreiben der Seiten in die Datenbank heißt Checkpoint. Standardmäßig löst SQLite ihn automatisch aus, wenn ein Commit das WAL über 1.000 Seiten wachsen lässt (sqlite.org). kacos2000 beobachtete dasselbe bei dieser Datenbank und stellte in den untersuchten Dateien eine Seitengröße von 4.096 Byte fest (kacos2000).
Was das für Timeline bedeutet:
| Situation | Wo die neuesten Zeilen stehen |
|---|---|
| Benutzer aktiv, WAL unter der Checkpoint-Schwelle | Nur im WAL |
| Checkpoint gerade gelaufen | In der Datenbank; das WAL kann zurückgesetzt und wiederverwendet werden |
| Letzte Verbindung sauber geschlossen | Zurückgeschrieben; das WAL wird „normalerweise“ gelöscht (sqlite.org) |
| Absturz, Stromausfall, Stecker gezogen | Das WAL kann mit bestätigten, nicht zurückgeschriebenen Transaktionen auf der Platte bleiben (sqlite.org) |
Eine Timeline-Zeile wird nicht nur einmal eingefügt. Eine Fokussitzung (Typ 6) wird aktualisiert, solange sie andauert: EndTime und activeDurationSeconds wachsen. Auch diese Aktualisierungen landen zuerst im WAL. Das WAL bestimmt also nicht nur, welche Zeilen existieren, sondern auch, was sie aussagen.
Im Inneren der WAL-Datei
Das Format ist in der SQLite-Dateiformatspezifikation dokumentiert:
| Struktur | Größe | Relevante Felder |
|---|---|---|
| WAL-Header | 32 Byte | Magic, Seitengröße, Checkpoint-Sequenznummer, Salt-1, Salt-2, Prüfsumme |
| Frame-Header | 24 Byte | Seitennummer, Datenbankgröße nach dem Commit (nur bei Commit-Frames ungleich null), Salts, kumulative Prüfsumme |
| Frame-Inhalt | eine Seite | Die neue Version dieser Datenbankseite |
Ein Frame ist nur gültig, wenn seine Salts zum Header passen und seine kumulative Prüfsumme stimmt. Ein Frame mit einem Feld „Datenbankgröße“ ungleich null ist ein Commit-Frame: Er schließt eine Transaktion ab. Frames nach dem letzten Commit-Frame gehören zu einer nie bestätigten Transaktion und müssen ignoriert werden (sqlite.org).
Nach einem Checkpoint, wenn der nächste Schreiber das WAL zurücksetzt, wird Salt-1 erhöht und Salt-2 zufällig neu gesetzt, was die alten Frames ungültig macht. Laut Spezifikation kann die Datei dabei gekürzt werden, muss aber nicht (sqlite.org). Genau hier liegt die forensische Chance: Frames früherer Generationen können hinter den aktuellen noch vorhanden sein.
Warum ein normaler SQLite-Client das falsche Werkzeug ist
ActivitiesCache.db mit einer gewöhnlichen SQLite-Bibliothek zu öffnen, funktioniert, und sie liest sogar das WAL mit, wenn es neben der Datenbank liegt. Das Problem ist, was danach passiert. Laut SQLite-Dokumentation führt die zuletzt geschlossene Verbindung einen letzten Checkpoint aus und löscht dann das WAL und die zugehörige Shared-Memory-Datei (sqlite.org/wal.html).
Anders gesagt: Schon das Hineinschauen kann die Datenbank umschreiben und das WAL vernichten, samt veralteter Frames mit älteren Daten. Faustregeln:
- Hashen Sie die Originale und arbeiten Sie mit Kopien.
- Wenn Sie einen SQLite-Client nutzen müssen, öffnen Sie eine Kopie oder verwenden Sie die Modi „read-only“ bzw. „immutable“ auf einer Kopie, die Sie opfern können.
- Bevorzugen Sie einen Parser, der die Bytes selbst liest und nie schreibt.
Wie der Windows Timeline Parser mit dem WAL umgeht
Der Windows Timeline Parser bringt einen eigenen, rein lesenden SQLite-Leser mit, geschrieben in Rust und nach WebAssembly kompiliert. Er enthält keine SQLite-Engine und keinen Codepfad, der schreibt. Mit dem WAL:
- Prüft er Magic, Seitengröße und Header-Prüfsumme des WAL.
- Durchläuft er die Frames, solange die Salts passen und die kumulative Prüfsumme stimmt.
- Legt er die letzte bestätigte Version jeder Seite über die Datenbank; Frames einer unbestätigten Transaktion werden gezählt und ignoriert.
- Wertet er das Ergebnis aus, anschließend die Datenbank ohne WAL, und vergleicht Zeile für Zeile.
- Markiert er jede Zeile als Nur im WAL (neu seit dem letzten Checkpoint) oder Im WAL geändert (die Datenbank enthält eine ältere Version).
Außerdem schützt er vor einem klassischen Fehler: einem -wal aus einer anderen Datenbank. Nichts im Dateiformat bindet ein WAL an seine Datenbank; ein naiver Leser würde ein WAL mit gleicher Seitengröße stillschweigend anwenden. Das Werkzeug prüft, ob die eingespielte Datenbank noch eine plausible Timeline ist, und ignoriert das WAL andernfalls mit einer Warnung.
Diese Markierungen machen aus „das WAL zählt“ praktische Triage. Filtern Sie auf Nur im WAL, und Sie sehen die Aktivität seit dem letzten Checkpoint; bei einem im laufenden Zustand gesicherten Rechner sind das oft die letzten Stunden vor der Sicherung.
Was das WAL enthalten kann, das Ihnen niemand zeigt
Drei Arten von Daten können im WAL oder in dessen Umfeld überdauern:
| Daten | Wo | Wertet dieses Werkzeug sie heute aus? |
|---|---|---|
| Bestätigte, nicht zurückgeschriebene Zeilen | Aktuelle WAL-Frames | Ja |
| Unbestätigte Transaktion | Frames nach dem letzten Commit | Gezählt, ignoriert (bewusst) |
| Ältere Seitenversionen | Frames einer früheren Salt-Generation | Nein (gemeldet, nicht gecarvt) |
| Gelöschte Zeilen | Freeblocks und Freelist-Seiten der Datenbank | Nein |
Forschung zur SQLite-Wiederherstellung, etwa die auf der DFRWS vorgestellte Arbeit zu bring2lite, zeigt, dass sich gelöschte Datensätze aus diesen Strukturen wiederherstellen lassen (Meng und Baier, 2019). Das Zwischenablage-Carving-Werkzeug von kacos2000 stellt laut Autor gelöschte Einträge sowohl aus der Datenbank als auch aus dem WAL wieder her (kacos2000/WindowsTimeline). Hängt Ihr Fall an gelöschten Timeline-Daten, arbeiten Sie mit einer Kopie und einem Carving-Werkzeug und dokumentieren Sie die Methode getrennt von der Zeitachse der aktiven Datensätze.
Checkliste
-
ActivitiesCache.dbundActivitiesCache.db-walzum selben Zeitpunkt gesichert (Sicherungsleitfaden). - Originale gehasht; Auswertung nur an Kopien.
- Der Parser meldet, wie viele Frames bestätigt, wie viele ignoriert wurden und warum er stoppte.
- Der Bericht unterscheidet Zeilen, die nur im WAL stehen, von zurückgeschriebenen Zeilen.
- Veraltete WAL-Frames und freie Seiten für Carving aufbewahrt, falls Löschungen relevant sind.
FAQ
Was ist ActivitiesCache.db-wal?
Das SQLite-Write-Ahead-Log der Windows-Timeline-Datenbank. Bestätigte Änderungen werden zuerst dort angehängt und erst bei einem Checkpoint nach ActivitiesCache.db kopiert, daher enthält das WAL oft die jüngste Aktivität.
Ist es sicher, ActivitiesCache.db in DB Browser for SQLite zu öffnen?
Nur mit einer Kopie. Eine normale SQLite-Verbindung liest das WAL neben der Datenbank und kann es beim Schließen der letzten Verbindung in die Hauptdatei zurückschreiben und löschen, was die Dateien verändert.
Kann das WAL gelöschte Aktivität enthalten?
Ja, das ist möglich. Alte Frames früherer WAL-Generationen werden durch einen geänderten Salt ungültig, aber nicht zwingend überschrieben, und können ältere Seitenversionen enthalten. Ihre Wiederherstellung erfordert Carving-Werkzeuge; die meisten Parser ignorieren sie.