Introduction aux rootkits par la pratique

Introduction aux rootkits par la pratique

Un rootkit est un programme malveillant qui, une fois installé sur votre machine, non seulement donne un accès administrateur distant à une tièrce personne située ailleurs sur la planète, mais en plus fait tout ce qu’il peut pour que vous ne soyez pas en mesure de le détecter sur votre machine, et encore moins de l’éradiquer.

Les fonctionnalités déployées par les rootkits peuvent se décliner à l’infini : espionnage de votre webcam, récupération des touches tapées au clavier (et donc des mots de passe), exfiltration de vos fichiers (et donc de vos secrets industriels), etc. Après c’est juste une histoire d’imagination.

Comment chope-t’on un rootkit ?

De manière classique : souvent en cliquant sur un lien sur lequel il ne fallait pas cliquer dans un email (au final c’est personne d’autre que nous-mêmes qui avons installé le rootkit, mais sans le savoir).

Détecter qu’un rootkit est installé sur sa machine reste faisable avec des outils comme unhide ou rkhunter sous Linux (ou RootkitRevealer sous Windows), mais s’en débarrasser définitivement est souvent compliqué.

La programmation d’un rootkit

Qui programme des rootkits ? Ce qu’on peut dire avec certitude, c’est que le point commun entre tous les programmeurs de rootkits est qu’ils sont doués. Après les motivations vont du simple défi technique (et l’on va voir ci-dessous que le défi est réel) à la malveillance cupide.

Pour illustrer le défi technique que représente la programmation d’un rootkit, nous allons prendre un cas d’école. Oublions ici toutes les fonctions évoluées telles que l’administration à distance, l’espionnage de webcam, l’extraction de fichiers, etc. pour se concentrer sur la programmation d’une seule fonction : la furtivité.

Pour cela, le mieux est d’étudier un petit programme “preuve de concept” rootkit, qui s’appelle Superhide. Le cahier des charges de ce rootkit simple est le suivant : une fois en mémoire sous forme de module noyau, il va empêcher la commande ls de montrer tous les fichiers dont le nom commence par “jordans_secrets.” (Jordan étant le nom du programmeur partageant le source de son programme), et surtout il va empêcher que son nom de module apparaisse dans la commande lsmod.

Ce programme met donc en œuvre deux types de techniques de furtivité de base dont il est passionnant d’étudier le code source.

La modification de ls

L’utilisation de la commande strace montre que l’appel système utilisé par la commande ls est getdents. Il va donc falloir détourner cet appel système pour le remplacer par un nouveau getdents qui retire les entrées dont le nom commence par “jordans_secrets.”

La première difficulté est de retrouver où exactement dans la mémoire du noyau se trouve la table des appels systèmes. En effet, celle-ci n’est pas au même endroit en mémoire d’un système Linux à l’autre, en fonction de la manière dont le noyau a été compilé. Superhide utilise l’astuce suivante : il recherche l’adresse de la table des appels systèmes dans le fichier /boot/System.map-$(uname -r) [/boot/System.map-4.15.0-48-generic par exemple sur ma machine]. Dans ce fichier, qui est un fichier texte en fait, l’adresse de la table des appels systèmes est donnée par l’entrée sys_call_table

Le script build_and_install.h fourni avec le rootkit récupère ainsi l’adresse de l’entrée sys_call_table et génère le fichier sysgen.h à partir de cette information manquante. sysgen.h est ensuite utilisé lors de la compilation du module.

Le module lui-même contient les fonctions classiques utilisées lors du chargement via insmod (static int __init lkm_init_module(void) {}) et lors du déchargement via rmmod (static void __exit lkm_cleanup_module(void) {})

Pour détourner l’appel système getdents, la fonction lkm_init_module va changer l’adresse de la fonction de traitement dans la table des appels systèmes via l’instruction :

sys_call_table[GETDENTS_SYSCALL_NUM] = sys_getdents_new;

Évidemment, l’adresse de l’ancienne fonction de traitement est sauvegardée au préalable afin de pouvoir la restaurer sur rmmod.

La zone mémoire correspondante à sys_call_table est protégée en écriture. Ce qui ne signifie pas grand chose lorsqu’on est dans le code du noyau comme ici. Il faut tout de même connaître l’astuce qui est employée ici pour accéder temporairement en écriture : la mise à 0 du 16ème bit du registre cr0 du microprocesseur ! Le noyau offre les fonctions write_cr0 et read_cr0 pour manipuler ce registre. Dans le code du rootkit on retrouve donc :

write_cr0(read_cr0() & (~WRITE_PROTECT_FLAG)); // pour faire sauter temporairement la protection en écriture

write_cr0(read_cr0() | WRITE_PROTECT_FLAG); // pour la remettre une fois l’appel système détourné

Le nouvel appel système getdents appelle d’abord l’ancien puis fait le ménage dans la liste des entrées ls ramenées avant de rendre la main.

Cacher le module dans lsmod

La commande lsmod affiche la liste des modules chargés. Si le rootkit ne fait rien, la commande lsmod trahira sa présence puisqu’on y verra un module nommé superhide.

Cacher la présence de ce module passe donc par /proc. Ce répertoire contient un certain nombre de fichiers qui n’en sont pas en réalité. Ce ne sont que des points d’entrée vers des fonctions particulières situées dans le noyau. C’est le cas du fichier qui nous intéresse : /proc/modules, qui pointe vers une fonction de listage des modules chargés qui est située dans le noyau. La commande lsmod ne fait que lister le contenu de /proc/modules.

Comme pour le détournement précédent de l’appel système getdents, le rootkit va maintenant détourner la fonction particulière du noyau reliée au fichier /proc/modules.

L’ancienne fonction va être sauvegardée par l’instruction :

proc_modules_read_orig = proc_modules_operations->read;

et la nouvelle fonction mise en place par l’instruction :

proc_modules_operations->read = proc_modules_read_new;

La nouvelle fonction proc_modules_read_new va, comme précédemment, appeler d’abord l’ancienne fonction, puis effacer dans le tableau retour les entrées correspondant au module superhide. Imparable.

Lorsqu’on retire le module superhide de la mémoire via la commande rmmod, il ne fait que remettre en place via 2 instructions l’appel système getdents et la fonction particulière reliée à /proc/modules.

Voilà, j’espère que l’étude de cette preuve de concept vous aura intéressé autant que moi. Bien sûr on est loin d’un rootkit complet opérationnel, mais quelle meilleure méthode que l’étude de briques individuelles pour comprendre comment chacune d’elle fonctionne ?

Le code source disponible sur Github est fourni en début d’article, il suffit de cliquer sur le lien associé au mot Superhide.

Et sous Windows ?

Sous Windows le détournement des appels systèmes du noyau a été rendu virtuellement impossible depuis d’introduction d’une technologie appelée Kernel Patch Protection (ou PatchGuard pour les intimes) dans les versions 64 bits de Windows. Il faut dire que jusque là tout se passait comme si les concepteurs du noyau avaient fait tout ce qu’ils pouvaient pour faciliter la vie des programmeurs de virus et autres rootkits.

Avec PatchGuard la modification du noyau est virtuellement impossible, mais dans les faits elle l’est. Ce qui est cocasse, c’est que les concepteurs de rootkits réussissent à contourner cette protection (non sans mal, mais ça marche), alors que les éditeurs d’antivirus n’y arrivent pas. Enfin, ils y arrivent mais au final le soft antivirus ne marche plus si bien. Bref, les antivirus qui avaient pris l’habitude de patcher le noyau 32 bits pour opérer, on dû revoir leur efficacité à la baisse à cause de PatchGuard. Le point à retenir étant tout de même que la technologie Kernel Patch Protection de Windows n’empêche pas les rootkits.