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.
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
| Chemin | Contenu |
|---|---|
/var/log/journal/<machine-id>/system.journal | Journal système actif (stockage persistant) |
/var/log/journal/<machine-id>/system@<id>-<seqnum>-<time>.journal | Journaux système archivés (tournés) |
/var/log/journal/<machine-id>/user-<UID>.journal | Journal 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 auseset à 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_seqnumettail_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
DATApeuvent ê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.journalconsomment aussi des numéros. Si seulsystem.journala é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
stated’en-tête online dans un fichier collecté signifie que journald l’avait ouvert. C’est normal pour unsystem.journalcopié 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
| Champ | Signification |
|---|---|
__REALTIME_TIMESTAMP | Moment où journald a reçu l’entrée, en microsecondes depuis l’epoch (UTC) |
_SOURCE_REALTIME_TIMESTAMP | Heure fiable la plus ancienne du message à sa source, lorsqu’elle diffère de la réception ; journalctl affiche celle-ci lorsqu’elle existe |
__MONOTONIC_TIMESTAMP | Microsecondes 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.