Accroché à Linux : Ingénierie de la détection des rootkits

Dans cette deuxième partie d'une série en deux parties, nous explorons l'ingénierie de la détection des rootkits Linux, en nous concentrant sur les limites de la fiabilité de la détection statique et sur l'importance de la détection comportementale des rootkits.

Introduction

Dans la première partie, nous avons examiné le fonctionnement des rootkits Linux : leur évolution, leur taxonomie et les techniques de manipulation de l'espace utilisateur et de l'espace noyau. Dans cette deuxième partie, nous nous intéressons à l'ingénierie de la détection. Nous commençons par montrer pourquoi la détection statique est souvent peu fiable contre les rootkits Linux, même lorsque les binaires ne sont que trivialement modifiés, puis nous passons aux signaux comportementaux et d'exécution que les défenseurs peuvent utiliser à la place. De l'abus d'objets partagés au chargement de LKM en passant par l'eBPF, l'io_uring, la persistance et l'évasion des défenses, cet article se concentre sur les moyens pratiques de détecter et d'enquêter sur l'activité des rootkits dans des environnements réels.

Détection statique via VirusTotal

Avant de se concentrer sur les techniques de détection comportementale, il est utile d'examiner dans quelle mesure les mécanismes de détection statique traditionnels permettent d'identifier les rootkits Linux. Pour ce faire, nous avons mené une petite expérience en utilisant VirusTotal comme substitut à la détection antivirus traditionnelle basée sur les signatures. Un ensemble de données de dix rootkits Linux a été constitué à partir d'articles de recherche accessibles au public et de dépôts de logiciels libres. Chaque échantillon a été soit téléchargé sur VirusTotal, soit récupéré à partir de soumissions existantes.

Pour chaque rootkit, nous avons enregistré le nombre de moteurs antivirus qui ont signalé le binaire original. Nous avons ensuite effectué deux tests supplémentaires :

  1. Binaires dépouillés, créés à l'aide de strip --strip-all, en supprimant les tables de symboles et d'autres métadonnées non essentielles.
  2. Binaires trivialement modifiés, créés en ajoutant un seul octet nul au fichier d'origine : une modification intentionnellement peu sophistiquée.

L'objectif n'était pas d'échapper à la détection par une obscurcissement avancé, mais d'évaluer la fragilité des signatures statiques face aux modifications binaires les plus simples.

Tableau 1 : Aperçu technique de l'ensemble de données analysées sur les rootkits

RootkitDétections de baseDépouilléAjout d'un octet nul
Azazel36/6619/6621/66
Bedevil*32/6632/6621/66
BrokePKG7/663/663/66
Diamorphine33/668/6422/66
Kovid27/661/6615/66
Mobkit29/666/6617/66
Reptile32/663/6620/66
Snapekit30/663/6619/66
Symbiote42/668/6622/66
Triple-A.T. Cross Ltd.31/6617/6619/66

* Bedevil est dépouillé par défaut et les détections de base et dépouillées sont donc les mêmes.

Observations

Comme prévu, l'élimination des binaires a généralement entraîné une forte baisse des taux de détection. Dans plusieurs cas, les détections sont tombées à un niveau proche de zéro, ce qui suggère que certains moteurs antivirus s'appuient fortement sur les informations relatives aux symboles ou sur d'autres métadonnées facilement amovibles. L'impact de l'ajout d'un seul octet nul est encore plus révélateur : une modification qui n'altère pas la logique du programme, le flux d'exécution ou le comportement, mais qui dégrade néanmoins de manière significative la détection pour de nombreux échantillons.

Cela met en évidence une faiblesse fondamentale de la détection statique basée sur les signatures. Si une modification d'un octet peut avoir une incidence significative sur les résultats de la détection, les attaquants n'ont pas besoin d'un obscurcissement sophistiqué pour échapper aux scanners statiques.

Techniques d'obscurcissement dans les rootkits

Il est intéressant de noter que la plupart des rootkits de cet ensemble de données n'utilisent que peu ou pas d'obscurcissement statique avancé. Lorsque l'obscurcissement est présent, il se limite généralement à un simple codage XOR de chaînes ou de données de configuration, ou à des techniques d'empaquetage légères qui modifient légèrement l'agencement binaire. Ces méthodes sont peu coûteuses à mettre en œuvre et suffisent à déjouer de nombreuses signatures statiques.

L'absence d'obscurcissement plus avancé dans ces échantillons est remarquable. Nombre d'entre eux sont des rootkits à code source ouvert conçus pour démontrer des techniques plutôt que pour échapper de manière agressive à la détection. Pourtant, même avec une obscurité minimale ou inexistante, la détection statique s'avère peu fiable.

Pourquoi la détection statique ne suffit pas

Cette expérience renforce un point essentiel : la détection statique seule est fondamentalement insuffisante pour une détection fiable des rootkits. La fragilité des signatures statiques (en particulier face à des modifications triviales) signifie que les défenseurs ne peuvent pas compter sur les indicateurs basés sur les fichiers ou la détection basée sur les hachages pour découvrir les menaces furtives.

Lorsque les binaires peuvent être modifiés sans affecter le comportement, le seul signal cohérent restant est le comportement du rootkit au moment de l'exécution. C'est pourquoi la suite de ce blog passe des artefacts statiques à l'analyse dynamique et à la détection comportementale, en examinant comment les rootkits interagissent avec le système d'exploitation, manipulent le flux d'exécution et laissent des traces observables au cours de l'exécution.

C'est là que l'ingénierie de détection devient à la fois plus difficile et beaucoup plus efficace.

Ingénierie de détection dynamique

Techniques de détection du chargement des rootkits en zone utilisateur

Les rootkits Userland détournent souvent le processus de liaison dynamique, injectant des objets partagés malveillants dans les processus cibles sans avoir besoin d'un accès au niveau du noyau. Une infection commence par la création d'un fichier d'objets partagés. La détection des fichiers d'objets partagés nouvellement créés peut se faire au moyen d'une règle de détection similaire à celle affichée ci-dessous :

file where event.action == "creation" and
(file.extension like~ "so" or file.name like~ "*.so.*")

Ces fichiers sont souvent écrits dans des chemins inscriptibles ou éphémères tels que /tmp/, /dev/shm/, ou dans des sous-répertoires cachés sous les répertoires personnels des utilisateurs. Les attaquants peuvent les télécharger, les compiler ou les déposer directement à partir d'un chargeur. Cette connaissance peut être appliquée à la règle de détection ci-dessus pour réduire le bruit.

Par exemple, dans la télémétrie présentée ci-dessus, on peut voir l'acteur de la menace utiliser scp pour télécharger un fichier d'objets partagés dans un sous-répertoire caché de /tmp, puis le déplacer dans un répertoire de bibliothèque, en essayant de se fondre dans la masse. Nous avons détecté cette menace, ainsi que d'autres menaces similaires, via :

Une fois que le fichier d'objets partagés est présent sur le système, l'attaquant dispose de plusieurs options pour l'activer. Les mécanismes les plus couramment utilisés sont la variable d'environnement LD_PRELOAD, le fichier /etc/ld.so.preload et les chemins de configuration de l'éditeur de liens dynamiques tels que /etc/ld.so.conf.

La variable d'environnement LD_PRELOAD permet à un attaquant de spécifier un objet partagé qui sera chargé avant toute autre bibliothèque lors de l'exécution d'un binaire à liaison dynamique. Cela permet de remplacer complètement les fonctions de libc, telles que execve(), open() ou readdir(). Cette méthode fonctionne par processus et ne nécessite pas d'accès root.

Pour détecter cette technique, la télémétrie de la variable d'environnement LD_PRELOAD est nécessaire. Une fois qu'elle est disponible, il est possible d'écrire une logique de détection des valeurs inhabituelles sur le site LD_PRELOAD. Par exemple :

process where event.type == "start" and event.action == "exec" and
process.env_vars != null

Comme le montre la figure 1, c'est également l'étape suivante pour les attaquants. Les attaquants ont déplacé libz.so.1 de /tmp/.X12-unix/libz.so.1 à /usr/local/lib/libz.so.1.

Pour une plus grande fidélité, nous avons mis en œuvre cette logique à l'aide du type de règle new_terms, en ne signalant que les entrées d'objets partagés inédites dans la variable LD_PRELOAD via :

Bien entendu, si les variables d'environnement collectées ne se limitent pas à LD_PRELOAD et LD_LIBRARY_PATH, la règle ci-dessus doit être modifiée pour inclure ces deux éléments spécifiquement. Pour réduire le bruit, il convient de procéder à une analyse statistique et/ou à une analyse de base.

Une autre méthode d'activation consiste à exploiter le fichier /etc/ld.so.preload. S'il est présent, ce fichier oblige l'éditeur de liens dynamiques à injecter l'objet partagé répertorié dans chaque binaire lié dynamiquement sur le système, ce qui se traduit par une injection globale.

Une méthode similaire consiste à modifier la configuration de l'éditeur de liens dynamiques afin de donner la priorité aux chemins d'accès aux bibliothèques malveillantes. Pour ce faire, vous pouvez modifier /etc/ld.so.conf ou ajouter des entrées à /etc/ld.so.conf.d/, puis exécuter ldconfig pour mettre à jour le cache. Cela modifie le chemin de résolution des bibliothèques critiques, telles que libc.so.6.

Ces scénarios peuvent être détectés en surveillant les fichiers /etc/ld.so.preload et /etc/ld.so.conf, ainsi que le répertoire /etc/ld.so.conf.d/ pour les événements de création/modification. En utilisant cette télémétrie brute, il est possible de mettre en œuvre une règle de détection pour signaler ces événements :

file where event.action in ("creation", "rename") and
file.path like ("/etc/ld.so.preload", "/etc/ld.so.conf", "/etc/ld.so.conf.d/*")

Nous voyons souvent cette chaîne, où un objet partagé est créé, puis l'éditeur de liens dynamiques est modifié.

Nous les détectons à l'aide des règles de détection suivantes :

L'enchaînement de ces deux alertes sur un même hôte justifie une enquête.

Techniques de détection du chargement de rootkits dans l'espace noyau

Le chargement manuel d'un LKM nécessite généralement l'utilisation d'utilitaires de ligne de commande intégrés tels que modprobe, insmod et kmod. La détection de l'exécution de ces utilitaires permet de détecter la phase de chargement (lorsqu'elle est effectuée manuellement).

process where event.type == "start" and event.action == "exec" and (
  (process.name == "kmod" and process.args == "insmod" and
   process.args like~ "*.ko*") or
  (process.name == "kmod" and process.args == "modprobe" and
   not process.args in ("-r", "--remove")) or
  (process.name == "insmod" and process.args like~ "*.ko*") or
  (process.name == "modprobe" and not process.args in ("-r", "--remove"))
)

De nombreux rootkits open-source sont publiés sans chargeur et s'appuient sur des utilitaires de chargement de LKM préinstallés. Singularity, par exemple, fournit un script load_and_persistence.sh, qui effectue plusieurs actions, après quoi il appelle insmod "$MODULE_DIR/$MODULE_NAME.ko". Bien que insmod soit appelé dans la commande, insmod est en fait kmod sous le capot, avec insmod comme argument de processus. Un exemple de charge de singularité :

Ce qui peut être facilement détecté grâce aux règles de détection suivantes :

Cette méthode de détection est toutefois loin d'être à l'épreuve des balles, car de nombreux rootkits s'appuient sur un chargeur pour charger le LKM, contournant ainsi l'exécution de ces utilitaires userland.

Par exemple, le chargeur de Reptile invoque directement le syscall init_module avec un blob de noyau décrypté en mémoire :

#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)

int main(void) {
    [...]
    do_decrypt(reptile_blob, len, DECRYPT_KEY);
    module_image = malloc(len);
    memcpy(module_image, reptile_blob, len);
    init_module(module_image, len, "");
    [...]
}

En outre, le module kmatryoshka de Reptile agit comme un chargeur de chaîne dans le noyau qui décrypte et charge un autre LKM caché à l'aide d'un pointeur de fonction direct vers sys_init_module, situé via kallsyms_on_each_symbol(). Le mécanisme de chargement n'est donc pas visible de l'utilisateur.

Pour cette raison, il est essentiel de comprendre ce que ces utilitaires font sous le capot ; ce sont simplement des enveloppes autour des appels système init_module() et finit_module(). Une détection efficace doit donc se concentrer sur le traçage de ces appels de système directement, plutôt que sur l'outil qui les invoque.

Pour garantir la disponibilité des sources de données nécessaires au chargement des LKM, divers outils de sécurité peuvent être utilisés. Auditd ou Auditd Manager sont des choix appropriés. Pour faciliter la collecte des appels de service init_module() et finit_module, la configuration suivante peut être mise en œuvre.

-a always,exit -F arch=b64 -S finit_module -S init_module
-a always,exit -F arch=b32 -S finit_module -S init_module

La combinaison de cette télémétrie brute avec une règle de détection qui émet une alerte lorsque cet événement se produit permet de mettre en place une défense solide.

driver where event.action == "loaded-kernel-module" and
auditd.data.syscall in ("init_module", "finit_module")

Cette stratégie permet de détecter le chargement du module du noyau, quel que soit l'utilitaire utilisé pour l'événement de chargement. Dans l'exemple ci-dessous, nous constatons une détection positive du rootkit Diamorphine.

Cette règle préconstruite est disponible ici :

Des conseils supplémentaires en matière d'ingénierie de détection Linux par le biais d'Auditd sont présentés dans la recherche sur l'ingénierie de détection Linux par le biais d'Auditd.

Modules hors-arbre et non signés

Un autre signe d'un LKM malveillant est la présence du drapeau "taint" du noyau. Lorsque le noyau détecte le chargement d'un module qui ne fait pas partie de l'arborescence officielle du noyau, qui n'a pas de signature valide ou qui utilise une licence non permissive, il marque le noyau comme étant "vicié". Il s'agit d'un mécanisme d'intégrité intégré qui indique que le noyau est dans un état potentiellement non fiable. Vous trouverez ci-dessous un exemple de chargement du module reveng_rtkit:

[ 2853.023215] reveng_rtkit: loading out-of-tree module taints kernel.
[ 2853.023219] reveng_rtkit: module license 'unspecified' taints kernel.
[ 2853.023220] Disabling lock debugging due to kernel taint
[ 2853.023297] reveng_rtkit: module verification failed: signature and/or required key missing - tainting kernel

Le noyau identifie le module comme étant hors-arbre, avec une licence non spécifiée, et manquant de vérification cryptographique. Le noyau est alors considéré comme vicié.

Pour détecter ce comportement, la journalisation du système et du noyau doit être analysée et ingérée. Une fois que les données télémétriques du journal du noyau sont disponibles, une simple recherche de motifs ou une détection basée sur des règles peut permettre de repérer ces événements. Le chargement de modules hors arborescence peut être détecté par le biais de :

event.dataset:"system.syslog" and process.name:"kernel" and
message:"loading out-of-tree module taints kernel."

Une logique de détection similaire peut être mise en œuvre pour détecter le chargement de modules non signés :

event.dataset:"system.syslog" and process.name:"kernel" and
message:"module verification failed: signature and/or required key missing - tainting kernel"

En utilisant la logique de détection ci-dessus, nous avons observé des vrais positifs dans la télémétrie, en essayant de charger Singularity :

Ces règles sont disponibles par défaut dans :

L'entrée du journal indique toujours le nom du module qui a déclenché l'événement, ce qui facilite le triage. Lorsque le LKM n'est pas présent dans le système lors d'un contrôle manuel déclenché par cette alerte, cela peut indiquer que le LKM se cache lui-même.

Signaux de mise à mort

De nombreux rootkits (à code source ouvert) exploitent les signaux kill, en particulier ceux des plages supérieures non attribuées (32+), comme canaux de communication secrets ou comme déclencheurs d'actions malveillantes. Par exemple, un rootkit peut intercepter un signal kill spécifique à numéro élevé (par exemple, kill -64 <pid>). À la réception de ce signal, la charge utile du rootkit peut être configurée pour élever les privilèges, exécuter des commandes, faire basculer les capacités de dissimulation ou créer une porte dérobée.

Pour détecter cela, nous pouvons nous appuyer sur Auditd et créer une règle qui collecte tous les signaux de mise à mort :

-a exit,always -F arch=b64 -S kill -k kill_rule

Les arguments transmis à kill() sont kill(pid, sig). Nous pouvons interroger a1 (le signal) pour signaler tout signal d'exécution supérieur à 32.

process where event.action == "killed-pid" and
auditd.data.syscall == "kill" and auditd.data.a1 in (
"21", "22", "23", "24", "25", "26", "27", "28", "29", "2a",
"2b", "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34",
"35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e",
"3f", "40", "41", "42", "43", "44", "45", "46", "47"
)

L'analyse de l'appel de système kill() à la recherche de valeurs de signaux inhabituelles via Auditd offre une excellente possibilité de détection des rootkits qui utilisent ces signaux, comme le montrent des techniques telles que celles employées par Diamorphine. Les règles préconstruites relatives à la mise à mort sont disponibles à l'adresse suivante :

Défauts de segmentation

Enfin, il est essentiel de reconnaître que les rootkits de l'espace noyau sont intrinsèquement fragiles. Les LKM sont généralement compilés pour une version et une configuration spécifiques du noyau. Un symbole mal résolu ou une écriture mémoire mal alignée peut déclencher une erreur de segmentation. Bien que ces défaillances ne mettent pas immédiatement en évidence la fonctionnalité du rootkit, elles constituent des signaux forts en matière de criminalistique.

Pour détecter cela, la collecte de données syslog brutes doit être activée. À partir de là, l'écriture d'une règle de détection pour signaler les messages d'erreur de segmentation peut aider à identifier un comportement malveillant ou une instabilité du noyau, qui méritent tous deux d'être étudiés :

event.dataset:"system.syslog" and process.name:"kernel" and message:"segfault"

Cette règle de détection est disponible prête à l'emploi en tant que règle modulaire:

La combinaison de la visibilité du chargement de modules au niveau du syscall avec les messages d'erreur du noyau, les messages hors de l'arbre, la détection du signal de mise à mort et les alertes de défaut de segmentation jette les bases d'une stratégie à plusieurs niveaux pour la détection des rootkits basés sur les LKM.

rootkits eBPF

Les rootkits eBPF exploitent la fonctionnalité légitime du sous-système BPF du noyau Linux. Les programmes peuvent être chargés et attachés dynamiquement à l'aide d'utilitaires tels que bpftool ou par le biais de chargeurs personnalisés qui utilisent abusivement les appels de système de bpf().

La détection des rootkits basés sur l'eBPF nécessite une visibilité à la fois sur les appels de système de bpf() et sur l'utilisation des aides sensibles de l'eBPF. Les principaux indicateurs concernés sont les suivants :

  • bpf(BPF_MAP_CREATE, ...)
  • bpf(BPF_MAP_LOOKUP_ELEM, ...)
  • bpf(BPF_MAP_UPDATE_ELEM, ...)
  • bpf(BPF_PROG_LOAD, ...)
  • bpf(BPF_PROG_ATTACH, ...)

En s'appuyant sur Auditd, il est possible de créer une règle d'audit où a0 est utilisé pour spécifier les appels de système BPF spécifiques qui nous intéressent :

-a always,exit -F arch=b64 -S bpf -F a0=0 -k bpf_map_create
-a always,exit -F arch=b64 -S bpf -F a0=1 -k bpf_map_lookup_elem
-a always,exit -F arch=b64 -S bpf -F a0=2 -k bpf_map_update_elem
-a always,exit -F arch=b64 -S bpf -F a0=5 -k bpf_prog_load
-a always,exit -F arch=b64 -S bpf -F a0=8 -k bpf_prog_attach

Ceux-ci doivent être réglés en fonction de l'environnement afin de garantir que les programmes bénins (par exemple, les EDR ou d'autres outils d'observabilité) qui exploitent l'eBPF ne génèrent pas de bruit. Un autre signal important est l'utilisation des fonctions d'aide de l'eBPF.

La fonction d'aide bpf_probe_write_user

L'aide bpf_probe_write_user permet aux programmes eBPF de l'espace noyau d'écrire directement dans la mémoire du pays utilisateur. Bien qu'elle soit destinée au débogage, cette fonction peut être utilisée de manière abusive par des rootkits.

La détection reste un défi, mais les noyaux Linux enregistrent généralement l'utilisation d'aides sensibles, telles que bpf_probe_write_user. La surveillance de ces entrées offre une possibilité de détection, nécessitant la collecte de données syslog brutes et des règles de détection spécifiques, telles que les suivantes :

event.dataset:"system.syslog" and process.name:"kernel" and
message:"bpf_probe_write_user"

Cette règle vous alertera sur toute entrée de journal du noyau indiquant l'utilisation de bpf_probe_write_user. Bien que des outils légitimes puissent l'invoquer occasionnellement, une utilisation inattendue ou fréquente, en particulier dans le cadre d'un processus suspect, justifie une enquête. Le contexte, tel que le point d'attache du programme eBPF et le processus userland concerné, facilite le triage. Cette règle de détection est disponible ici :

Vous trouverez ci-dessous quelques exemples évidents de vrais positifs détectés par cette logique :

La règle se déclenche sur nysm (un conteneur furtif de post-exploitation) et boopkit (une porte dérobée Linux eBPF).

io_uring rootkits

La recherche ARMO (2025) a introduit une nouvelle technique d'évasion de défense qui s'appuie sur io_uring, une conception pour les E/S asynchrones, afin de réduire l'activité observable de l'appel système et de contourner la télémétrie standard. Cette technique est limitée aux versions 5.1 et supérieures du noyau et évite l'utilisation de crochets. Bien que cette méthode ait été récemment découverte par des chercheurs de rootkits, elle est toujours en cours de développement et reste relativement immature dans ses caractéristiques. RingReaper est un exemple d'outil qui exploite cette technique. Les rootkits peuvent effectuer des opérations par lots sur les fichiers, le réseau et d'autres opérations d'E/S via io_uring_enter(). Vous trouverez ci-dessous un exemple de code.

struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, size, offset);
io_uring_submit(&ring);

Ces appels mettent en file d'attente et soumettent une demande de lecture en utilisant io_uring, en contournant les chemins de télémétrie typiques du syscall.

Contrairement au crochetage de la table syscall ou à l'injection basée sur LD_PRELOAD, io_uring n'est pas un mécanisme de livraison de rootkit en soi, mais fournit un moyen plus furtif d'interagir avec le système de fichiers et les périphériques après la compromission. Bien que io_uring ne puisse pas exécuter directement des binaires (en raison de l'absence de capacités similaires à execve), il permet des actions malveillantes telles que la création de fichiers, l'énumération et l'exfiltration de données, tout en minimisant l'observabilité.

La détection des rootkits basés sur io_uringnécessite une visibilité sur les appels de système qui sous-tendent leur fonctionnement, tels que io_uring_setup(), io_uring_enter() et io_uring_register().

Alors que les solutions EDR peuvent avoir du mal à capturer les effets indirects de io_uring, Auditd peut tracer ces appels syscall directement. La règle d'audit suivante permet de capturer les événements pertinents à des fins d'analyse :

-a always,exit -F arch=b64 -k io_uring
-S io_uring_setup -S io_uring_enter -S io_uring_register

Cependant, cela n'expose que l'utilisation de la syscall elle-même, et non le fichier ou l'objet spécifique auquel on accède. La véritable magie "" de io_uring se produit dans les bibliothèques userland (par exemple, liburing), ce qui rend l'analyse des arguments syscall essentielle.

Par exemple, la surveillance de io_uring_enter() avec to_submit > 0 indique qu'une opération d'E/S est en cours de mise en lots, tandis que les appels alternés avec min_complete > 0 signalent la fin de l'interrogation. La corrélation avec les attributs du processus (par exemple, UID=0, chemins inhabituels tels que /dev/shm, /tmp, ou tmpfs- emplacements adossés) améliore l'efficacité de la détection.

Une méthode pratique pour retracer l'activité de io_uring consiste à utiliser l'eBPF avec des outils tels que BCC, en ciblant des points de repère tels que sys_enter_io_uring_enter. Cela permet aux analystes de surveiller le comportement des processus et les descripteurs de fichiers actifs pendant les opérations io_uring:

tracepoint:syscalls:sys_enter_io_uring_enter
{
    printf("\nPID %d (%s) called io_uring_enter with fd=%d, to_submit=%d, min_complete=%d, flags=%d\n",
        pid, comm, args->fd, args->to_submit, args->min_complete, args->flags);

    printf("Manually inspect with: ls -l /proc/%d/fd\n", pid);
}

Pour illustrer cela, plusieurs techniques introduites par RingReaper ont été testées. Le traçage en direct révèle les descripteurs de fichiers utilisés, ce qui permet d'identifier les activités suspectes telles que la lecture de /run/utmp pour détecter les utilisateurs connectés :

L'activité d'écriture dans un fichier, dans cet exemple /root/test:

Ou lister les informations sur les processus via ps en lisant le contenu de comm pour chaque PID actif :

Si la surveillance des appels de service permet de connaître l'utilisation de io_uring, elle ne révèle pas directement la nature des E/S sans corrélation supplémentaire. io_uring est une technique relativement nouvelle et donc encore furtive, mais elle présente aussi plusieurs limites. io_uring ne peuvent pas exécuter directement du code ; cependant, les attaquants peuvent abuser de l'écriture de fichiers (par exemple, les tâches cron, les règles udev) pour obtenir une exécution retardée ou indirecte, comme le démontrent les techniques de persistance utilisées par les familles de logiciels malveillants Reptile et Sedexp.

Techniques de persistance des rootkits

Les rootkits, qu'ils se trouvent dans l'espace utilisateur ou dans l'espace noyau, ont besoin d'une certaine forme de persistance pour rester fonctionnels malgré les redémarrages ou les sessions utilisateur. Les méthodes varient en fonction du type et des privilèges du rootkit, mais il s'agit généralement d'abuser des fichiers de configuration, de la gestion des services ou des scripts d'initialisation du système.

Userland rootkits - persistance des variables d'environnement

Lorsque vous utilisez LD_PRELOAD pour activer un rootkit userland, le comportement n'est pas persistant par défaut. Pour obtenir la persistance, les attaquants peuvent modifier les fichiers d'initialisation du shell (par exemple, ~/.bashrc, ~/.zshrc, ou /etc/profile) pour exporter des variables d'environnement telles que LD_PRELOAD ou LD_LIBRARY_PATH. Ces modifications garantissent que chaque nouvelle session shell hérite automatiquement de l'environnement nécessaire à l'activation du rootkit. Notamment, ces fichiers existent à la fois pour les contextes utilisateur et racine. Par conséquent, même les utilisateurs non privilégiés peuvent introduire une persistance qui détourne le flux d'exécution à leur niveau de privilège.

Pour détecter cela, une règle similaire à celle affichée ci-dessous peut être utilisée :

file where event.action in ("rename", "creation") and file.path like (
  // system-wide configurations
  "/etc/profile", "/etc/profile.d/*", "/etc/bash.bashrc",
  "/etc/bash.bash_logout", "/etc/zsh/*", "/etc/csh.cshrc",
  "/etc/csh.login", "/etc/fish/config.fish", "/etc/ksh.kshrc",

  // root and user configurations
  "/home/*/.profile", "/home/*/.bashrc", "/home/*/.bash_login",
  "/home/*/.bash_logout", "/home/*/.bash_profile", "/root/.profile",
  "/root/.bashrc", "/root/.bash_login", "/root/.bash_logout",
  "/root/.bash_profile", "/root/.bash_aliases", "/home/*/.bash_aliases",
  "/home/*/.zprofile", "/home/*/.zshrc", "/root/.zprofile", "/root/.zshrc",
  "/home/*/.cshrc", "/home/*/.login", "/home/*/.logout", "/root/.cshrc",
  "/root/.login", "/root/.logout", "/home/*/.config/fish/config.fish",
  "/root/.config/fish/config.fish", "/home/*/.kshrc", "/root/.kshrc"
)

En fonction de l'environnement, plusieurs de ces shells peuvent ne pas être utilisés, et une règle de détection plus personnalisée peut être créée, se concentrant uniquement sur bash ou zsh, par exemple. La logique de détection complète utilisant Elastic Defend et l'intégration de la surveillance de l'intégrité des fichiers d'Elastic est disponible ici :

Pour plus d'informations, une analyse complète de cette technique de persistance, y compris plusieurs autres façons de détecter son abus, est présentée dans Linux Detection Engineering - A primer on persistence mechanisms (Ingénierie de détection Linux - Une introduction aux mécanismes de persistance).

Userland rootkits - persistance basée sur la configuration

La modification des fichiers de configuration /etc/ld.so.preload, /etc/ld.so.conf, ou /etc/ld.so.conf.d/ permet aux rootkits de persister globalement à travers les utilisateurs et les sessions (de plus amples informations sur ce vecteur de persistance sont disponibles dans Linux Detection Engineering - A Continuation on Persistence Mechanisms). Une fois écrit, l'éditeur de liens dynamiques continuera à injecter l'objet partagé malveillant à moins que ces configurations ne soient explicitement annulées. Ces méthodes sont persistantes de par leur conception. Les stratégies de détection reflètent celles décrites dans la section précédente et reposent sur la surveillance des événements de création ou de modification de fichiers dans ces chemins.

Rootskits dans l'espace noyau - Persistance du LKM

À l'instar des rootkits du domaine utilisateur, les LKM ne sont pas persistants par défaut. Un attaquant doit explicitement configurer le système pour qu'il recharge le module malveillant au démarrage. Pour ce faire, on exploite généralement les mécanismes légitimes de chargement de modules du noyau :

Fichier des modules : modules

Ce fichier dresse la liste des modules du noyau qui doivent être chargés automatiquement lors du démarrage du système. L'ajout d'un nom de fichier .ko malveillant ici garantit que modprobe le chargera au démarrage. Ce fichier se trouve à l'adresse suivante : /etc/modules.

Répertoire de configuration pour modprobe

Ce répertoire contient les fichiers de configuration de l'utilitaire modprobe. Les attaquants peuvent utiliser l'aliasing pour dissimuler leur rootkit ou le charger automatiquement lorsqu'un événement spécifique se produit dans le noyau (par exemple, lorsqu'un périphérique est sondé). Ces fichiers de configuration modprobe sont situés à /etc/modprobe.d/, /run/modprobe.d/, /usr/local/lib/modprobe.d/, /usr/lib/modprobe.d/, et /lib/modprobe.d/.

Configurez les modules du noyau à charger au démarrage : modules-load.d

Ces fichiers de configuration spécifient les modules à charger au début du processus de démarrage et sont situés à l'adresse /etc/modules-load.d/, /run/modules-load.d/, /usr/local/lib/modules-load.d/ et /usr/lib/modules-load.d/.

Pour détecter toutes les techniques de persistance énumérées ci-dessus, vous pouvez créer une règle de détection similaire à celle présentée ci-dessous :

file where event.action in ("rename", "creation") and file.path like (
  "/etc/modules",
  "/etc/modprobe.d/*",
  "/run/modprobe.d/*",
  "/usr/local/lib/modprobe.d/*",
  "/usr/lib/modprobe.d/*",
  "/lib/modprobe.d/*",
  "/etc/modules-load.d/*",
  "/run/modules-load.d/*",
  "/usr/local/lib/modules-load.d/*",
  "/usr/lib/modules-load.d/*"
)

Cette règle préconstruite qui combine tous les chemins énumérés ci-dessus en une seule règle de détection est disponible ici :

Singularity est un exemple de rootkit qui déploie automatiquement la persistance à l'aide de cette méthode. Dans le cadre de son déploiement, les commandes suivantes sont exécutées :

read -p "Enter the module name (without .ko): " MODULE_NAME
CONF_DIR="/etc/modules-load.d"
mkdir -p "$CONF_DIR"
echo "[*] Setting up persistence..."
echo "$MODULE_NAME" > "$CONF_DIR/$MODULE_NAME.conf"

Par défaut, cela signifie que singularity.conf sera créé comme une nouvelle entrée sous /etc/modules-load.d/. En examinant la télémétrie, nous détectons cette technique simplement en surveillant la création de nouveaux fichiers :

Ces répertoires sont également utilisés pour des LKM bénins et seront donc sujets à des faux positifs. Une autre méthode de persistance consiste à utiliser une technique basée sur un déclencheur ou un calendrier pour charger le module du noyau en exécutant le chargeur.

Persistance basée sur l'Udev - Exemple de reptile

Une méthode de persistance moins courante mais puissante consiste à abuser de udev, le gestionnaire de périphériques de Linux qui gère les événements dynamiques liés aux périphériques. Udev exécute des scripts basés sur des règles lorsque des conditions spécifiques sont remplies. Une analyse complète de cette technique est présentée dans Linux Detection Engineering - A Sequel on Persistence Mechanisms. Le rootkit Reptile démontre cette technique en installant une règle udev malveillante sous /etc/udev/rules.d/:

ACTION=="add", ENV{MAJOR}=="1", ENV{MINOR}=="8", RUN+="/lib/udev/reptile"

Cette règle a probablement servi d'inspiration au logiciel malveillant Sedexp découvert par Levelblue. Voici comment fonctionne cette règle :

  • ACTION=="add": Se déclenche lorsqu'un nouveau dispositif est ajouté au système.
  • ENV{MAJOR}=="1": Correspond aux dispositifs ayant le numéro majeur "1", généralement des dispositifs liés à la mémoire tels que /dev/mem, /dev/null, /dev/zero et /dev/random.
  • ENV{MINOR}=="8": Réduit encore la condition à /dev/random.
  • RUN+="/lib/udev/reptile": Exécute le binaire du chargeur Reptile lorsque le dispositif ci-dessus est détecté.

Cette règle établit la persistance en déclenchant l'exécution d'un binaire de chargement chaque fois que le dispositif /dev/random est chargé. En tant que générateur de nombres aléatoires largement utilisé et essentiel pour de nombreuses applications système et le processus de démarrage, cette méthode est efficace. L'activation n'a lieu qu'en cas d'événements spécifiques sur l'appareil, et l'exécution se fait avec des privilèges de racine par l'intermédiaire de udev daemon. Pour détecter cette technique, vous pouvez créer une règle de détection similaire à celle décrite ci-dessous :

file where event.action in ("rename", "creation") and file.extension == "rules" and file.path like (
  "/lib/udev/*",
  "/etc/udev/rules.d/*",
  "/usr/lib/udev/rules.d/*",
  "/run/udev/rules.d/*",
  "/usr/local/lib/udev/rules.d/*"
)

Nous couvrons la création et la modification de ces fichiers à l'aide des règles préétablies suivantes :

Mécanismes généraux de persistance

Outre les chemins de chargement des modules du noyau, les attaquants peuvent s'appuyer sur des méthodes de persistance Linux plus génériques pour recharger des rootkits en espace utilisateur ou en espace noyau via le chargeur :

Systemd: Créez ou ajoutez un service/timer dans n'importe quel répertoire (par exemple, /etc/systemd/system/) qui supporte le chargeur au démarrage.

file where event.action in ("rename", "creation") and file.path like (
  "/etc/systemd/system/*", "/etc/systemd/user/*",
  "/usr/local/lib/systemd/system/*", "/lib/systemd/system/*",
  "/usr/lib/systemd/system/*", "/usr/lib/systemd/user/*",
  "/home/*.config/systemd/user/*", "/home/*.local/share/systemd/user/*",
  "/root/.config/systemd/user/*", "/root/.local/share/systemd/user/*"
) and file.extension in ("service", "timer")

Scripts d'initialisation: Créez ou ajoutez à un /etc/rc.local /etc/init.d/ /etc/init/script malveillant de contrôle d'exécution (), SysVinit () ou Upstart ().

file where event.action in ("creation", "rename") and
file.path like (
  "/etc/init.d/*", "/etc/init/*", "/etc/rc.local", "/etc/rc.common"
)

Travaux cron: Créez ou ajoutez un travail cron qui permet l'exécution répétée d'un chargeur.

file where event.action in ("rename", "creation") and
file.path like (
  "/etc/cron.allow", "/etc/cron.deny", "/etc/cron.d/*",
  "/etc/cron.hourly/*", "/etc/cron.daily/*", "/etc/cron.weekly/*",
  "/etc/cron.monthly/*", "/etc/crontab", "/var/spool/cron/crontabs/*",
  "/var/spool/anacron/*"
)

Sudoers: Créez ou ajoutez une configuration sudoers malveillante en tant que porte dérobée.

file where event.type in ("creation", "change") and
file.path like "/etc/sudoers*"

Ces méthodes sont largement utilisées, flexibles et souvent plus faciles à détecter en utilisant la télémétrie des processus ou des modifications de fichiers.

Vous trouverez ci-dessous la liste des règles de détection prédéfinies permettant de détecter ces techniques de persistance :

Techniques d'évitement de la défense contre les rootkits

Bien que les rootkits soient, par définition, des outils permettant d'échapper aux défenses, nombre d'entre eux mettent en œuvre des techniques supplémentaires pour ne pas être détectés pendant et après leur déploiement. Ces méthodes sont conçues pour ne pas être visibles dans les journaux, échapper aux agents de détection des points finaux et interférer avec les processus d'investigation courants. La section suivante présente les principales techniques d'évasion employées par les rootkits Linux modernes, classées en fonction de leurs cibles opérationnelles.

Tente de rester furtive lors de son déploiement

Les acteurs de la menace se concentrent généralement sur des tactiques d'exécution furtives du point de vue de la criminalistique. Par exemple, un acteur de la menace peut stocker et exécuter ses charges utiles à partir du répertoire à mémoire partagée /dev/shm, car il s'agit d'un système de fichiers entièrement virtuel, et les charges utiles ne toucheront donc jamais le disque. C'est une bonne chose du point de vue de la criminalistique, mais en tant qu'ingénieurs spécialisés dans la détection comportementale, nous trouvons ce comportement très suspect et peu courant.

À titre d'exemple, bien qu'il ne s'agisse pas d'un véritable acteur de la menace, l'auteur de Singularity suggère la méthode de déploiement suivante :

cd /dev/shm
git clone https://github.com/MatheuZSecurity/Singularity
cd Singularity
sudo bash setup.sh
sudo bash scripts/x.sh

Il y a plusieurs câbles de déclenchement à installer pour détecter ce comportement avec un taux de faux positifs proche de zéro, à commencer par le clonage d'un dépôt GitHub dans le répertoire /dev/shm.

sequence by process.entity_id, host.id with maxspan=10s
  [process where event.type == "start" and event.action == "exec" and (
     (process.name == "git" and process.args == "clone") or
     (
       process.name in ("wget", "curl") and
       process.command_line like~ "*github*"
     )
  )]
  [file where event.type == "creation" and
   file.path like ("/tmp/*", "/var/tmp/*", "/dev/shm/*")]

Le clonage de répertoires dans /tmp et /var/tmp est courant, de sorte que ces répertoires pourraient être supprimés de cette règle dans les environnements où le clonage de référentiels est courant. La même activité sur le site /dev/shm est cependant très rare.

Le script setup.sh, appelé par le chargeur, poursuit la compilation du LKM dans un sous-répertoire /dev/shm/. Les véritables acteurs de la menace ne compilent généralement pas sur l'hôte lui-même, mais il n'est pas rare que cela se produise d'une manière ou d'une autre.

sequence with maxspan=10s
  [process where event.type == "start" and event.action == "exec" and
   process.name like (
     "*gcc*", "*g++*", "c++", "cc", "c99", "c89", "cc1*", "clang*",
     "musl-clang", "tcc", "zig", "ccache", "distcc"
   )] as event0
  [file where event.action == "creation" and file.path like "/dev/shm/*" and
   process.name like (
     "ld", "ld.*", "lld", "ld.lld", "mold", "collect2", "*-linux-gnu-ld*", 
     "*-pc-linux-gnu-ld*"
   ) and
   stringcontains~(event0.process.command_line, file.name)]

Cette logique d'extrémité détecte l'exécution d'un compilateur, suivie de la création par l'éditeur de liens d'un fichier dans /dev/shm (ou un sous-répertoire).

Enfin, puisqu'il a cloné l'ensemble du référentiel dans /dev/shm, et exécuté setup.sh et x.sh, nous observerons l'exécution d'un processus à partir du répertoire de mémoire partagée, ce qui n'est pas courant dans la plupart des environnements :

process where event.type == "start" and event.action == "exec" and
process.executable like ("/dev/shm/*", "/run/shm/*")

Ces règles sont disponibles dans les référentiels detection-rules et protections-artifacts :

Se faire passer pour des processus légitimes

Pour éviter d'être examinés lors de l'énumération des processus ou de la surveillance du système, les rootkits renomment souvent leurs processus et leurs fils d'exécution pour les faire correspondre à des composants bénins du système. Les déguisements les plus courants sont les suivants :

  • kworker, migration, ou rcu_sched (threads du noyau)
  • sshd, systemd, dbus-daemon, ou bash (démons userland)

Ces noms sont choisis pour se fondre dans les résultats d'outils tels que ps, top ou htop, ce qui rend la détection manuelle plus difficile. Reptile et PUMAKIT sont des exemples de rootkits qui exploitent cette technique. Reptile génère des événements réseau inhabituels par le biais de kworker lors de l'initialisation :

network where event.type == "start" and event.action == "connection_attempted" 
and process.name like~ ("kworker*", "kthreadd") and not (
  destination.ip == null or
  destination.ip == "0.0.0.0" or
  cidrmatch(
    destination.ip,
    "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12",
    "192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32",
    "192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24", 
    "192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24",
    "224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24","198.18.0.0/15", 
    "198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1",
    "FE80::/10", "FF00::/8"
  )
)

L'exemple ci-dessous montre la fonctionnalité de knocking de port de Reptile, où le thread du noyau bifurque, change son ID de session à 0 et établit la connexion réseau :

Reptile utilise également le même processus kworker pour créer des fichiers :

file where event.type == "creation" and
process.name like~ ("kworker*", "kthreadd")

PUMAKIT crée des threads dans le noyau pour exécuter des commandes userland via kthreadd, mais des activités similaires ont été observées via un processus kworker dans d'autres rootkits :

process where event.type == "start" and event.action == "exec" and
process.parent.name like~ ("kworker*", "kthreadd") and
process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and
process.args == "-c"

Ces règles kworker et kthreadd peuvent générer des faux positifs en raison des opérations internes du noyau Linux. Ceux-ci peuvent facilement être exclus en fonction de l'environnement, ou des arguments de ligne de commande supplémentaires peuvent être ajoutés à la logique.

Ces règles sont disponibles dans les référentiels detection-rules et protections-artifacts :

En outre, des processus malveillants, tels qu'un dispositif de dépôt initial ou un mécanisme de persistance, peuvent se faire passer pour des threads du noyau et tirer parti d'une fonction shell intégrée pour ce faire. Grâce à la commande exec -a, n'importe quel processus peut être créé avec un nom choisi par l'attaquant. Le masquage des processus du noyau peut être détecté à l'aide de la requête de détection suivante :

process where event.type == "start" and event.action == "exec" and 
process.command_line like "[*]" and process.args_count == 1

Ce comportement est illustré ci-dessous, où plusieurs logiciels malveillants tentent de se faire passer soit pour un travailleur du noyau, soit pour un processus de service web.

Cette technique est également couramment utilisée par les acteurs de la menace qui utilisent la boîte à outils The Hacker's Choice (THC), en particulier lors du déploiement de gsocket.

Les règles relatives au masquage du noyau, et au masquage via exec -a en général, sont disponibles dans le référentiel protections-artifacts :

Une autre technique observée dans la nature, et également dans la pilule du cheval, est l'utilisation de prctl pour piétiner son nom de processus. Pour s'assurer que cette télémétrie est disponible, une règle Auditd personnalisée peut être créée :

-a exit,always -F arch=b64 -S prctl -k prctl_detection

Et accompagné de la logique de détection suivante :

process where host.os.type == "linux" and auditd.data.syscall == "prctl" and
auditd.data.a0 == "f"

Permettra la détection de cette technique. Dans la capture d'écran ci-dessous, nous pouvons voir des exemples de télémétrie de cette technique, où le process.executable est du charabia, et le prctl sera ensuite utilisé pour se faire passer sur le système pour un processus légitime.

Cette règle, y compris ses instructions d'installation, est disponible ici :

Bien qu'il existe de nombreuses façons de se déguiser, voici celles qui sont le plus souvent observées.

Nettoyage des journaux et des audits

De nombreux rootkits comprennent des routines qui effacent les traces de leur installation ou de leur activité dans les journaux. L'une de ces techniques consiste à effacer l'historique du shell de la victime. Ce phénomène peut être détecté de deux manières. Une méthode consiste à détecter la suppression du fichier historique du shell :

file where event.type == "deletion" and file.name in (
  ".bash_history", ".zsh_history", ".sh_history", ".ksh_history",
  ".history", ".csh_history", ".tcsh_history", "fish_history"
)

La deuxième méthode consiste à détecter les exécutions de processus avec des arguments de ligne de commande liés à l'effacement de l'historique de l'interpréteur de commandes :

process where event.type == "start" and event.action == "exec" and (
  (
    process.args in ("rm", "echo") or
    (
      process.args == "ln" and process.args == "-sf" and
      process.args == "/dev/null"
    ) or
    (process.args == "truncate" and process.args == "-s0")
  )
  and process.command_line like~ (
    "*.bash_history*", "*.zsh_history*", "*.sh_history*", "*.ksh_history*",
    "*.history*", "*.csh_history*", "*.tcsh_history*", "*fish_history*"
  )
) or
(process.name == "history" and process.args == "-c") or
(
  process.args == "export" and
  process.args like~ ("HISTFILE=/dev/null", "HISTFILESIZE=0")
) or
(process.args == "unset" and process.args like~ "HISTFILE") or
(process.args == "set" and process.args == "history" and process.args == "+o")

Le fait que les deux règles de détection (processus et fichier) soient actives permet de mettre en place une stratégie de défense en profondeur plus solide.

Lors de leur chargement, les rootkits peuvent altérer le noyau ou générer des messages hors de l'arbre qui peuvent être identifiés lors de l'analyse des journaux syslog et du noyau. Pour effacer leurs traces, les rootkits peuvent supprimer ces fichiers journaux :

file where event.type == "deletion" and file.path in (
  "/var/log/syslog", "/var/log/messages", "/var/log/secure", 
  "/var/log/auth.log", "/var/log/boot.log", "/var/log/kern.log", 
  "/var/log/dmesg"
)

Vous pouvez également effacer le tampon de messages du noyau à l'aide de dmesg:

process where event.type == "start" and event.action == "exec" and
process.name == "dmesg" and process.args in ("-c", "--clear")

Un exemple de rootkit qui nettoie automatiquement le dmesg est le rootkit bds, qui se charge en exécutant /opt/bds_elf/bds_start.sh:

Un autre moyen d'effacer ces journaux est d'utiliser journalctl:

process where event.type == "start" and event.action == "exec" and
process.name == "journalctl" and
process.args like ("--vacuum-time=*", "--vacuum-size=*", "--vacuum-files=*")

Il s'agit d'une technique utilisée par Singularity :

Une autre technique employée par le script de chargement de Singularity est la suppression de tous les fichiers associés au rootkit au cas où il ne pourrait pas se charger, ou une fois qu'il a terminé son processus de chargement. Pour une suppression plus complète, l'auteur a choisi d'utiliser shred plutôt que rm. rm (remove) supprime simplement le pointeur du fichier, ce qui le rend rapide mais permet de récupérer les données. shred écrase les données du fichier plusieurs fois avec des données aléatoires, ce qui garantit qu'il ne pourra pas être récupéré. Cela rend la suppression plus permanente mais, en même temps, plus bruyante du point de vue de la détection des comportements, puisque shred n'est pas couramment utilisé sur la plupart des systèmes Linux.

process where event.type == "start" and event.action == "exec" and
process.name == "shred" and (
// Any short-flag cluster containing at least one of u/z, 
// and containing no extra "-" after the first one
process.args regex~ "-[^-]*[uz][^-]*" or
process.args in ("--remove", "--zero")
) and
not process.parent.name == "logrotate"

La regex ci-dessus rend plus difficiles les tentatives d'échapper à la détection en combinant ou en modifiant les drapeaux. Vous trouverez ci-dessous un exemple de Singularity recherchant tous les fichiers liés à son déploiement et les détruisant :

Ces techniques de suppression de fichiers et de journaux peuvent être détectées au moyen de plusieurs règles de détection prêtes à l'emploi :

Une fois qu'un rootkit a fini d'effacer ses traces, il peut compacter dans le temps les fichiers qu'il a modifiés pour s'assurer qu'aucune trace de modification de fichier n'est laissée derrière lui :

process where event.type == "start" and event.action == "exec" and
process.name == "touch" and
process.args like (
  "-t*", "-d*", "-a*", "-m*", "-r*", "--date=*", "--reference=*", "--time=*"
)

En voici un exemple : un acteur de la menace utilise l'horodatage du fichier /etc/ld.so.conf comme référence temporelle pour les fichiers du disque /dev/shm afin de se fondre dans la masse :

Il s'agit d'une technique pour laquelle nous avons ajouté une couverture via des règles de détection et des artefacts de protection :

Bien qu'il existe toujours d'autres techniques que nous n'avons pas abordées dans le cadre de cette étude, nous sommes convaincus qu'elle permettra d'approfondir la compréhension du paysage des rootkits Linux et de leur technique de détection.

Techniques de prévention des rootkits

La prévention des rootkits Linux nécessite une stratégie de défense à plusieurs niveaux qui combine le renforcement du noyau et du domaine utilisateur, un contrôle d'accès strict et une surveillance continue. Les cadres de contrôle d'accès obligatoires, tels que SELinux et AppArmor, limitent le comportement des processus et les possibilités de persistance dans le domaine de l'utilisateur. Par ailleurs, les techniques de renforcement du noyau, notamment le Lockdown Mode, KASLR, SMEP/SMAP et des outils tels que LKRG, atténuent le risque de compromission au niveau du noyau. Restreindre l'utilisation des modules du noyau en désactivant le chargement dynamique ou en imposant la signature des modules réduit encore les vecteurs courants de déploiement des rootkits.

La visibilité des comportements malveillants est améliorée grâce à l'auditd et à la surveillance de l'intégrité des fichiers pour l'activité des appels système et des fichiers, ainsi que grâce aux solutions EDR qui identifient et préviennent les comportements d'exécution suspects. La sécurité est encore renforcée par la minimisation des privilèges des processus grâce à seccomp-bpf, aux capacités Linux et au LSM de Landlock, ce qui limite l'accès au syscall et les interactions avec le système de fichiers.

Des mises à jour opportunes du noyau et des logiciels, soutenues par des correctifs en direct si nécessaire, comblent les vulnérabilités connues avant qu'elles ne soient exploitées. En outre, les configurations des systèmes de fichiers et des périphériques doivent être renforcées en remontant les systèmes de fichiers sensibles avec des drapeaux restrictifs et en désactivant l'accès aux interfaces de mémoire du noyau, telles que /dev/mem et /proc/kallsyms.

Aucun contrôle ne peut à lui seul empêcher les rootkits. Une défense par couches, combinant le renforcement de la configuration, la détection statique et dynamique et la préparation médico-légale, reste essentielle.

Conclusion

Dans la première partie de cette série, nous avons examiné le fonctionnement interne des rootkits Linux, en explorant leur évolution, leur taxonomie et leurs techniques de manipulation de l'espace utilisateur et de l'espace noyau. Dans cette deuxième partie, nous avons traduit ces connaissances en stratégies de détection pratiques, en nous concentrant sur les signaux comportementaux et la télémétrie d'exécution qui révèlent l'activité des rootkits.

Alors que les logiciels malveillants Windows continuent de dominer l'attention des fournisseurs de sécurité commerciale et des communautés de recherche sur les menaces, Linux reste comparativement peu étudié, bien qu'il alimente la majorité des infrastructures en nuage, des environnements de calcul à haute performance et des services internet dans le monde.

Notre analyse montre que les rootkits Linux évoluent. L'adoption croissante de technologies telles que eBPF, io_uring, et les charges de travail Linux conteneurisées introduisent de nouvelles surfaces d'attaque qui ne sont pas encore bien comprises ou largement protégées.

Nous encourageons la communauté de la sécurité à :

  • Investissez dans l'ingénierie de détection axée sur Linux, sous des angles à la fois statiques et dynamiques.
  • Partager ouvertement les résultats des recherches, les preuves de concept et les stratégies de détection afin d'accélérer la connaissance collective parmi les défenseurs.
  • Collaborer avec les fournisseurs, les universités et l'industrie pour que la défense contre les rootkits sous Linux atteigne le même niveau de maturité que sous Windows.

Ce n'est qu'en améliorant collectivement la visibilité, la détection et les capacités de réaction que les défenseurs pourront garder une longueur d'avance sur ce paysage de menaces furtif et en évolution rapide.

Partager cet article