Decodificar los requisitos de código csreq de TCC.db
Cómo leer los blobs csreq e indirect_object_code_identity de TCC.db: requisitos compilados, anclas, identificadores y clientes ad hoc con solo cdhash.
Resumen. La columna csreq de una fila de TCC.db es un requisito de código compilado: un pequeño blob binario que empieza por el número mágico 0xFADE0C00 y codifica una regla en el Code Signing Requirement Language de Apple, como identifier "com.apple.Terminal" and anchor apple. tccd aplica la fila a cualquier código que cumpla esa regla. indirect_object_code_identity contiene el mismo tipo de blob para el destino de una concesión de Automatización. Un requisito que solo es cdhash H"..." fija una compilación exacta y es típico del código con firma ad hoc. En un Mac, csreq -r- -t decodifica un blob; TCC Parser decodifica ambas columnas a texto en el navegador.
Por qué el requisito importa más que el nombre
La columna client indica para quién es la fila: un bundle ID como com.example.app o una ruta absoluta como /usr/local/bin/tool. Pero un nombre es fácil de reutilizar. Cualquiera puede compilar una app que declare el bundle ID com.example.app. Lo que impide que ese impostor herede los permisos de la app real es el requisito almacenado: cuando un programa con ese identificador pide el servicio, tccd comprueba si la firma de código del programa cumple csreq.
Para el investigador, eso tiene dos consecuencias:
- Una fila por bundle ID se aplica a cualquier código que cumpla el requisito. Si el requisito es estricto (ancla de Apple o un Team ID concreto), solo cumplen las compilaciones de ese desarrollador firmadas correctamente. Si es laxo o no existe, la protección es más débil.
- Puede que el binario que hay hoy en disco no sea el que se aprobó. Las actualizaciones, las sustituciones y las herramientas borradas rompen el vínculo entre "fila" y "archivo". El requisito es la evidencia que describe lo que se aprobó.
El formato del blob
Un requisito compilado, tal como se guarda en csreq, es una estructura big-endian:
| Parte | Contenido |
|---|---|
| Número mágico | 0xFADE0C00 (requisito) |
| Longitud | Longitud total del blob |
| Tipo | 1: forma de expresión |
| Expresión | Una expresión prefija: un operador seguido de sus operandos (cadenas, hashes, campos de certificado, expresiones anidadas) |
Rara vez tendrá que recorrer la expresión a mano. Conocer el formato sirve para reconocer el blob (en una vista hexadecimal empieza por FA DE 0C 00) y para fiarse del texto que devuelve un decodificador solo tras comprobar que ha consumido el blob entero.
Decodificar en un Mac
La forma nativa de macOS es la herramienta csreq. Extraiga el blob como bytes y páselo por la entrada estándar:
sqlite3 TCC.db "SELECT hex(csreq) FROM access
WHERE client = 'com.apple.Terminal'
AND service = 'kTCCServiceSystemPolicyAllFiles';" \
| xxd -r -p | csreq -r- -t
Para compararlo con un binario en disco, muestre el requisito designado de ese binario:
codesign -d -r- /System/Applications/Utilities/Terminal.app
Si no trabaja en un Mac, o tiene cientos de filas, TCC Parser decodifica cada blob csreq e indirect_object_code_identity a texto de requisito y lo muestra junto a la fila.
Cómo leer el texto decodificado
Los requisitos son expresiones booleanas. Las cláusulas que verá con más frecuencia:
| Cláusula | Significado |
|---|---|
identifier "com.apple.Terminal" | El identificador de firma del código debe ser exactamente este |
anchor apple | Firmado por la propia Apple (software de Apple) |
anchor apple generic | Encadena con la raíz de Apple, lo que incluye las firmas Developer ID y App Store |
certificate leaf[subject.OU] = TEAMID | El certificado de firma pertenece a este Team ID |
cdhash H"..." | El hash del code directory debe ser exactamente este valor: una compilación concreta |
and, or, paréntesis | Combinan cláusulas |
Tres patrones cubren la mayoría de las filas:
Software de Apple. identifier "com.apple.Terminal" and anchor apple. Solo cumple el código firmado por Apple con ese identificador.
Software de terceros firmado. anchor apple generic and identifier "com.example.app" and certificate leaf[subject.OU] = TEAMID, a menudo con cláusulas adicionales sobre campos del certificado. Solo cumplen las compilaciones firmadas por ese Team ID. El Team ID es un buen punto de pivote: búsquelo en su flota, en otras filas TCC y en los datos de firma de código de los binarios en disco.
Código ad hoc o fijado. cdhash H"4b6a..." a secas. No hay más identidad que el hash de una compilación. Es lo que suele verse en los binarios con firma ad hoc (sin certificado de desarrollador) y en muchos clientes identificados por ruta. No es malicioso por sí mismo: las compilaciones propias de los desarrolladores, algunas herramientas de código abierto y los scripts compilados localmente tienen el mismo aspecto. Pero es inusual en un servicio de alto impacto, y significa que la aprobación está ligada a una sola compilación: si el archivo en disco tiene otro cdhash, no es la compilación que se aprobó.
Un requisito ausente (csreq nulo o vacío) también merece una nota. Puede ser legítimo en algunas filas fijadas por el sistema, pero en una concesión visible para el usuario elimina la comprobación de identidad que la fila llevaría normalmente.
indirect_object_code_identity: el otro lado de la Automatización
En la automatización mediante Apple Events (kTCCServiceAppleEvents), una fila tiene dos partes: el client que envía los eventos y el destino en indirect_object_identifier (por ejemplo, com.apple.finder o com.apple.systemevents). indirect_object_code_identity es el requisito compilado de ese destino, en el mismo formato que csreq. Se decodifica igual. Para destinos de Apple cabe esperar requisitos anchor apple; cualquier otra cosa merece una lectura atenta.
Ejemplo práctico
En la muestra ficticia FIN-MBP-03 que incluye TCC Parser, la base de datos del sistema contiene una fila de Accesibilidad para un cliente identificado por ruta:
| Campo | Valor |
|---|---|
service | kTCCServiceAccessibility |
client | /Users/dana.whitlock/Library/Caches/.sync/sync-helper |
client_type | 1 (ruta absoluta) |
csreq (decodificado) | solo cdhash H"..." |
Leído en conjunto: un binario en una carpeta oculta dentro de Caches del usuario, identificado por ruta y no por bundle ID, con un requisito que fija una única compilación ad hoc, y con una capacidad que permite controlar la interfaz de usuario. Cada elemento por separado tiene explicaciones inocentes; juntos son motivo para adquirir ese archivo, calcular su hash, comparar su cdhash con el requisito y averiguar cómo llegó. Compárelo con la fila de acceso total al disco de com.apple.Terminal en la misma base de datos, cuyo requisito es un ancla de Apple: la identidad es correcta, y la pregunta pasa a ser quién usó Terminal y para qué. Ese es el tema de investigar el abuso de permisos TCC.
Cómo usa TCC Parser el requisito
En la pestaña Permissions (permisos), haga clic en una fila para ver su requisito decodificado en el panel de detalle, y la pestaña Findings (hallazgos) marca las filas cuyo requisito es ad hoc (solo cdhash) o no existe. La marca es una invitación a mirar, no un veredicto. El mismo texto decodificado aparece en la exportación JSON, así que puede conservarlo con el caso.
Errores frecuentes
- Decodificar no es verificar. Leer el requisito indica lo que se aprobó. Saber si un archivo concreto lo cumple requiere el archivo y
codesign. - Los identificadores no prueban el origen.
identifier "com.example.app"sin una cláusula de ancla o de Team ID es débil. - Las actualizaciones cambian los cdhash. Una fila con solo cdhash deja de coincidir tras una recompilación, y una nueva solicitud crea un nuevo estado de la fila.
- El csreq de
expiredy deMDMOverrides.plist. Las filas caducadas conservan sucsreq, y las entradas MDM llevanCodeRequirement(texto) yCodeRequirementData(blob). Compárelos con las filas activas.