Skip to content

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.

Veröffentlicht am 7 Min. Lesezeit

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)

FeldHinweise
Magic0x377f0682 oder 0x377f0683 (legt die Byte-Reihenfolge der Prüfsumme fest)
Formatversion3007000
SeitengrößeWie in der Datenbank
Checkpoint-SequenzWird mit jedem Checkpoint erhöht
Salt-1, Salt-2Zufallswerte, 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-HeaderHinweise
SeitennummerWelche Seite der Datenbank dieser Frame ersetzt
DatenbankgrößeGröße in Seiten nach dem Commit, bei Commit-Frames; sonst 0
Salt-1, Salt-2Müssen mit dem WAL-Header übereinstimmen, damit der Frame gültig ist
Checksum-1, Checksum-2Laufende 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:

QuelleWas sie enthältWie wahrscheinlich
Frühere Commits in der aktuellen WALÄltere Kopien derselben Seite, von vor einem späteren Commit in derselben WALHäufig, wenn zwischen zwei Checkpoints mehrere Änderungen stattfanden
Frames einer veralteten GenerationFrames aus einer früheren WAL-Generation (andere Salts), noch nicht überschriebenHäufig: Nach einem Checkpoint wird die WAL wiederverwendet und mit neuen Salts neu begonnen, nicht genullt
Unbestätigte FramesFrames nach dem letzten Commit-FrameGelegentlich; Änderungen, die nie bestätigt wurden
Freie Seiten in der HauptdateiSeiten, die freigegeben wurden, nachdem Zeilen oder Tabellen geschrumpft sindGelegentlich; 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:

  1. 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.
  2. 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.
  3. 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, csreq oder last_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:

Quelleserviceclientauth_valuelast_modified (UTC)
WAL, früherer CommitkTCCServiceAccessibility/Users/dana.whitlock/Library/Caches/.sync/sync-helper2 (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.yaml von UAC, das TCC-Modul von Aftermath) kopieren nur TCC.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, mit expired_at.
  • Unified Log: Entscheidungen von tccd unter com.apple.TCC, soweit die Aufbewahrung reicht.
  • Telemetrie von Endpoint Security (ab macOS 15.4): Ereignisse zu TCC-Änderungen.

Checkliste

  1. Sichern Sie TCC.db, TCC.db-wal und TCC.db-shm zusammen, für das System und jeden Benutzer, bevor jemand tccutil ausführt oder die Dateien öffnet.
  2. Bilden Sie Hashes und arbeiten Sie dann auf Kopien der Kopien.
  3. Laden Sie den Ordner in TCC Parser und öffnen Sie History.
  4. Notieren Sie für jede entfernte oder geänderte Zeile mit hoher Tragweite die Quelle (WAL-Commit, veraltete Generation, unbestätigt, freie Seite).
  5. Suchen Sie die passende Änderung im Unified Log, in der Shell-Historie und in EDR-Daten.
  6. Prüfen Sie Snapshots und Backups auf eine vollständige frühere Kopie.

Glossar: SQLite-WAL, tccutil.

Verwandte Artikel

Verwandte Artikel

Wie sich die Tabelle access in TCC.db von Mojave über Big Sur bis Sonoma verändert hat: allowed, auth_value, auth_reason, pid, last_reminded und Nebentabellen.
Schritt für Schritt: TCC.db samt WAL in einen kostenlosen Browser-Parser laden, Befunde lesen, Berechtigungen und Verlauf prüfen, Zeitraum setzen, exportieren.
So lesen Sie die Blobs csreq und indirect_object_code_identity in TCC.db: kompilierte Code-Anforderungen, anchor, identifier und ad hoc signierte Clients.