Skip to content

Récupérer des autorisations TCC supprimées dans TCC.db-wal

Comment des autorisations TCC supprimées ou modifiées survivent dans TCC.db-wal et les pages libérées : bases du WAL SQLite, traces de tccutil reset, limites.

Publié le 8 min de lecture

En bref. TCC.db ne conserve que l'état courant de chaque autorisation : une nouvelle décision remplace la ligne, et tccutil reset la supprime. Mais TCC.db fonctionne en mode journal à écriture anticipée (write-ahead log) de SQLite, et le fichier TCC.db-wal qui l'accompagne peut encore contenir des copies antérieures des pages qui renfermaient ces lignes : commits plus anciens, trames d'une génération précédente du WAL, parfois des trames non validées. Les pages libérées du fichier principal peuvent aussi contenir des restes. TCC Parser rejoue le WAL validé pour afficher l'état courant, et place chaque ligne antérieure ou supprimée qu'il parvient à récupérer dans l'onglet History (historique), à côté de la valeur courante. Rien de tout cela n'est garanti de survivre : collectez le WAL tôt et traitez ce que vous y trouvez comme une preuve d'appui.

Pourquoi l'historique manque au départ

La table access contient une ligne par client, service et cible (la clé primaire est service, client, client_type, indirect_object_identifier). Lorsqu'un utilisateur bascule un interrupteur, répond à une invite ou qu'un profil MDM modifie une autorisation, cette ligne est écrasée, et last_modified avance. Lorsque quelqu'un exécute :

tccutil reset Accessibility
tccutil reset ScreenCapture com.example.agent

les lignes correspondantes sont supprimées (tccutil prend le nom du service sans le préfixe kTCCService, et éventuellement un bundle ID). Une réinitialisation dans les Réglages Système ou la suppression d'une app ont le même effet. La base principale ne montre alors plus aucune trace de l'existence de l'autorisation. C'est un problème lorsqu'un intrus accorde une capacité, l'utilise, puis la retire avant de partir.

Le WAL SQLite en cinq minutes

En mode journal à écriture anticipée, SQLite n'écrit pas immédiatement les modifications dans le fichier principal. Il les ajoute au fichier -wal et les recopie plus tard, lors d'un checkpoint. Le format est documenté sur sqlite.org ; voici ce qui compte pour la récupération :

En-tête du WAL (32 octets)

ChampNotes
Nombre magique0x377f0682 ou 0x377f0683 (détermine l'ordre des octets de la somme de contrôle)
Version du format3007000
Taille de pageIdentique à celle de la base
Séquence de checkpointIncrémentée à chaque checkpoint
Salt-1, Salt-2Valeurs aléatoires, changées à chaque redémarrage du WAL
Checksum-1, Checksum-2Calculées sur l'en-tête

Trames (frames), les unes à la suite des autres : un en-tête de trame de 24 octets suivi d'une copie complète d'une page de la base.

Champ de l'en-tête de trameNotes
Numéro de pagePage de la base que cette trame remplace
Taille de la baseTaille en pages après le commit, pour les trames de commit ; 0 sinon
Salt-1, Salt-2Doivent correspondre à l'en-tête du WAL pour que la trame soit valide
Checksum-1, Checksum-2Somme de contrôle cumulée sur les trames précédentes

Un lecteur rejoue les trames dont les salts correspondent à l'en-tête et dont la somme de contrôle cumulée est valide, jusqu'à la dernière trame de commit, et utilise la copie validée la plus récente de chaque page. C'est l'état courant de la base.

Où se cachent les anciennes lignes

Le rejeu vous donne le présent. Le passé se trouve dans les trames que le rejeu n'utilise pas :

SourceCe qu'elle contientProbabilité
Commits antérieurs dans le WAL courantDes copies plus anciennes de la même page, antérieures à un commit ultérieur dans le même WALFréquent lorsque plusieurs modifications ont eu lieu entre deux checkpoints
Trames d'une génération périméeDes trames laissées par une génération antérieure du WAL (salts différents), pas encore écraséesFréquent : après un checkpoint, le WAL est réutilisé et redémarré avec de nouveaux salts, pas remis à zéro
Trames non validéesDes trames postérieures à la dernière trame de commitOccasionnel ; modifications jamais validées
Pages libérées du fichier principalDes pages libérées après la réduction de lignes ou de tablesOccasionnel ; dépend de la façon dont SQLite a réutilisé l'espace

TCC.db est petite, si bien que les pages de table qui contiennent les lignes d'access sont souvent réécrites, et chaque réécriture laisse une copie supplémentaire dans le WAL. C'est pourquoi une autorisation supprimée il y a quelques minutes ou quelques heures survit souvent, alors qu'une autorisation supprimée il y a plusieurs mois ne survit généralement pas.

Ce qu'en fait TCC Parser

Lorsqu'un fichier -wal se trouve à côté d'une base (même nom de base), TCC Parser :

  1. Valide l'en-tête du WAL et rejoue les trames validées, comme le ferait SQLite, pour construire l'état courant. Le fichier -shm n'est pas nécessaire.
  2. Décode les cellules de lignes dans les autres copies de chaque page : commits antérieurs, trames non validées et trames de génération périmée, ainsi que les pages libérées du fichier principal.
  3. Compare chaque ligne récupérée avec l'état courant par clé primaire et l'affiche dans l'onglet History comme ligne supprimée (qui n'existe plus) ou comme version antérieure (avec un auth_value, un auth_reason, un csreq ou un last_modified différent).

L'onglet Findings (constats) signale « grants removed or changed » (autorisations supprimées ou modifiées) lorsqu'une ligne récupérée diffère de la ligne courante, par exemple une ligne Allowed pour un service à fort impact qui ne figure plus dans la base.

Exemple : une autorisation Accessibilité supprimée

Dans l'échantillon fictif FIN-MBP-03, l'état courant de la base système ne contient aucune ligne Accessibilité pour l'utilitaire identifié par chemin /Users/dana.whitlock/Library/Caches/.sync/sync-helper. Le WAL raconte une autre histoire :

Sourceserviceclientauth_valuelast_modified (UTC)
WAL, commit antérieurkTCCServiceAccessibility/Users/dana.whitlock/Library/Caches/.sync/sync-helper2 (autorisé)2026-09-14 10:19:05
État courant(aucune ligne)

Dans le scénario de l'échantillon, la suppression a eu lieu à 10:47:12 UTC. Les suppressions ne laissent pas de last_modified propre dans la table : l'heure de la suppression provient donc d'autres éléments, à savoir la chronologie de la séquence de commits du WAL, le journal unifié (subsystem == "com.apple.TCC"), un historique du shell montrant tccutil, ou la télémétrie EDR. À partir de macOS 15.4, Endpoint Security expose les modifications TCC aux outils de sécurité (ES_EVENT_TYPE_NOTIFY_TCC_MODIFY) : votre EDR a donc peut-être enregistré directement le changement.

Les limites à mentionner dans un rapport

  • La survie est opportuniste. Un checkpoint suivi de suffisamment de nouvelles écritures écrase les anciennes trames. Une base peu active peut conserver une ancienne trame longtemps ; une base très sollicitée la perd vite.
  • Pas d'heure de suppression fiable. Une ligne récupérée donne l'état et son last_modified, pas le moment de sa suppression.
  • L'ordre au sein d'une génération du WAL est fiable ; entre générations, il est déduit. Les trames périmées d'une génération plus ancienne sont antérieures à la génération courante, mais les intervalles sont inconnus.
  • La collecte détruit facilement des preuves. Ouvrir la base avec un outil qui écrit, ou laisser SQLite effectuer un checkpoint à la fermeture, réécrit le WAL. Plusieurs collecteurs (le tcc.yaml d'UAC, le module TCC d'Aftermath) ne copient que TCC.db. Voir TCC.db sous macOS : emplacement et acquisition.
  • Récupéré ne veut pas dire courant. Présentez les lignes récupérées comme « un état antérieur récupéré dans le WAL », avec la trame source, et non comme des autorisations actives.

Autres endroits où chercher

  • Snapshots APFS locaux et Time Machine : des copies complètes et plus anciennes de la base. Le moyen le plus fiable de voir une autorisation qui a été supprimée.
  • La table expired : les clients dont les autorisations ont expiré, avec expired_at.
  • Journal unifié : les décisions de tccd sous com.apple.TCC, dans la limite de la durée de rétention.
  • Télémétrie Endpoint Security (macOS 15.4+) : événements de modification TCC.

Liste de contrôle

  1. Collectez ensemble TCC.db, TCC.db-wal et TCC.db-shm pour le système et pour chaque utilisateur, avant que quiconque n'exécute tccutil ou n'ouvre les fichiers.
  2. Calculez les empreintes, puis travaillez sur des copies des copies.
  3. Chargez le dossier dans TCC Parser et ouvrez History.
  4. Pour chaque ligne à fort impact supprimée ou modifiée, notez la source (commit du WAL, génération périmée, trame non validée, page libérée).
  5. Cherchez la modification correspondante dans le journal unifié, l'historique du shell et les données EDR.
  6. Vérifiez les snapshots et les sauvegardes pour trouver une copie antérieure complète.

Glossaire : WAL SQLite, tccutil.

Articles liés

Articles liés

L'évolution de la table access de TCC.db de Mojave à Big Sur et Sonoma : allowed, auth_value, auth_reason, pid, last_reminded et les autres tables.
Pas à pas : charger les TCC.db de macOS et leur WAL dans un parseur gratuit, lire constats, autorisations et historique, filtrer une période, exporter.
Lire les blobs csreq et indirect_object_code_identity de TCC.db : exigences compilées, clauses anchor et identifier, clients ad hoc réduits à un cdhash.