Formato de archivo SEGB de Biome: referencia v1 y v2
Referencia byte a byte de SEGB v1 y v2 de Biome: cabeceras, trailer, estados de registro, CRC32, alineación, horas en el nombre y campos protobuf por flujo.
Resumen. Un flujo (stream) de Biome es una carpeta de archivos SEGB. v1 tiene una cabecera de 56 bytes con SEGB en los bytes 52–55 y el desplazamiento de fin de datos en los bytes 0–3; cada registro tiene una cabecera de 32 bytes (longitud, estado, dos marcas de tiempo, CRC32, desconocido) y se rellena hasta un múltiplo de 8 bytes. v2 empieza por SEGB, un número de entradas y una hora de creación, e indexa sus registros mediante un trailer de entradas de 16 bytes (desplazamiento final, estado, hora) situado al final del archivo; cada registro es un CRC32, un int32 desconocido y el payload, alineado a 4 bytes. Los estados son 1 escrito, 3 eliminado, 4 vacío. Los payloads son protocol buffers sin esquema publicado. Apple no documenta nada de esto.
Esta referencia sigue a ccl-segb de CCL Forensics (licencia MIT), que atribuye a Cellebrite la investigación sobre la estructura v2, y es lo que implementa KnowledgeC Parser. Para saber dónde están estos archivos y por qué importan, empiece por la guía de análisis forense de knowledgeC.db y por knowledgeC frente a Biome.
Dónde están los archivos SEGB
~/Library/Biome/streams/restricted/<Stream>/local/<file>
~/Library/Biome/streams/public/<Stream>/local/<file>
~/Library/Biome/streams/restricted/<Stream>/remote/<device UUID>/<file>
~/Library/Biome/streams/restricted/<Stream>/local/tombstone/<file>
/private/var/db/biome/streams/...
El nombre del flujo (App.InFocus, App.WebUsage, ...) no se guarda en el archivo: lo da la carpeta. Conserve la estructura de carpetas al recolectar, o perderá el nombre del flujo, la distinción entre local y remote y el UUID del dispositivo. Los archivos de tombstone/ están caducados (tombstone de Biome).
Todos los enteros que siguen son little-endian. Las horas están en tiempo absoluto de Mac, guardadas como doubles de 64 bits: segundos desde el 2001-01-01 00:00:00 UTC. Sume 978307200 para obtener tiempo Unix.
SEGB v1
Cabecera de archivo (56 bytes)
| Desplazamiento | Tamaño | Tipo | Campo |
|---|---|---|---|
0x00 | 4 | uint32 | Desplazamiento de fin de datos (absoluto, desde el inicio del archivo) |
0x04 | 48 | No documentado | |
0x34 | 4 | ASCII | Firma SEGB (bytes 52–55) |
Los registros empiezan en el byte 56 (0x38) y llegan hasta el desplazamiento de fin de datos. Si ese desplazamiento apunta más allá del final del archivo, la copia está truncada.
Cabecera de registro (32 bytes)
| Desplazamiento en el registro | Tamaño | Tipo | Campo |
|---|---|---|---|
0x00 | 4 | int32 | Longitud del payload |
0x04 | 4 | int32 | Estado |
0x08 | 8 | double | timestamp1 (tiempo absoluto de Mac) |
0x10 | 8 | double | timestamp2 (tiempo absoluto de Mac) |
0x18 | 4 | uint32 | CRC32 del payload |
0x1C | 4 | int32 | Desconocido |
0x20 | longitud | bytes | Payload |
Después del payload, el siguiente registro empieza en el siguiente múltiplo de 8 contado desde el inicio del archivo. Los nombres timestamp1 y timestamp2 son descriptivos: las fuentes públicas no les asignan un significado fijo en todos los flujos, así que conviene informar de ambos valores en bruto en lugar de etiquetar uno como "inicio" y otro como "fin".
SEGB v2
Cabecera de archivo (32 bytes)
| Desplazamiento | Tamaño | Tipo | Campo |
|---|---|---|---|
0x00 | 4 | ASCII | Firma SEGB |
0x04 | 4 | int32 | Número de entradas (entradas del trailer) |
0x08 | 8 | double | Hora de creación (tiempo absoluto de Mac) |
0x10 | 16 | No documentado |
Trailer (16 bytes por entrada, al final del archivo)
El trailer ocupa los últimos count × 16 bytes del archivo.
| Desplazamiento en la entrada | Tamaño | Tipo | Campo |
|---|---|---|---|
0x00 | 4 | int32 | Desplazamiento final del registro, relativo al byte 32 |
0x04 | 4 | int32 | Estado |
0x08 | 8 | double | Hora (tiempo absoluto de Mac) |
Registros
Los registros empiezan en el byte 32. Cada uno se compone de:
| Desplazamiento en el registro | Tamaño | Tipo | Campo |
|---|---|---|---|
0x00 | 4 | uint32 | CRC32 del payload |
0x04 | 4 | int32 | Desconocido |
0x08 | variable | bytes | Payload, hasta el desplazamiento final |
Un registro v2 no tiene campo de longitud. Para leerlo, ordene las entradas del trailer por desplazamiento final, empiece en el byte 32 y lea hasta 32 + end offset. El siguiente registro empieza en el siguiente límite de 4 bytes. Como el estado y la hora están en el trailer, la cabecera de un registro v2 solo contiene el CRC y la palabra desconocida.
Desplazamientos finales compartidos
Dos entradas del trailer pueden tener el mismo desplazamiento final. El caso habitual es un registro escrito (estado 1) y eliminado más tarde (estado 3): ambas entradas apuntan a los mismos bytes, cada una con su propia hora. Un parser debe informar de las dos, sin tratar la segunda como un registro nuevo. KnowledgeC Parser muestra la entrada eliminada con el mismo payload y la marca como eliminada. También ignora las entradas del trailer con estado 0, que no hacen referencia a ningún dato.
Estados de registro y CRC
| Estado | Significado | Qué hacer |
|---|---|---|
| 1 | Escrito | Registro normal |
| 3 | Eliminado | El payload puede seguir presente: decodifíquelo e infórmelo como eliminado |
| 4 | Vacío / sin usar | No hay registro aprovechable |
El CRC es el CRC-32 estándar tal como lo calcula zlib (zlib.crc32), solo sobre el payload. Una discrepancia indica un registro escrito a medias o dañado, o una copia tomada mientras se escribía el archivo. Conserve esos registros pero márquelos; no los descarte sin avisar.
Un ejemplo v2 comentado
El archivo siguiente se construyó para este artículo con la estructura descrita: un registro App.InFocus que indica que Terminal pasó a primer plano. Es sintético, y aquí los bytes no documentados de la cabecera valen cero.
00000000 53 45 47 42 01 00 00 00 00 00 00 a6 c0 2b c8 41 SEGB.........+.A
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020 67 88 af ff 00 00 00 00 18 01 21 00 00 00 80 ff g.........!.....
00000030 2b c8 41 32 12 63 6f 6d 2e 61 70 70 6c 65 2e 54 +.A2.com.apple.T
00000040 65 72 6d 69 6e 61 6c 00 27 00 00 00 01 00 00 00 erminal.'.......
00000050 66 66 66 80 ff 2b c8 41 fff..+.A
| Bytes | Valor | Significado |
|---|---|---|
0x00–0x03 | SEGB | Firma v2 |
0x04–0x07 | 01 00 00 00 | 1 entrada en el trailer |
0x08–0x0F | double 811041100.0 | Creado el 2026-09-14 01:11:40 UTC |
0x10–0x1F | ceros | No documentado |
0x20–0x23 | 0xffaf8867 | CRC32 del payload |
0x24–0x27 | 0 | Desconocido |
0x28–0x46 | 31 bytes | Payload protobuf (abajo) |
0x47 | 00 | Relleno hasta un límite de 4 bytes |
0x48–0x4B | 27 00 00 00 | Desplazamiento final 39: 32 + 39 = 0x47, el final del payload |
0x4C–0x4F | 01 00 00 00 | Estado 1, escrito |
0x50–0x57 | double 811073280.8 | 2026-09-14 10:08:00.8 UTC |
El payload se decodifica así:
| Bytes | Etiqueta | Campo | Valor |
|---|---|---|---|
18 01 | campo 3, varint | estado | 1 = en primer plano |
21 + 8 bytes | campo 4, 64 bits | hora | 811073280.0 = 10:08:00 UTC |
32 12 + 18 bytes | campo 6, delimitado por longitud | bundle ID | com.apple.Terminal |
Un archivo real de este tipo llevaría como nombre su hora de creación en microsegundos, aquí 811041100000000.
Horas en el nombre de archivo
Los nombres de los archivos SEGB son enteros. mac_apt los interpreta como tiempo Cocoa en microsegundos, lo que da una hora de creación aproximada del archivo:
date -u -r $(( 811041100000000 / 1000000 + 978307200 ))
# Mon Sep 14 01:11:40 UTC 2026
Trátela como un indicio a nivel de archivo. Las horas de los eventos proceden de los registros y, en algunos flujos, del payload.
Payloads protobuf
Los tipos de wire en resumen
Un mensaje protobuf es una secuencia de campos. Cada uno empieza por una clave varint: field number << 3 | wire type (guía de codificación).
| Tipo de wire | Codificación | Contenido habitual |
|---|---|---|
| 0 | Varint | Enteros, booleanos, enums |
| 1 | 8 bytes | double, fixed64 |
| 2 | Longitud varint + bytes | Cadenas, bytes, mensajes anidados |
| 5 | 4 bytes | float, fixed32 |
Los tipos de wire 3 y 4 (grupos) están obsoletos y no se esperan aquí.
Por qué la decodificación sin esquema es ambigua
Apple no publica archivos .proto para Biome. Sin esquema:
- un campo delimitado por longitud puede ser una cadena, bytes en bruto o un mensaje anidado, y cadenas cortas como los bundle IDs a veces se interpretan como mensajes válidos;
- un campo de 8 bytes puede ser un double o un entero;
- un varint puede ser un valor con signo, sin signo, codificado en zigzag o booleano.
KnowledgeC Parser prueba primero texto UTF-8 imprimible, después un mensaje anidado que consuma todos los bytes y, por último, bytes en bruto. Muestra los valores de 8 bytes como doubles, con una fecha cuando el valor es un tiempo absoluto de Mac plausible. Algunos payloads incluyen property lists binarias, que se muestran como tales. Los números de campo se conservan siempre, para que pueda contrastar cualquier interpretación con otra herramienta.
Números de campo por flujo
Estos números proceden de la ingeniería inversa de la comunidad, en el plugin BIOME de mac_apt y en iLEAPP. No son documentación de Apple; valídelos con datos de prueba de la misma compilación del sistema operativo antes de apoyarse en ellos.
| Flujo | Campo | Significado | Fuente |
|---|---|---|---|
App.InFocus | 3 | Estado: 1 en primer plano, 0 fuera de primer plano | mac_apt |
App.InFocus | 4 | Hora (double) | iLEAPP |
App.InFocus | 6 | Bundle ID | mac_apt |
App.InFocus | 9, 10 | Cadenas de versión | mac_apt |
App.WebUsage | 2 | Hora | iLEAPP |
App.WebUsage | 4 / 5 / 6 | URL / dominio / bundle ID | mac_apt |
Safari.* | 1 | Dominio | mac_apt |
ScreenTime.AppUsage | 1 / 3 | Estado / bundle ID | mac_apt |
Notification.Usage | 4 / 8 / 9 | App / título / subtítulo | mac_apt |
Device.Wireless.WiFi | 1 / 2 | SSID / estado | mac_apt |
Device.Wireless.Bluetooth | 1 / 2 / 3 / 4 | Dirección / nombre / ID de producto / estado | mac_apt |
SystemSettings.SearchTerms | 1 | Término de búsqueda | mac_apt |
Los registros de App.InFocus son eventos, no intervalos: a un registro "en primer plano" le sigue más tarde un registro "fuera de primer plano" de la misma app. KnowledgeC Parser los empareja en intervalos (mismo usuario, mismo dispositivo, mismo bundle ID, en un plazo de 24 horas) y deja los registros sin pareja como eventos sueltos.
Lo que se desconoce
- Los 48 bytes no documentados de la cabecera v1, los 16 de la cabecera v2 y el int32 "desconocido" de cada registro.
- Un significado fijo de
timestamp1ytimestamp2de v1 en todos los flujos. - Los campos que no figuran en la tabla anterior, y todos los campos de los flujos sin decodificador público.
App.MenuItem, documentado por Unit 42 en macOS Tahoe 26, no tiene un mapa de campos público: KnowledgeC Parser lo decodifica sin esquema y muestra los números de campo en bruto. - La versión de macOS en la que v2 sustituyó a v1.
Cuando un informe dependa de alguno de estos puntos, indíquelo y muestre el valor en bruto.
Herramientas que leen SEGB
| Herramienta | Qué hace |
|---|---|
| ccl-segb | Lector de referencia para archivos v1 y v2 individuales, registros en bruto |
Plugin BIOME de mac_apt | Lee los flujos desde una imagen o una carpeta y decodifica los flujos anteriores |
| iLEAPP | Flujos de Biome de iOS |
| KnowledgeC Parser | v1 y v2 en el navegador, con estados, comprobación de CRC, origen remote y tombstone, y una cronología combinada con knowledgeC.db |
Preguntas frecuentes
¿Documenta Apple el formato SEGB?
No. Las estructuras de SEGB v1 y v2 proceden de la ingeniería inversa de la comunidad, implementada en ccl-segb (CCL Forensics), que atribuye parte de la investigación a Cellebrite. El significado de los campos del payload procede de mac_apt y de iLEAPP. Todo lo que esas fuentes no cubren debe tratarse como desconocido.
¿Cómo se distingue SEGB v1 de v2?
Busque la firma SEGB. En v1 está en los bytes 52 a 55, al final de una cabecera de 56 bytes. En v2 son los cuatro primeros bytes del archivo, al principio de una cabecera de 32 bytes.
¿Un registro SEGB eliminado sigue conteniendo datos?
A menudo, sí. El estado 3 marca un registro como eliminado, pero el payload puede seguir en el archivo. En v2, una entrada eliminada puede compartir su desplazamiento final con una escrita, de modo que ambas apuntan a los mismos bytes. Informe de esos registros como eliminados, no como actividad vigente.
¿Qué versión de macOS pasó de SEGB v1 a v2?
Ese límite no está documentado públicamente para macOS. En iOS, se indica v1 para iOS 14 a 16 y v2 a partir de iOS 17. Los parsers detectan la versión a partir de la cabecera, así que no es necesario conocerla de antemano.