Entfernte TCC-Freigaben aus TCC.db-wal wiederherstellen
Wie gelöschte oder geänderte TCC-Berechtigungen in TCC.db-wal und freien Seiten überdauern: Grundlagen des SQLite-WAL, Spuren von tccutil reset und die Grenzen.
Kurz gesagt. TCC.db bewahrt nur den aktuellen Zustand jeder Berechtigung: Eine neue Entscheidung ersetzt die Zeile, und tccutil reset löscht sie. TCC.db läuft aber im Write-Ahead-Log-Modus von SQLite, und die Datei TCC.db-wal daneben kann noch ältere Kopien der Seiten enthalten, auf denen diese Zeilen standen: ältere Commits, Frames aus einer vorherigen WAL-Generation, manchmal unbestätigte Frames. Auch freie Seiten in der Hauptdatei können Reste enthalten. TCC Parser spielt die bestätigte WAL ein, um den aktuellen Zustand zu zeigen, und legt jede frühere oder entfernte Zeile, die er wiederherstellen kann, im Tab History (Verlauf) neben den aktuellen Wert. Nichts davon überdauert garantiert. Sichern Sie die WAL also früh und behandeln Sie das Gefundene als unterstützenden Beleg.
Warum der Verlauf überhaupt fehlt
Die Tabelle access hat eine Zeile pro Client, Dienst und Ziel (der Primärschlüssel lautet service, client, client_type, indirect_object_identifier). Legt ein Benutzer einen Schalter um, beantwortet er eine Abfrage oder ändert ein MDM-Profil eine Freigabe, wird diese Zeile überschrieben, und last_modified rückt vor. Führt jemand Folgendes aus:
tccutil reset Accessibility
tccutil reset ScreenCapture com.example.agent
werden die passenden Zeilen gelöscht (tccutil erwartet den Dienstnamen ohne das Präfix kTCCService und optional eine Bundle-ID). Ein Zurücksetzen in den Systemeinstellungen oder das Entfernen einer App hat dieselbe Wirkung. Die Hauptdatenbank zeigt dann keine Spur mehr davon, dass die Freigabe existierte. Das ist ein Problem, wenn ein Eindringling sich eine Fähigkeit freigibt, sie nutzt und sie vor dem Rückzug wieder entfernt.
SQLite-WAL in fünf Minuten
Im Write-Ahead-Log-Modus schreibt SQLite Änderungen nicht sofort in die Hauptdatei. Es hängt sie an die -wal-Datei an und überträgt sie später, bei einem Checkpoint, zurück. Das Format ist auf sqlite.org dokumentiert; für die Wiederherstellung zählen diese Teile:
WAL-Header (32 Bytes)
| Feld | Hinweise |
|---|---|
| Magic | 0x377f0682 oder 0x377f0683 (legt die Byte-Reihenfolge der Prüfsumme fest) |
| Formatversion | 3007000 |
| Seitengröße | Wie in der Datenbank |
| Checkpoint-Sequenz | Wird mit jedem Checkpoint erhöht |
| Salt-1, Salt-2 | Zufallswerte, bei jedem Neustart der WAL geändert |
| Checksum-1, Checksum-2 | Über den Header |
Frames, einer nach dem anderen: ein 24-Byte-Frame-Header, gefolgt von einer vollständigen Kopie einer Datenbankseite.
| Feld im Frame-Header | Hinweise |
|---|---|
| Seitennummer | Welche Seite der Datenbank dieser Frame ersetzt |
| Datenbankgröße | Größe in Seiten nach dem Commit, bei Commit-Frames; sonst 0 |
| Salt-1, Salt-2 | Müssen mit dem WAL-Header übereinstimmen, damit der Frame gültig ist |
| Checksum-1, Checksum-2 | Laufende Prüfsumme über die bisherigen Frames |
Ein lesender Prozess spielt Frames ein, deren Salts zum Header passen und deren laufende Prüfsumme gültig ist, bis zum letzten Commit-Frame, und verwendet die neueste bestätigte Kopie jeder Seite. Das ist der aktuelle Zustand der Datenbank.
Wo sich die alten Zeilen verstecken
Das Einspielen liefert Ihnen die Gegenwart. Die Vergangenheit steckt in den Frames, die beim Einspielen nicht verwendet werden:
| Quelle | Was sie enthält | Wie wahrscheinlich |
|---|---|---|
| Frühere Commits in der aktuellen WAL | Ältere Kopien derselben Seite, von vor einem späteren Commit in derselben WAL | Häufig, wenn zwischen zwei Checkpoints mehrere Änderungen stattfanden |
| Frames einer veralteten Generation | Frames aus einer früheren WAL-Generation (andere Salts), noch nicht überschrieben | Häufig: Nach einem Checkpoint wird die WAL wiederverwendet und mit neuen Salts neu begonnen, nicht genullt |
| Unbestätigte Frames | Frames nach dem letzten Commit-Frame | Gelegentlich; Änderungen, die nie bestätigt wurden |
| Freie Seiten in der Hauptdatei | Seiten, die freigegeben wurden, nachdem Zeilen oder Tabellen geschrumpft sind | Gelegentlich; hängt davon ab, wie SQLite den Platz wiederverwendet hat |
TCC.db ist klein. Die Tabellenseiten mit den Zeilen von access werden daher oft neu geschrieben, und jedes Neuschreiben hinterlässt eine weitere Kopie in der WAL. Deshalb überdauert eine vor wenigen Minuten oder Stunden entfernte Freigabe oft, eine vor Monaten entfernte meist nicht.
Was TCC Parser damit macht
Liegt neben einer Datenbank eine -wal-Datei (mit demselben Basisnamen), geht TCC Parser so vor:
- Er validiert den WAL-Header und spielt bestätigte Frames so ein, wie SQLite es tun würde, um den aktuellen Zustand aufzubauen. Die
-shm-Datei wird nicht benötigt. - Er dekodiert die Zeilenzellen in den anderen Kopien jeder Seite: ältere Commits, unbestätigte Frames und Frames veralteter Generationen, dazu freie Seiten in der Hauptdatei.
- Er vergleicht jede wiederhergestellte Zeile über den Primärschlüssel mit dem aktuellen Zustand und zeigt sie im Tab History als entfernte Zeile (nicht mehr vorhanden) oder als frühere Version (anderer Wert in
auth_value,auth_reason,csreqoderlast_modified).
Der Tab Findings meldet „grants removed or changed“ (Freigaben entfernt oder geändert), wenn eine wiederhergestellte Zeile von der aktuellen abweicht, zum Beispiel eine erlaubte Zeile für einen Dienst mit hoher Tragweite, die nicht mehr in der Datenbank steht.
Beispiel: eine entfernte Freigabe für Bedienungshilfen
Im fiktiven Beispiel FIN-MBP-03 enthält der aktuelle Zustand der System-Datenbank keine Zeile für Bedienungshilfen zum pfadbasierten Hilfsprogramm /Users/dana.whitlock/Library/Caches/.sync/sync-helper. Die WAL erzählt eine andere Geschichte:
| Quelle | service | client | auth_value | last_modified (UTC) |
|---|---|---|---|---|
| WAL, früherer Commit | kTCCServiceAccessibility | /Users/dana.whitlock/Library/Caches/.sync/sync-helper | 2 (erlaubt) | 2026-09-14 10:19:05 |
| Aktueller Zustand | (keine Zeile) |
In der Geschichte des Beispiels erfolgte die Entfernung um 10:47:12 UTC. Löschungen hinterlassen in der Tabelle kein eigenes last_modified. Der Zeitpunkt der Entfernung ergibt sich daher aus anderen Belegen: dem zeitlichen Ablauf der WAL-Commits, dem Unified Log (subsystem == "com.apple.TCC"), einer Shell-Historie mit tccutil oder EDR-Telemetrie. Ab macOS 15.4 stellt Endpoint Security TCC-Änderungen für Sicherheitswerkzeuge bereit (ES_EVENT_TYPE_NOTIFY_TCC_MODIFY); Ihr EDR hat die Änderung also womöglich direkt aufgezeichnet.
Grenzen, die Sie im Bericht nennen sollten
- Das Überdauern ist Zufall. Ein Checkpoint, gefolgt von genügend neuen Schreibvorgängen, überschreibt alte Frames. Eine ruhige Datenbank kann einen alten Frame lange behalten, eine viel genutzte verliert ihn schnell.
- Kein verlässlicher Löschzeitpunkt. Eine wiederhergestellte Zeile liefert den Zustand und ihr
last_modified, nicht den Zeitpunkt der Löschung. - Die Reihenfolge innerhalb einer WAL-Generation ist verlässlich, über Generationen hinweg wird sie abgeleitet. Veraltete Frames einer älteren Generation stammen aus der Zeit vor der aktuellen, Lücken sind aber unbekannt.
- Die Sicherung zerstört leicht Beweismittel. Wer die Datenbank mit einem schreibenden Werkzeug öffnet oder SQLite beim Schließen einen Checkpoint ausführen lässt, schreibt die WAL neu. Mehrere Sicherungswerkzeuge (
tcc.yamlvon UAC, das TCC-Modul von Aftermath) kopieren nurTCC.db. Siehe TCC.db unter macOS: Speicherort und Sicherung. - Wiederhergestellt ist nicht dasselbe wie aktuell. Berichten Sie wiederhergestellte Zeilen als „einen früheren, aus der WAL wiederhergestellten Zustand“, mit dem Quell-Frame, nicht als bestehende Freigaben.
Weitere Fundorte
- Lokale APFS-Snapshots und Time Machine: vollständige ältere Kopien der Datenbank. Der zuverlässigste Weg, eine entfernte Freigabe zu sehen.
- Die Tabelle
expired: Clients, deren Freigaben abgelaufen sind, mitexpired_at. - Unified Log: Entscheidungen von
tccduntercom.apple.TCC, soweit die Aufbewahrung reicht. - Telemetrie von Endpoint Security (ab macOS 15.4): Ereignisse zu TCC-Änderungen.
Checkliste
- Sichern Sie
TCC.db,TCC.db-walundTCC.db-shmzusammen, für das System und jeden Benutzer, bevor jemandtccutilausführt oder die Dateien öffnet. - Bilden Sie Hashes und arbeiten Sie dann auf Kopien der Kopien.
- Laden Sie den Ordner in TCC Parser und öffnen Sie History.
- Notieren Sie für jede entfernte oder geänderte Zeile mit hoher Tragweite die Quelle (WAL-Commit, veraltete Generation, unbestätigt, freie Seite).
- Suchen Sie die passende Änderung im Unified Log, in der Shell-Historie und in EDR-Daten.
- Prüfen Sie Snapshots und Backups auf eine vollständige frühere Kopie.
Glossar: SQLite-WAL, tccutil.