Skip to content

Journal systemd : fichiers, trous de seqnum, .journal~

Analyse forensique des fichiers journal systemd sans journalctl : format, champs fiables, compression, trous de seqnum, fichiers .journal~ et champs d’heure.

Publié le 6 min de lecture

En bref. Un fichier journal est une base de données binaire en ajout seul, de signature LPKSHHRH. Chaque entrée porte un numéro de séquence que journald incrémente sur l’ensemble des fichiers d’une machine : une plage de numéros manquante reste donc visible même après la disparition des entrées. Lisez les fichiers eux-mêmes, pas seulement un export journalctl : c’est dans les en-têtes, les numéros de séquence, les fichiers .journal~ et les journaux utilisateur que les suppressions apparaissent.

Où se trouvent les fichiers

CheminContenu
/var/log/journal/<machine-id>/system.journalJournal système actif (stockage persistant)
/var/log/journal/<machine-id>/system@<id>-<seqnum>-<time>.journalJournaux système archivés (tournés)
/var/log/journal/<machine-id>/user-<UID>.journalJournal par utilisateur pour les UID ordinaires (SplitMode=uid par défaut)
*.journal~Fichiers renommés après un arrêt non propre de journald ou une corruption détectée
/run/log/journal/<machine-id>/Journal volatil, perdu au redémarrage

<machine-id> correspond à /etc/machine-id. L’écriture dans /var dépend de Storage= dans journald.conf : lisez la configuration dans l’image plutôt que de la supposer.

Pourquoi le journal en vaut la peine

Le journal systemd stocke chaque entrée sous forme de paires FIELD=value. Les champs qui commencent par un tiret bas sont ajoutés par journald à partir de ce que le noyau sait de l’émetteur ; le processus émetteur ne peut donc pas les falsifier :

  • _PID, _UID, _GID, _COMM, _EXE, _CMDLINE : qui a réellement envoyé le message.
  • _SYSTEMD_UNIT, _SYSTEMD_USER_UNIT : de quel service ou service utilisateur il provient.
  • _BOOT_ID : quel démarrage ; les redémarrages deviennent des frontières explicites.
  • _AUDIT_SESSION, _AUDIT_LOGINUID : la session d’audit et l’UID de connexion, qui relient l’entrée au ses et à l’auid d’auditd.
  • _TRANSPORT : syslog, journal, stdout, kernel, audit, driver.

MESSAGE, SYSLOG_IDENTIFIER et PRIORITY sont fournis par le client. N’importe qui peut écrire sshd: Accepted password ... avec logger ; les champs à tiret bas montreront que le message vient de logger, sous l’UID de cet utilisateur.

Le format de fichier en bref

  • En-tête. Signature LPKSHHRH, indicateurs de fonctionnalités compatibles et incompatibles, state (offline, online, archived), machine_id, seqnum_id, head_entry_seqnum et tail_entry_seqnum, premier et dernier horodatages realtime, identifiant du dernier démarrage et nombres d’objets. Le systemd actuel écrit un en-tête de 272 octets ; les lecteurs doivent utiliser la taille d’en-tête stockée dans le fichier, car les fichiers plus anciens ont des en-têtes plus courts.
  • Objets. Après l’en-tête viennent des objets typés : DATA (une charge champ=valeur, dédoublonnée par hachage), FIELD, ENTRY (la liste des objets DATA formant une entrée, avec seqnum, realtime, monotonic et identifiant de démarrage), ENTRY_ARRAY, des tables de hachage et, avec Forward Secure Sealing, TAG.
  • Compression. Les grosses charges DATA peuvent être compressées en XZ, LZ4 ou ZSTD, signalé objet par objet. La variante LZ4 stocke la taille décompressée sous forme d’entier 64 bits little-endian avant le bloc LZ4.
  • Mode compact. Depuis systemd 252, les fichiers peuvent utiliser la structure compacte, avec des offsets 32 bits au lieu de 64 bits. Les anciennes versions de journalctl refusent ces fichiers, ce qui est une raison d’utiliser un lecteur récent.

Un parseur qui parcourt directement les objets peut lire des entrées que journalctl ignorerait, par exemple dans un fichier dont l’en-tête n’a pas été mis à jour après un crash.

Numéros de séquence : la preuve d’altération intégrée

Chaque entrée possède un seqnum. journald incrémente un compteur par machine (identifié par seqnum_id) et l’utilise dans tous les fichiers qu’il écrit, système comme utilisateur. Triez toutes les entrées de tous les fichiers par seqnum au sein d’un même seqnum_id : les numéros doivent être continus. Un trou dans les seqnum du journal signifie que des entrées qui ont existé ne figurent pas dans les fichiers dont vous disposez.

Les causes bénignes d’abord :

  • Journaux utilisateur non collectés. Les entrées écrites dans user-1001.journal consomment aussi des numéros. Si seul system.journal a été collecté, chaque entrée utilisateur apparaît comme un trou.
  • Archives purgées (vacuum). journalctl --vacuum-* et la rotation fondée sur la taille suppriment les anciens fichiers archivés ; le trou se situe alors au début de la plage conservée.
  • Frontières de rotation entre une archive que vous avez et une autre que vous n’avez pas.

Le cas intéressant est un trou à l’intérieur de la fenêtre de l’incident, dans un ensemble où tous les journaux utilisateur sont présents, ou un trou qui coïncide avec un journal utilisateur supprimé à la main. Dans l’exemple de l’outil, l’attaquant supprime user-1001.journal : le journal système conserve ses numéros, mais les entrées parties dans le fichier utilisateur manquent, et les lignes du gestionnaire utilisateur qu’elles contenaient ne survivent que dans /var/log/syslog (rsyslog les avait reçues aussi).

Fichiers sales et fichiers en ligne

  • Un fichier .journal~ a été renommé parce que journald ne l’a pas fermé proprement ou l’a jugé corrompu. Il contient souvent les dernières minutes avant un crash ou une coupure brutale. Analysez-le ; la fin peut être partiellement écrite.
  • Un state d’en-tête online dans un fichier collecté signifie que journald l’avait ouvert. C’est normal pour un system.journal copié depuis un hôte actif, mais les dernières entrées peuvent être incomplètes.
  • Un fichier dont l’en-tête annonce plus d’entrées qu’on ne peut en lire, ou dont la fin est endommagée, mérite d’être mentionné dans le rapport, pas écarté.

Champs d’heure

ChampSignification
__REALTIME_TIMESTAMPMoment où journald a reçu l’entrée, en microsecondes depuis l’epoch (UTC)
_SOURCE_REALTIME_TIMESTAMPHeure fiable la plus ancienne du message à sa source, lorsqu’elle diffère de la réception ; journalctl affiche celle-ci lorsqu’elle existe
__MONOTONIC_TIMESTAMPMicrosecondes depuis le démarrage, significatives avec _BOOT_ID

Les valeurs realtime suivent l’horloge système. Une horloge reculée se voit quand realtime diminue alors que seqnum augmente ; au sein d’un même démarrage, l’horloge monotonic donne l’ordre réel.

Lire les exports

Si les fichiers bruts ne sont pas disponibles, journalctl -o export est sans perte entrée par entrée (les valeurs binaires sont préfixées par leur longueur) et -o json est pratique. Les deux perdent les en-têtes de fichiers et se limitent à ce que journalctl a pu lire. Voir le guide de collecte.

journalctl -D /mnt/evidence/var/log/journal --header       # plages des fichiers, état, identifiants seqnum
journalctl -D /mnt/evidence/var/log/journal --list-boots --utc
journalctl -D /mnt/evidence/var/log/journal -o export > journal.export

Dans le navigateur

Linux Log Parser analyse les fichiers journal standard et compacts, les champs compressés en XZ/LZ4/ZSTD, les fichiers .journal~ et les exports, signale les trous de numéros de séquence (et avertit lorsqu’aucun journal utilisateur n’a été chargé), repère les fichiers sales et en ligne, et marque les lignes d’auth.log que le journal contient aussi. Les lignes d’authentification présentes dans le journal mais absentes d’auth.log font l’objet d’un constat distinct, traité dans le guide sur l’altération.

La fiche sur le journal systemd de linuxforensics.app détaille les paramètres de rétention et les commandes journalctl.

Articles liés

Prouver que des journaux Linux ont été modifiés ou supprimés : trous de seqnum, lignes absentes d’auth.log, wtmp effacé, démons arrêtés, commandes d’effacement.
Quoi collecter pour une investigation sur les journaux Linux et comment : tar sur un hôte actif, export journalctl, ausearch, UAC, Velociraptor ou image disque.
Investigation des journaux d’un bastion synthétique : essais de mots de passe SSH, nouvelle IP, sudo -i, persistance cron et systemd, journaux altérés.