Vérifier qu'un processus en cours est toujours le binaire d'origine : /proc/<pid>/exe, sha256 et dpkg -V
Un attaquant qui plante une porte dérobée dans sshd ou nginx laisse le processus tourner. Le fichier sur le disque a été remplacé, ou le processus tourne depuis un fichier supprimé qui n'existe plus nulle part. Trois contrôles qui rendent cela visible sans EDR : le hachage de /proc/<pid>/exe contre une baseline, dpkg -V contre la base des paquets, et le marqueur (deleted) qu'aucun processus légitime ne devrait porter.
Sommaire
- Contrôle 1 : qu'est-ce qui tourne vraiment ? — /proc/<pid>/exe
- Contrôle 2 : le binaire correspond-il encore au paquet ? — dpkg -V
- Contrôle 3 : une baseline qui ne vit pas sur l'hôte
- Contrôle 4 (bonus) : bibliothèques chargées et LD_PRELOAD
- Pièges
- Ce qu'il vous manque encore
- Comment monsys fait
- FAQ
ps montre un processus nommé sshd. Cela ne dit rien de ce qui tourne. Le nom est le premier argument ; le fichier peut avoir été remplacé ; le processus peut avoir été lancé depuis un binaire supprimé ensuite, de sorte qu'il ne reste rien de suspect sur le disque. Qui prend le contrôle d'un serveur remplace rarement ls — il remplace sshd (pour journaliser les mots de passe) ou fait charger à un processus existant une bibliothèque injectée. Cet article vous donne trois contrôles indépendants qui fonctionnent sur tout hôte Linux sans agent, plus le script cron qui les combine.
Contrôle 1 : qu'est-ce qui tourne vraiment ? — /proc/<pid>/exe
Pour chaque processus, le noyau maintient un lien symbolique /proc/<pid>/exe vers le fichier exécuté. Ce lien suit l'inode, pas le nom : si le fichier est remplacé (cp nouveau /usr/sbin/sshd), le lien de l'ancien processus pointe toujours vers l'ancien inode et reçoit le suffixe (deleted).
# Par processus : pid, nom, vrai chemin
sudo ls -l /proc/[0-9]*/exe 2>/dev/null | awk '{print $NF, $(NF-2)}' | sed 's|/proc/\([0-9]*\)/exe|\1|' | sort -u | head
# Processus tournant depuis un fichier SUPPRIMÉ
sudo find /proc -maxdepth 2 -name exe -lname '*(deleted)*' 2>/dev/null \
| while read -r l; do pid=${l#/proc/}; pid=${pid%/exe}; printf '%s\t%s\t%s\n' "$pid" "$(cat /proc/$pid/comm)" "$(sudo readlink "$l")"; done
# 1437 agetty /usr/sbin/agetty (deleted) ← util-linux a été mis à jour ; getty tourne encore l'ancien
Un processus (deleted) est généralement innocent : un service pas encore redémarré après un apt upgrade (voir unattended-upgrades). Mais la combinaison « supprimé » + « aucune mise à jour de paquet dans les logs » + « chemin dans /tmp ou /dev/shm » est précisément la façon dont un malware se cache.
# Processus dont le binaire se trouve dans un répertoire suspect (même sans deleted)
sudo ls -l /proc/[0-9]*/exe 2>/dev/null | awk '{print $NF}' | grep -E '^/(tmp|dev/shm|var/tmp|run/user|home)/' | sort | uniq -c
Contrôle 2 : le binaire correspond-il encore au paquet ? — dpkg -V
Debian et Ubuntu conservent pour chaque fichier installé une somme MD5 dans /var/lib/dpkg/info/*.md5sums. dpkg -V compare les fichiers sur disque à celles-ci.
# Tout ce qui s'écarte du paquet (lit CHAQUE fichier de CHAQUE paquet : comptez des minutes, pas des secondes)
sudo dpkg -V 2>/dev/null | grep -vE '^\?\?5\?+ +c ' | head -30
# Lecture de la sortie : "??5??????" = MD5 différent ; " c " = fichier de config (attendu) ; "missing" = disparu
# Seulement binaires et bibliothèques :
sudo dpkg -V 2>/dev/null | awk '$NF ~ /^\/(usr\/)?(s?bin|lib)/ && $2 != "c"'
Un binaire différent dans /usr/sbin qui n'est pas un fichier de config a exactement deux explications : quelqu'un l'a remplacé, ou vous l'avez patché vous-même et oublié. Famille RHEL : rpm -Va.
La limite : dpkg -V vérifie contre les md5sums que le paquet lui-même a fournies. Qui est root peut aussi les modifier. D'où le contrôle 3.
Contrôle 3 : une baseline qui ne vit pas sur l'hôte
Prenez un sha256 de chaque exécutable en cours et conservez-le en dehors du serveur. Ensuite, comparez chaque jour.
sudo tee /usr/local/sbin/exe-baseline.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
# Affiche le sha256 du fichier derrière /proc/<pid>/exe pour chaque exécutable unique.
# Hache via /proc/<pid>/exe lui-même, de sorte qu'un binaire supprimé est aussi haché.
for l in /proc/[0-9]*/exe; do
pid=${l#/proc/}; pid=${pid%/exe}
target=$(readlink "$l" 2>/dev/null) || continue
[ -z "$target" ] && continue
h=$(sha256sum "$l" 2>/dev/null | awk '{print $1}') || continue
printf '%s\t%s\n' "$h" "$target"
done | sort -u -k2,2
EOF
sudo chmod 0755 /usr/local/sbin/exe-baseline.sh
# Créer la baseline sur un hôte sain, et la conserver LOIN de l'hôte
sudo /usr/local/sbin/exe-baseline.sh > /tmp/exe-baseline.tsv
scp /tmp/exe-baseline.tsv hote-admin:/srv/baselines/$(hostname -s).tsv
La comparaison quotidienne, depuis l'hôte d'administration (pour que la baseline ne soit pas sur la machine contrôlée) :
sudo tee /usr/local/sbin/exe-verify.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
# Tourne sur l'hôte d'administration ; contrôle chaque hôte de hosts.txt contre sa baseline.
NTFY="https://ntfy.example.be/security"
B=/srv/baselines
while read -r h; do
cur=$(ssh -o BatchMode=yes -o ConnectTimeout=5 "$h" sudo /usr/local/sbin/exe-baseline.sh 2>/dev/null) || { echo "$h unreachable"; continue; }
base="$B/$h.tsv"; [ -f "$base" ] || { echo "$cur" > "$base"; echo "$h: baseline created"; continue; }
# Même chemin, hachage différent = remplacé (ou patché). Nouveau chemin = nouveau processus.
changed=$(join -t $'\t' -1 2 -2 2 <(sort -t $'\t' -k2,2 "$base") <(echo "$cur" | sort -t $'\t' -k2,2) | awk -F'\t' '$2!=$3 {print $1}')
new=$(comm -13 <(cut -f2 "$base" | sort) <(echo "$cur" | cut -f2 | sort))
deleted=$(echo "$cur" | grep -c '(deleted)')
msg=""
[ -n "$changed" ] && msg+="CHANGED BINARY: $(echo "$changed" | paste -sd, -)\n"
[ -n "$new" ] && msg+="new executables: $(echo "$new" | paste -sd, -)\n"
[ "$deleted" -gt 0 ] && msg+="$deleted process(es) running from deleted files\n"
[ -n "$msg" ] && printf "$msg" | curl -s -H "Title: integrity $h" -H "Priority: urgent" --data-binary @- "$NTFY" >/dev/null
done < /srv/baselines/hosts.txt
EOF
sudo chmod 0755 /usr/local/sbin/exe-verify.sh
echo '40 * * * * root /usr/local/sbin/exe-verify.sh' | sudo tee /etc/cron.d/exe-verify
Après une mise à jour légitime (apt upgrade + redémarrage du service), le hachage change évidemment. Le script le signale ; vous le confirmez contre dpkg.log et rafraîchissez la baseline :
grep "$(date +%F)" /var/log/dpkg.log | grep ' upgrade ' | awk '{print $4}' # quels paquets aujourd'hui ?
ssh web-01 sudo /usr/local/sbin/exe-baseline.sh > /srv/baselines/web-01.tsv # rafraîchir la baseline APRÈS vérification
Contrôle 4 (bonus) : bibliothèques chargées et LD_PRELOAD
Un processus n'a pas besoin d'être remplacé pour être détourné : une injection de bibliothèque via LD_PRELOAD ou /etc/ld.so.preload suffit. Deux vérifications rapides :
# /etc/ld.so.preload existe-t-il ? Sur un serveur ordinaire, il ne devrait pas.
ls -la /etc/ld.so.preload 2>/dev/null && cat /etc/ld.so.preload
# Processus avec LD_PRELOAD dans leur environnement
for e in /proc/[0-9]*/environ; do pid=${e#/proc/}; pid=${pid%/environ}; sudo cat "$e" 2>/dev/null | tr '\0' '\n' | grep -q '^LD_PRELOAD=' && echo "$pid $(cat /proc/$pid/comm)"; done
# Bibliothèques chargées depuis des chemins suspects
sudo awk '/\.so/ && $NF ~ /^\/(tmp|dev\/shm|var\/tmp|home)\// {print FILENAME, $NF}' /proc/[0-9]*/maps 2>/dev/null | sort -u | head
Pièges
- Oublier de redémarrer après une mise à jour. La plupart des signalements
(deleted)sont des services non redémarrés après une mise à jour de bibliothèque ou de binaire. Ce n'est pas une intrusion, mais une raison d'activerneedrestart— sinon le vrai signalement se noie dans le bruit. - Interpréteurs. Pour un service Python ou Node,
/proc/<pid>/exeest l'interpréteur (/usr/bin/python3.12), pas votre application. Le hachage de l'interpréteur est correct alors que le code de l'application a été modifié. Hachez le répertoire de l'application séparément (find /srv/app -type f -exec sha256sum {} +), ou fiez-vous à git plutôt qu'àdpkg -V. - Baseline sur l'hôte. Une baseline dans
/var/lib/…sur la même machine peut être modifiée en même temps par un attaquant root. D'où l'hôte d'administration. - Prelink et ASLR. Sur les systèmes avec
prelink(rare de nos jours), les binaires sur disque changent légitimement. Debian/Ubuntu ne l'utilisent pas. - Conteneurs.
/proc/<pid>/exed'un processus de conteneur pointe vers un chemin dans le système de fichiers du conteneur (/api,/bin/node_exporter) ; la baseline les contient donc aussi, maisdpkg -Vsur l'hôte n'en sait rien. Hachez par image, ou utilisez Trivy.
Ce qu'il vous manque encore
- La première baseline. Si l'hôte était déjà compromis quand vous avez créé la baseline, chaque écart ensuite est « normal ». Créez-la juste après l'installation, ou comparez avec une installation fraîche du même paquet.
- La corrélation. Un binaire remplacé et une nouvelle clé SSH et une connexion depuis un nouveau pays en une heure : trois scripts, trois messages, pas d'histoire.
- La preuve. Pour une notification d'incident NIS2, vous voulez la chronologie (quand le hachage a changé, quelle session était active) signée et conservée.
Comment monsys fait
L'agent monsys fait cela en continu sous le nom de Process DNA : au premier passage, il prend le SHA256 de /proc/<pid>/exe de chaque exécutable en cours comme baseline, puis signale chaque divergence — binaire remplacé, processus depuis un fichier supprimé, exécutable dans /tmp ou /dev/shm — comme process_dna_divergence. La baseline est dans le hub, pas sur l'hôte. Après une mise à jour de paquet légitime, le hub voit le changement d'inventaire dpkg correspondant et marque le nouveau hachage comme attendu, de sorte que seul l'écart inexpliqué devient une alerte. Voir surveillance d'intégrité dans la documentation.
FAQ
Comment voir si un processus en cours a été modifié ?
Comparez le hachage de /proc/<pid>/exe avec une baseline enregistrée précédemment, et vérifiez si le lien porte le suffixe (deleted). dpkg -V <paquet> compare en plus le fichier sur disque avec la somme de contrôle du paquet.
dpkg -V est-il fiable comme preuve ?
Comme première indication oui ; comme preuve non, parce que les md5sums sont sur le même hôte que les binaires et peuvent être modifiées par root. Une baseline hors de l'hôte (ou un agent qui conserve les hachages ailleurs) est la réponse.
Que faire si un processus tourne depuis un fichier supprimé ?
Vérifiez d'abord le /var/log/dpkg.log du jour : si le paquet vient d'être mis à jour, redémarrez le service. Sinon, et surtout si le chemin est dans /tmp, /dev/shm ou un répertoire home : traitez-le comme un incident, isolez l'hôte et préservez /proc/<pid>/exe (cp /proc/<pid>/exe /root/evidence-<pid>) avant d'arrêter le processus.
Rédigé par l'équipe monsys — des sysadmins qui font cela tous les jours.
Fait à la main ? Laissez monsys s'en charger en continu.
Tout ce que contient ce guide tourne dans monsys comme contrôle permanent, avec historique, alertes et preuves d'audit. 5 serveurs gratuits, hébergés en Belgique, installés en 60 secondes.