Décoder les exigences de code csreq dans TCC.db
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.
En bref. La colonne csreq d'une ligne de TCC.db est une exigence de code compilée : un petit blob binaire qui commence par le nombre magique 0xFADE0C00 et encode une règle dans le Code Signing Requirement Language d'Apple, par exemple identifier "com.apple.Terminal" and anchor apple. tccd applique la ligne à tout code qui satisfait cette règle. indirect_object_code_identity contient le même type de blob pour la cible d'une autorisation d'Automatisation. Une exigence réduite à cdhash H"..." épingle un build précis et est typique du code signé ad hoc. Sur un Mac, csreq -r- -t décode un blob ; TCC Parser décode les deux colonnes en texte dans le navigateur.
Pourquoi l'exigence compte plus que le nom
La colonne client indique à qui s'applique la ligne : un bundle ID comme com.example.app, ou un chemin absolu comme /usr/local/bin/tool. Mais un nom se réutilise facilement. N'importe qui peut compiler une app qui revendique le bundle ID com.example.app. Ce qui empêche cet imposteur d'hériter des autorisations de la vraie app, c'est l'exigence enregistrée : quand un programme portant cet identifiant demande le service, tccd vérifie que la signature de code du programme satisfait csreq.
Pour un enquêteur, cela a deux conséquences :
- Une ligne par bundle ID s'applique à tout code qui satisfait l'exigence. Si l'exigence est stricte (ancre Apple, ou un Team ID précis), seuls les builds correctement signés par ce développeur sont acceptés. Si elle est lâche ou absente, la protection est plus faible.
- Le binaire présent aujourd'hui sur le disque n'est peut-être pas celui qui a été approuvé. Mises à jour, remplacements et outils supprimés rompent tous le lien entre « ligne » et « fichier ». L'exigence est l'élément de preuve qui décrit ce qui a été approuvé.
Le format du blob
Une exigence compilée, telle qu'elle est stockée dans csreq, est une structure big-endian :
| Partie | Contenu |
|---|---|
| Nombre magique | 0xFADE0C00 (exigence) |
| Longueur | Longueur totale du blob |
| Type | 1 : forme expression |
| Expression | Une expression préfixée : un opérateur suivi de ses opérandes (chaînes, hachages, champs de certificat, expressions imbriquées) |
Il est rarement nécessaire de parcourir l'expression à la main. Connaître la structure sert à reconnaître le blob (il commence par FA DE 0C 00 dans une vue hexadécimale) et à ne faire confiance à la sortie texte d'un décodeur qu'après avoir vérifié qu'il a consommé la totalité du blob.
Décoder sur un Mac
La méthode native de macOS passe par l'outil csreq. Extrayez le blob sous forme d'octets et fournissez-le sur l'entrée standard :
sqlite3 TCC.db "SELECT hex(csreq) FROM access
WHERE client = 'com.apple.Terminal'
AND service = 'kTCCServiceSystemPolicyAllFiles';" \
| xxd -r -p | csreq -r- -t
Pour comparer avec un binaire présent sur le disque, affichez l'exigence désignée de ce binaire :
codesign -d -r- /System/Applications/Utilities/Terminal.app
Si vous n'êtes pas sur un Mac, ou si vous avez des centaines de lignes, TCC Parser décode chaque blob csreq et indirect_object_code_identity en texte d'exigence et l'affiche à côté de la ligne.
Lire le texte décodé
Les exigences sont des expressions booléennes. Les clauses que vous verrez le plus :
| Clause | Signification |
|---|---|
identifier "com.apple.Terminal" | L'identifiant de signature du code doit être exactement celui-ci |
anchor apple | Signé par Apple elle-même (logiciels d'Apple) |
anchor apple generic | Chaîne jusqu'à la racine d'Apple, ce qui inclut les signatures Developer ID et App Store |
certificate leaf[subject.OU] = TEAMID | Le certificat de signature appartient à ce Team ID |
cdhash H"..." | Le hachage du code directory doit valoir exactement cette valeur : un build précis |
and, or, parenthèses | Combinent les clauses |
Trois motifs couvrent la plupart des lignes :
Logiciels Apple. identifier "com.apple.Terminal" and anchor apple. Seul du code signé par Apple avec cet identifiant est accepté.
Logiciels tiers signés. anchor apple generic and identifier "com.example.app" and certificate leaf[subject.OU] = TEAMID, souvent avec des clauses supplémentaires sur des champs de certificat. Seuls les builds signés par ce Team ID sont acceptés. Le Team ID est un point de pivot solide : recherchez-le dans votre parc, dans d'autres lignes TCC et dans les données de signature de code des binaires présents sur disque.
Code ad hoc ou épinglé. cdhash H"4b6a..." seul. Il n'y a aucune identité au-delà du hachage d'un build. C'est ce que l'on voit en général pour les binaires signés ad hoc (sans certificat de développeur) et pour de nombreux clients identifiés par chemin. Ce n'est pas malveillant en soi : les builds personnels des développeurs, certains outils open source et des scripts compilés localement présentent le même aspect. Mais c'est inhabituel pour un service à fort impact, et cela signifie que l'approbation est liée à un seul build : si le fichier sur le disque a un cdhash différent, ce n'est pas le build qui a été approuvé.
Une exigence absente (csreq nul ou vide) mérite aussi une note. Elle peut être légitime pour certaines lignes définies par le système, mais pour une autorisation visible par l'utilisateur, elle supprime le contrôle d'identité que la ligne porterait normalement.
indirect_object_code_identity : l'autre versant de l'Automatisation
Pour l'automatisation Apple Events (kTCCServiceAppleEvents), une ligne implique deux parties : le client qui envoie les événements et la cible dans indirect_object_identifier (par exemple com.apple.finder ou com.apple.systemevents). indirect_object_code_identity est l'exigence compilée de cette cible, au même format que csreq. Décodez-la de la même façon. Pour des cibles Apple, on attend des exigences anchor apple ; tout le reste mérite une lecture attentive.
Exemple commenté
Dans l'échantillon fictif FIN-MBP-03 fourni avec TCC Parser, la base système contient une ligne Accessibilité pour un client identifié par chemin :
| Champ | Valeur |
|---|---|
service | kTCCServiceAccessibility |
client | /Users/dana.whitlock/Library/Caches/.sync/sync-helper |
client_type | 1 (chemin absolu) |
csreq (décodé) | cdhash H"..." uniquement |
Pris ensemble : un binaire situé dans un dossier caché sous les Caches de l'utilisateur, identifié par son chemin plutôt que par un bundle ID, avec une exigence qui épingle un unique build ad hoc, et qui détient une capacité permettant de piloter l'interface utilisateur. Chaque élément pris isolément a des explications anodines ; réunis, ils justifient d'acquérir ce fichier, d'en calculer l'empreinte, de comparer son cdhash avec l'exigence et de chercher comment il est arrivé là. Comparez avec la ligne d'accès complet au disque de com.apple.Terminal dans la même base, dont l'exigence est une ancre Apple : l'identité ne pose pas de problème, et la question devient qui a utilisé Terminal, et pour quoi faire. C'est le sujet de l'article Enquêter sur l'abus des autorisations TCC.
Comment TCC Parser utilise l'exigence
Dans l'onglet Permissions (autorisations), cliquez sur une ligne pour voir son exigence décodée dans le panneau de détail ; l'onglet Findings (constats) signale les lignes dont l'exigence est ad hoc (cdhash seul) ou absente. Ce signalement invite à regarder de plus près, ce n'est pas un verdict. Le même texte décodé figure dans l'export JSON, ce qui permet de le conserver avec le dossier.
Pièges
- Décoder n'est pas vérifier. Lire l'exigence indique ce qui a été approuvé. Savoir si un fichier donné la satisfait nécessite le fichier et
codesign. - Un identifiant ne prouve pas l'origine.
identifier "com.example.app"sans clause d'ancre ou de Team ID est faible. - Les mises à jour changent les cdhash. Une ligne réduite à un cdhash ne correspond plus après une recompilation, et une nouvelle invite crée un nouvel état de la ligne.
- Le csreq dans
expiredetMDMOverrides.plist. Les lignes expirées conservent leurcsreq, et les entrées MDM portentCodeRequirement(texte) etCodeRequirementData(blob). Comparez-les avec les lignes actives.