Skip to content

Journal de systemd: archivos, huecos de seqnum y .journal~

Análisis forense del journal de systemd sin journalctl: formato, campos de confianza, compresión, huecos de seqnum, archivos .journal~ y campos de hora.

Publicado el 6 min de lectura

En resumen. Un archivo de journal es una base de datos binaria de solo anexado con la firma LPKSHHRH. Cada entrada lleva un número de secuencia que journald incrementa en todos los archivos de una máquina, así que un rango de números que falta es visible incluso después de que las entradas hayan desaparecido. Lee los propios archivos, no solo una exportación de journalctl: las cabeceras, los números de secuencia, los archivos .journal~ y los journals de usuario son donde se ven los borrados.

Dónde están los archivos

RutaContenido
/var/log/journal/<machine-id>/system.journalJournal del sistema activo (almacenamiento persistente)
/var/log/journal/<machine-id>/system@<id>-<seqnum>-<time>.journalJournals del sistema archivados (rotados)
/var/log/journal/<machine-id>/user-<UID>.journalJournal por usuario para los UID normales (por defecto SplitMode=uid)
*.journal~Archivos renombrados tras una parada incorrecta de journald o una corrupción detectada
/run/log/journal/<machine-id>/Journal volátil, se pierde al reiniciar

<machine-id> coincide con /etc/machine-id. Que se escriba algo en /var depende de Storage= en journald.conf: lee la configuración de la imagen en lugar de suponerla.

Por qué merece la pena el journal

El journal de systemd guarda cada entrada como pares FIELD=value. Los campos que empiezan por guion bajo los añade journald a partir de lo que el kernel sabe del remitente, por lo que el proceso que envía el mensaje no puede falsificarlos:

  • _PID, _UID, _GID, _COMM, _EXE, _CMDLINE: quién envió realmente el mensaje.
  • _SYSTEMD_UNIT, _SYSTEMD_USER_UNIT: de qué servicio o servicio de usuario procede.
  • _BOOT_ID: en qué arranque; los reinicios se convierten en límites explícitos.
  • _AUDIT_SESSION, _AUDIT_LOGINUID: la sesión de auditoría y el UID de inicio de sesión, que vinculan la entrada con el ses y el auid de auditd.
  • _TRANSPORT: syslog, journal, stdout, kernel, audit, driver.

MESSAGE, SYSLOG_IDENTIFIER y PRIORITY los proporciona el cliente. Cualquiera puede escribir sshd: Accepted password ... con logger; los campos con guion bajo mostrarán que procede de logger con el UID de ese usuario.

El formato de archivo en resumen

  • Cabecera. Firma LPKSHHRH, indicadores de funciones compatibles e incompatibles, state (offline, online, archived), machine_id, seqnum_id, head_entry_seqnum y tail_entry_seqnum, primera y última marca de tiempo realtime, ID del último arranque y recuentos de objetos. El systemd actual escribe una cabecera de 272 bytes; los lectores deben usar el tamaño de cabecera guardado en el archivo, ya que los archivos antiguos tienen cabeceras más cortas.
  • Objetos. Tras la cabecera vienen objetos con tipo: DATA (un contenido campo=valor, deduplicado por hash), FIELD, ENTRY (la lista de objetos DATA que forman una entrada, con seqnum, realtime, monotonic e ID de arranque), ENTRY_ARRAY, tablas hash y, con Forward Secure Sealing, TAG.
  • Compresión. Los contenidos DATA grandes pueden comprimirse con XZ, LZ4 o ZSTD, indicado en cada objeto. La variante LZ4 guarda el tamaño sin comprimir como un entero de 64 bits little-endian antes del bloque LZ4.
  • Modo compacto. Desde systemd 252, los archivos pueden usar la estructura compacta, con desplazamientos de 32 bits en lugar de 64. Las versiones antiguas de journalctl rechazan esos archivos, lo que es un motivo para usar un lector reciente.

Un analizador que recorre los objetos directamente puede leer entradas que journalctl omitiría, por ejemplo en un archivo cuya cabecera no se actualizó tras un fallo.

Números de secuencia: la prueba de manipulación integrada

Cada entrada tiene un seqnum. journald incrementa un único contador por máquina (identificado por seqnum_id) y lo usa en todos los archivos que escribe, tanto del sistema como de usuario. Ordena por seqnum todas las entradas de todos los archivos con un mismo seqnum_id, y los números deberían ser continuos. Un hueco en el seqnum del journal significa que entradas que existieron no están en los archivos que tienes.

Primero, las causas inocentes:

  • Journals de usuario no recopilados. Las entradas escritas en user-1001.journal también consumen números. Si solo se recopiló system.journal, cada entrada de usuario aparece como un hueco.
  • Archivos eliminados por vacuum. journalctl --vacuum-* y la rotación por tamaño borran los archivos archivados antiguos; en ese caso el hueco está al principio del rango conservado.
  • Límites de rotación entre un archivo archivado que tienes y otro que no.

El caso interesante es un hueco dentro de la ventana del incidente, en un conjunto donde están todos los journals de usuario, o un hueco que coincide con un journal de usuario borrado a mano. En el ejemplo de la herramienta, el atacante elimina user-1001.journal: el journal del sistema conserva sus números, pero faltan las entradas que fueron al archivo de usuario, y las líneas del gestor de usuario que contenían solo sobreviven en /var/log/syslog (rsyslog también las recibió).

Archivos sucios y en línea

  • Un archivo .journal~ se renombró porque journald no lo cerró correctamente o lo encontró corrupto. A menudo contiene los últimos minutos antes de un fallo o de un apagado brusco. Analízalo; el final puede estar escrito a medias.
  • Un state online en la cabecera de un archivo recopilado significa que journald lo tenía abierto. Es normal en un system.journal copiado de un host en vivo, pero las últimas entradas pueden estar incompletas.
  • Un archivo cuya cabecera dice contener más entradas de las que se pueden leer, o cuyo final está dañado, merece una nota en el informe; no lo descartes.

Campos de hora

CampoSignificado
__REALTIME_TIMESTAMPCuándo recibió journald la entrada, en microsegundos desde la época Unix (UTC)
_SOURCE_REALTIME_TIMESTAMPLa hora de confianza más temprana del mensaje en su origen, cuando difiere de la recepción; journalctl muestra esta si existe
__MONOTONIC_TIMESTAMPMicrosegundos desde el arranque, con sentido junto a _BOOT_ID

Los valores realtime siguen el reloj del sistema. Un reloj atrasado a mano se ve como un realtime que baja mientras el seqnum sube; dentro de un mismo arranque, el reloj monotónico da el orden real.

Leer exportaciones

Si no dispones de los archivos en bruto, journalctl -o export no pierde información por entrada (los valores binarios llevan un prefijo de longitud) y -o json es cómodo. Ambos pierden las cabeceras de los archivos y se limitan a lo que journalctl pudo leer. Consulta la guía de recopilación.

journalctl -D /mnt/evidence/var/log/journal --header       # rangos de archivos, estado, ids de seqnum
journalctl -D /mnt/evidence/var/log/journal --list-boots --utc
journalctl -D /mnt/evidence/var/log/journal -o export > journal.export

En el navegador

Linux Log Parser analiza archivos de journal normales y compactos, campos comprimidos con XZ/LZ4/ZSTD, archivos .journal~ y exportaciones, informa de los huecos en los números de secuencia (y avisa cuando no se cargó ningún journal de usuario), señala los archivos sucios y en línea, y marca las líneas de auth.log que también contiene el journal. Las líneas de autenticación presentes en el journal pero ausentes de auth.log se notifican como un hallazgo aparte, tratado en la guía sobre manipulación.

La ficha del journal de systemd en linuxforensics.app detalla los ajustes de retención y los comandos de journalctl.

Artículos relacionados

Cómo probar que se editaron o borraron logs de Linux: huecos de seqnum, líneas ausentes de auth.log, registros wtmp a cero, daemons detenidos y borrados.
Qué recopilar para investigar logs de Linux y cómo: un comando tar en un host en vivo, journalctl, ausearch, UAC, Velociraptor o una imagen de disco montada.
Investigación de logs de Linux en un host de salto sintético: adivinación de contraseñas SSH, acceso desde una IP nueva, sudo -i, persistencia y manipulación.