Playing Games With Kernel Memory

                           ==Phrack Inc.==

              Volume 0x0b, Issue 0x3f, Phile #0x07 of 0x14

|=-------=[ Playing Games With Kernel Memory ... FreeBSD Style ]=--------=|
|=-----------------------------------------------------------------------=|
|=-----------------=[ Joseph Kong <jkong01@gmail.com> ]=-----------------=|
|=--------------------------=[ July 8, 2005 ]=---------------------------=|
|=---------------=[ traduit par Aryliin pour arsouyes.org]=--------------=|

--[ Sommaire


  1.0 - Introduction

  2.0 - Trouver les appels systèmes

  3.0 - Comprendre les appels d'instructions et les injections de bytecode

  4.0 - Allouer de la mémoire noyau

  5.0 - Mettre tout ça ensemble

  6.0 - Remarques de conclusion

  7.0 - Références




--[ 1.0 - Introduction

    L'interface mémoire du noyau ou l'interface du kvm ont été pour la 
première fois introduits par SunOS. Malgré le fait que ça fasse un moment
qu'il soit à disposition, beaucoup de personnes les considèrent très obscures.
Cet article montre l'utilisation de base de la librairie d'accès des données
noyau (libkvm), et va explorer plusieurs moyens d'utiliser libkvm (/dev/kmem)
afin d'alterer le comportement du système tournant sous FreeBSD.

	Vous devez avoir un niveau moyen pour pirater le noyau FreeBSD (i.e vous
devez savoir comment utiliser ddb), aussi bien qu'une compréhension décente des 
langages C et Assembleur x86 (syntaxe AT&T) afin de comprendre le contenu de 
cet article.

	Cette article a été écrit en perspective d'un système FreeBSD 5.4 stable.

Note: malgré le fait que les techniques décrites dans cet article aient
deja été explorées dans d'autres articles (c.f. Références), elles concernent
toujours Linuw ou Windows. Je n'ai personnelement connaissance que
d'un seul article qui touche les mêmes informations que celles citées ici. Il
s'appelle "Fun and Games with FreeBSD Kernel Modules", de Stephanie Wehner,
et qui  explique quelques petites choses qu'on peut faire avec libkvm. En
considérant le fait qu'on peut toujours faire mieux, et que la documentation
a propos de libkvm est insuffisante ( les pages de manuel et le code source),
j'ai décidé d'écrire cet article.


--[ 2.0 - Trouver les appels système.

Note: Cette section est très basique, si vous connaissez bien les fonctions de
libkvm, lisez le prochain paragraphe et passez à la prochaine partie.

	Stephanie Wehner a écrit un programme appelé checkcal, qui vérifie si
sysent[CALL] a été falsifié, et si c'est le cas, le repasse dans sa version 
d'origine. Pour nous aider avec le deboguage dans les prochaines parties de cet
article, nous allons utiliser la fonctionnalité de checkcall pour trouver les 
appels systèmes. Ce qui suit est une version épurée de checkcall, avec 
seulement la fonction trouvant les appels systèmes. C'est également un 
excellent exemple pour apprendre les bases de libkvm. Une explication ligne par
ligne des fonctions de libkvm est fournit à la fin du code source.

find_syscall.c:

/*
 * Prend deux arguments : le nom de l'appel système et son numéro correspondant 
 * et renvoi l'endroit en mémoire où est situé cet appel.
 *
 * Si vous entrez un nom d'appel système avec un numéro incorrect,
 * la sortie va être n'importe quoi. Trop flemmard pour implementer une 
 * vérification
 *
 * Basé sur checkcall.c v 1.1.1.1 de Stephanie Wehner
 *
 * find_syscall.c,v 1.0 2005/05/20
 */


#include <stdio.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/sysent.h>
#include <sys/syscall.h>

int main(int argc, char *argv[]) {

	char errbuf[_POSIX2_LINE_MAX];
	kvm_t *kd;
	u_int32_t addr;
	int callnum;
	struct sysent call;
	struct nlist nl[] = { { NULL }, { NULL }, { NULL }, };


	/* Vérification du nombre d'arguments */

	if(argc != 3) {
		printf("Usage:n%s <name of system call> <syscall number>"
		       " nn", argv[0]);

		printf("See /usr/src/sys/sys/syscall.h for syscall numbers"
		       " n");

		exit(0);
	}


	/* Trouve l'appel système */

	nl[0].n_name = "sysent";
	nl[1].n_name = argv[1];
	callnum = atoi(argv[2]);

	printf("Finding syscall %d: %snn", callnum, argv[1]);

	/* Initialise l'acces a la mémoire virtuelle du noyau */

	kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
	if(kd == NULL) {
		fprintf(stderr, "ERROR: %sn", errbuf);
		exit(-1);
	}

	/* Trouve les adresses  */

	if(kvm_nlist(kd, nl) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	if(!nl[0].n_value) {
		fprintf(stderr, "ERROR: %s not found (fubar?)n"
			, nl[0].n_name);
		exit(-1);
	}
	else {
		printf("%s is 0x%x at 0x%xn", nl[0].n_name, nl[0].n_type
		       , nl[0].n_value);
	}

	if(!nl[1].n_value) {
		fprintf(stderr, "ERROR: %s not foundn", nl[1].n_name);
		exit(-1);
	}


	/* Calcul l'adresse */

	addr = nl[0].n_value + callnum * sizeof(struct sysent);


	/* Affiche la position */

	if(kvm_read(kd, addr, &call, sizeof(struct sysent)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}
	else {
		printf("sysent[%d] is at 0x%x and will execute function"
		       " located at 0x%xn", callnum, addr, call.sy_call);
	}

	if(kvm_close(kd) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	exit(0);
}

	Il y a cinq fonctions de libkvm incluses dans la programme précédent;
Ce sont :

	kvm_openfiles
	kvm_nlist
	kvm_geterr
	kvm_read
	kvm_close

kvm_openfiles:

	kvm_openfiles fait essentiellement l'initialisation de l'acces à la 
mémoire virtuelle du noyau, et retourne un descripteur pouvant être utilisé
dans les appels ultérieurs à la librairie kvm. Dans find_syscall, la syntaxe
est la suivante:

	kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);

    kd est utilisé pour stocker le descripteur retourné, et s'il est égal à 
NULL après l'appel kd, une erreur se produit.

    Les trois premiers arguments correspondent repectivement à const char* 
execfile, const char *corefile, et const char * swapfiles. Cependant, pour
nous ils ne sont pas nécessaires, et par conséquent sont mis à NULL. Le
quatrième argument indique si nous voulons faire un accès lecture/écriture. Le
cinquième indique dans quel buffer placer les messages d'erreur, plus tard.

kvm_nlist:

	La page de manuel affirme que kvm_nlist récupère les entrées de la table
des symboles indiquées par la liste de noms en argument (struct nlist). Les
membres de la structure nlist qui nous interessent sont les suivants:

	char *n_name;		    /* nom du symbol (en mémoire) */
	unsigned long n_value;	/* adresse du symbole   */

	Avant l'appel à kvm_nlist dans find_syscall, un tableau de structure nlist
est mis en place de la manière suivante :

	struct nlist nl[] = { { NULL }, { NULL }, { NULL }, };
	nl[0].n_name = "sysent";
	nl[1].n_name = argv[1];

    La syntaxe de l'appel à kvm_nlist est la suivante :

	kvm_nlist(kd, nl)
	
	Ici, on ajoute au membre n_value de chaque élément du tableau n1 l'adresse
mémoire de départ correspondant à la valeur de n_name. En d'autres termes, nous
connaissons maintenant la localisation mémoire de sysent et de l'argument donné par
l'utilisateur à l'appel système (argv[1]). n1 est initialisé avec trois
éléments parce que kvm_list demande en second argument un tableau terminé
par NULL de structure nlist.

kvm_geterr:

	Comme il est dit dans la page de manuel, cette fonction renvoie une chaine
de caractères décrivant l'erreur la plus récente. Si vous regardez dans le code
source, vous verrez que kvm_geterr est appelé après chaque fonction de libkvm,
à part kvm_openfiles. kvm_openfiles utilise sa propre forme unique de gestion
d'erreurs, car kvm_geterr demande en argument un descripteur, qui ne peut pas
exister si kvm_openfiles n'a pas encore été appelé. Voici un exemple 
d'utilisation de kvm_geterr:

	fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));

kvm_read:

	Cette fonction est utilisée pour lire la mémoire virtuelle du noyau. Dans
find_syscall, la syntaxe est la suivante :

	kvm_read(kd, addr, &call, sizeof(struct sysent))

    Le premier argument est le descripteur. Le second est l'adresse à laquelle
on commence à lire. Le troisième argument est la localisation de l'espace 
utilisateur pour ranger cette donnée. Le quatrième est le nombre d'octets à 
lire.

kvm_close:

	Cette fonction casse la connection entre le pointeur et la mémoire 
virtuelle du noyeau établie avec kvm_openfiles. Voici l'appel de cette fonction
dans syscall:

	kvm_close(kd)

Voici l'algorithme utilisé dans find_syscall.c:
	1. Vérifier que l'utilisateur a fourni un nom d'appel système et 
	   un numéro. (Pas de vérification d'erreurs, juste vérification du 
	   nombre d'arguments)
	2. Mise en place du tableau de structures nlist approprié.
	3. Initialisation de l'acces à la mémoire virtuelle du noyau(kvm_openfiles)
	4. Trouver l'adresse de sysent et l'appel système donné par l'utilisateur.
	   (kvm_nlist)
	5. Calculer la localisation de l'appel système dans sysent.
	6. Copier la structure sysent de l'appel système à partir de l'espace noyau
	   dans l'espace utilisateur. (kvm_read)
	7. Afficher la localisation de l'appel système dans la structure sysent et 
	   la localisation de la fonction executée.
	8. Fermer le descripteur (kvm_close)

	Afin de vérifier que la sortie de find_syscall est exacte, nous pouvons 
utiliser ddb de la manière suivante :

Note: La sortie suivante a été modifiée pour garder le format de 75 caractères
par ligne.

	[---------------------------------------------------------]

ghost@slavetwo:~#ls
find_syscall.c
ghost@slavetwo:~#gcc -o find_syscall find_syscall.c -lkvm
ghost@slavetwo:~#ls
find_syscall   find_syscall.c
ghost@slavetwo:~#sudo ./find_syscall
Password:
Usage:
./find_syscall <name of system call> <syscall number>

See /usr/src/sys/sys/syscall.h for syscall numbers
ghost@slavetwo:~#sudo ./find_syscall mkdir 136
Finding syscall 136: mkdir

sysent is 0x4 at 0xc06dc840
sysent[136] is at 0xc06dcc80 and will execute function located at
0xc0541900
ghost@slavetwo:~#KDB: enter: manual escape to debugger
[thread pid 12 tid 100004 ]
Stopped at      kdb_enter+0x32: leave
db> examine/i 0xc0541900
mkdir:  pushl   %ebp
db>
mkdir+0x1:      movl    %esp,%ebp
db> c

ghost@slavetwo:~#

	[---------------------------------------------------------]


--[ 3.0 - Comprendre les appels d'instructions et les injections de bytecode

	En assembleur x86 un Call est une instruction de tranfert de controle, 
utilisé pour appeler une procédure. Il y a deux types de d'instructions Call
Near et Far, pour les besoins de cet articles, nous ne devons comprendre que
l'instruction Near Call. Le code suivant montre les détails d'un appel 
à une instruction Near Call (dans la syntaxe Intel):

	0200	BB1295	MOV BX,9512
	0203	E8FA00	CALL 0300
	0206	B82F14	MOV AX,142F

	Dans le morceau de code ci-dessus, lorsque que IP (le pointeur
d'instruction) arrive en 0203, il va sauter en 0300. La représentation 
hexadécimale de CALL est E8, cependant FA00 n'est pas 0300. 0x300 - 0x206 = 
0xFA. Dans un Near Call l'adresse du pointeur d'instructions IP après le Call
est sauvegardé dans la pile, donc la procédure appelée sait à quel endroit 
elle doit retourner une fois finie. Celà explique pourquoi l'opérande pour Call
dans cet exemple est 0xFA00 et non 0x300. C'est un point important et on va 
s'en reservir plus tard.

	Une des choses les plus amusantes que l'on peut faire avec les fonctions
de libkvm est de patcher la mémoire virtuelle du noyau. Comme toujours, nous
allons partir d'un exemple simple ... Hello World ! Le code suivant est un 
kdl qui ajoute un appel système fonctionnant comme un programme Hello World !

hello.c:

/*
 * Affiche 10 fois "FreeBSD Rox!"
 *
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>

/*
 * La fonction implémentant l'appel système
 */

static int
hello (struct thread *td, void *arg)
{
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	printf ("FreeBSD Rox!n");
	return 0;
}

/*
 * `sysent' pour le nouvel appel système
 */

static struct sysent hello_sysent = {
	0,			/* sy_narg */
	hello			/* sy_call */
};

/*
 * L'offset dans le sysent ou l'appel système est situé
 * [210 : n° d'appel système libre sous *BSD]
 */

static int offset = 210; 

/*
 * Fonction appelée au chargement/déchargement
 */

static int
load (struct module *module, int cmd, void *arg)
{
	int error = 0;

	switch (cmd) {
	case MOD_LOAD :
		printf ("syscall loaded at %dn", offset);
		break;
	case MOD_UNLOAD :
		printf ("syscall unloaded from %dn", offset);
		break;
	default :
		error = EOPNOTSUPP;
		break;
	}
	return error;
}

SYSCALL_MODULE(hello, &offset, &hello_sysent, load, NULL);

Voici le programme en espace utilisateur pour le kld précédent:

interface.c:

#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/module.h>

int main(int argc, char **argv) {

        return syscall(210);
}

	Si nous compilons le kld précédent en utilisant un Makefile standard, 
puis le chargeon et lançons le programme en mode-utilisateur, nous récupérons
une sortie assez ennuyeuse. Pour rendre cet appel système moins agaçant, nous
pouvons utiliser le programme suivant. Comme précedement, il y aura une
explication de toutes les nouvelles fonctions et nouveaux concepts à la fin
du code.

test_call.c:

/*
 * Essai de compréhension d'un instruction call:
 * L'opérande pour L'instruction call est la différence entre la fonction appelée
 * et l'adresse de l'instruction suivante à l'appel.
 *
 * Testé sur l'appel système hello. Normalement, affiche 10 fois "FreeBSD Rox!",
 * apres avoir été patché, ne l'affiche plus qu'une fois.
 *
 * test_call.c,v 2.1 2005/06/15
 */


#include <stdio.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <limits.h>
#include <sys/types.h>

/*
 * Offset de la chaîne de caractères à afficher
 * Démarre au début de l'appel système hello
 */

#define OFFSET_1	0xed

/*
 * Offset de l'instruction suivante à l'instruction call
 */

#define OFFSET_2	0x12

/*
 * code de remplacement
 */

unsigned char code[] =
	"x55"				/* push  %ebp			*/
	"x89xe5"			/* mov   %esp,%ebp		*/
	"x83xecx04"			/* sub   {CONTENT}x4,%esp		*/
	"xc7x04x24x00x00x00x00"	/* movl  {CONTENT},(%esp)		*/
	"xe8x00x00x00x00"		/* call  printf			*/
	"xc9"				/* leave			*/
	"x31xc0"			/* xor   %eax,%eax		*/
	"xc3"				/* ret				*/
	"x8dxb4x26x00x00x00x00"	/* lea   0x0(%esi),%esi		*/
	"x8dxbcx27x00x00x00x00";	/* lea   0x0(%edi),%edi		*/


int main(int argc, char *argv[]) {

	char errbuf[_POSIX2_LINE_MAX];
	kvm_t *kd;
	u_int32_t offset_1;
	u_int32_t offset_2;
	struct nlist nl[] = { { NULL }, { NULL }, { NULL }, };


	/* Initialise l'accès à la mémoire virtuelle du noyau */

        kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
        if(kd == NULL) {
                fprintf(stderr, "ERROR: %sn", errbuf);
                exit(-1);
        }


	/* Trouve l'adresse de hello et printf */

	nl[0].n_name = "hello";
	nl[1].n_name = "printf";

	if(kvm_nlist(kd, nl) < 0) {
                fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
                exit(-1);
        }

        if(!nl[0].n_value) {
                fprintf(stderr, "ERROR: Symbol %s not foundn"
			, nl[0].n_name);
                exit(-1);
        }

	if(!nl[1].n_value) {
                fprintf(stderr, "ERROR: Symbol %s not foundn"
			, nl[1].n_name);
                exit(-1);
        }


	/* Calcule l'offset correcte */

	offset_1 = nl[0].n_value + OFFSET_1;
	offset_2 = nl[0].n_value + OFFSET_2;


	/* Met l'adresse correcte dans code */

	*(unsigned long *)&code[9] = offset_1;
	*(unsigned long *)&code[14] = nl[1].n_value - offset_2;


	/* Patche hello */

	if(kvm_write(kd, nl[0].n_value, code, sizeof(code)) < 0) {
        	fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
        	exit(-1);
	}

	printf("Luke, I am your father!n");


	/* ferme kd */

	if(kvm_close(kd) < 0) {
                fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
                exit(-1);
        }

        exit(0);
}

	La seule fonction de libkvm incluse dans le programme précédent et n'ayant
pas été expliquée est kvm_write.

kvm_write:

	Cette fonction est utilisée pour écrire dans la mémoire virtuelle du noyau.
Dans test_call, la syntaxe est la suivante :

	kvm_write(kd, nl[0].n_value, code, sizeof(code))

    Le premier argument est le descripteur. Le second est l'adresse à laquelle
commencer à écrire. Le troisième est la localisation en espace utilisateur à
partir de laquelle on lit. Le quatrième argument est le nombre d'octets à lire.
Le code de remplacement (bytecode) dans test_call à été généré à l'aide 
d'objdump.

	[---------------------------------------------------------]

ghost@slavetwo:~#objdump -DR hello.ko | less

hello.ko:     file format elf32-i386-freebsd

Disassembly of section .hash:

00000094 <.hash>:
  94:   11 00                   adc    %eax,(%eax)
  96:   00 00                   add    %al,(%eax)

OUTPUT SNIPPED

Disassembly of section .text:

00000500 <hello>:
 500:   55                      push   %ebp
 501:   89 e5                   mov    %esp,%ebp
 503:   83 ec 04                sub    {CONTENT}x4,%esp
 506:   c7 04 24 ed 05 00 00    movl   {CONTENT}x5ed,(%esp)
                        509: R_386_RELATIVE     *ABS*
 50d:   e8 fc ff ff ff          call   50e <hello+0xe>
                        50e: R_386_PC32 printf
 512:   c7 04 24 ed 05 00 00    movl   {CONTENT}x5ed,(%esp)
                        515: R_386_RELATIVE     *ABS*
 519:   e8 fc ff ff ff          call   51a <hello+0x1a>
                        51a: R_386_PC32 printf
 51e:   c7 04 24 ed 05 00 00    movl   {CONTENT}x5ed,(%esp)
                        521: R_386_RELATIVE     *ABS*
 525:   e8 fc ff ff ff          call   526 <hello+0x26>
                        526: R_386_PC32 printf

OUTPUT SNIPPED

 57e:   c9                      leave
 57f:   31 c0                   xor    %eax,%eax
 581:   c3                      ret
 582:   8d b4 26 00 00 00 00    lea    0x0(%esi),%esi
 589:   8d bc 27 00 00 00 00    lea    0x0(%edi),%edi

	[---------------------------------------------------------]

Note: votre sortie peut varier de celle-ci, tout dépend de votre compilateur
et de vos flags.

	En comparant la sortie de la section text avec le bytecode de test_call,
on peut voir qu'elle sont essentiellement les même, à cela près qu'il y a neuf
appels de plus à printf. Un point important à remarquer est ce que objdump
signale comme quelque chose de relatif. Dans ce cas, deux elements le sont ; 
movl {CONTENT}x5ed,(%esp) (mise en place de la chaîne à afficher) et l'appel a printf.
Ce qui nous donne ...

Dans test_call il y a deux #define, qui sont:

	#define OFFSET_1        0xed
	#define OFFSET_2        0x12

	Le premier est l'adresse de la chaine de caractère à afficher part rapport
au début de l'appel système hello (le nombre est issu de la sortie d'objdump).
Alors que le second représente l'offset de l'instruction suivant l'appel à
printf dans le bytecode. Plus loin dans test_call il y a quatre autres 
instructions :

	/* Calcule l'offset correct */

        offset_1 = nl[0].n_value + OFFSET_1;
        offset_2 = nl[0].n_value + OFFSET_2;

	/* Donne la bonne adresse a code */

        *(unsigned long *)&code[9] = offset_1;
        *(unsigned long *)&code[14] = nl[1].n_value - offset_2;

	D'apres le commentaire, ce que font ces instructions peut paraître évident.
code[9] est la section dans le bytecode ou est stockée l'adresse de la chaîne à
afficher. code[14] est l'opérande pour l'instruction call; l'adresse de printf 
- l'adresse de la prochaine instruction.

Voici la sortie console avant et après avoir executé test_call:

	[---------------------------------------------------------]

ghost@slavetwo:~#ls
Makefile    hello.c     interface.c test_call.c
ghost@slavetwo:~#make
Warning: Object directory not changed from original /usr/home/ghost
@ -> /usr/src/sys
machine -> /usr/src/sys/i386/include

OUTPUT SNIPPED

J% objcopy % hello.kld
ld -Bshareable  -d -warn-common -o hello.ko hello.kld
objcopy --strip-debug hello.ko
ghost@slavetwo:~#sudo kldload ./hello.ko
Password:
syscall loaded at 210
ghost@slavetwo:~#gcc -o interface interface.c
ghost@slavetwo:~#./interface
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
FreeBSD Rox!
ghost@slavetwo:~#gcc -o test_call test_call.c -lkvm
ghost@slavetwo:~#sudo ./test_call
Luke, I am your father!
ghost@slavetwo:~#./interface
FreeBSD Rox!
ghost@slavetwo:~#

	[---------------------------------------------------------]


--[ 4.0 - Allouer de la mémoire noyau

	Être capable seulement de patcher la mémoire noyau a ses limites dès
que vous n'avez plus assez de place pour jouer. Être capable d'allouer de 
la mémoire noyau permet d'alléger ce problème. Voilà un kld qui fait 
justement celà:

kmalloc.c:

/*
 * Module permettant à un utilisateur non privilégié d'allouer de 
 * la mémoire noyau
 *
 * kmalloc.c,v 2.0 2005/06/01
 * Date Modified 2005/06/14
 */


#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>


/*
 * Arguments pour kmalloc
 */

struct kma_struct {
	unsigned long size;
	unsigned long *addr;
};

struct kmalloc_args { struct kma_struct *kma; };

/*
 * La fonction pour implémenter kmalloc.
 */

static int
kmalloc (struct thread *td, struct kmalloc_args *uap) {

	int error = 1;
	struct kma_struct kts;

	if(uap->kma) {
		MALLOC(kts.addr, unsigned long*, uap->kma->size
		       , M_TEMP, M_NOWAIT);
		error = copyout(&kts, uap->kma, sizeof(kts));
	}

	return (error);
}

/*
 * `sysent' pour kmalloc
 */

static struct sysent kmalloc_sysent = {
	1,			/* sy_narg */
	kmalloc			/* sy_call */
};

/*
 * L'offset dans sysent où l'appel système est alloué.
 */

static int offset = 210;

/*
 * La fonction appelée au chargement/déchargement.
 */

static int
load (struct module *module, int cmd, void *arg)
{
	int error = 0;

	switch (cmd) {
	case MOD_LOAD :
		uprintf ("syscall loaded at %dn", offset);
		break;
	case MOD_UNLOAD :
		uprintf ("syscall unloaded from %dn", offset);
		break;
	default :
		error = EOPNOTSUPP;
		break;
	}
	return error;
}

SYSCALL_MODULE(kmalloc, &offset, &kmalloc_sysent, load, NULL);

Voici le programme dans l'espace utilisateur pour le kld précédent:

interface.c:

/*
 * Programme utilisateur pour interagir avec le module kmalloc
 */

#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/module.h>

struct kma_struct {

	unsigned long size;
	unsigned long *addr;
};

int main(int argc, char **argv) {

	struct kma_struct kma;

	if(argc != 2) {
		printf("Usage:n%s <size>n", argv[0]);
		exit(0);
	}

	kma.size = (unsigned long)atoi(argv[1]);

	return syscall(210, &kma);
}

    En utilisant les techniques/fonctions décritent dans les deux sections 
précédentes et l'algorithme suivant crée par Silvio Cesare, on peut allouer
de la mémoire noyau sans utiliser un kld.

L'algorithme kmalloc de Silvio Cesare pour l'espace utilisateur.:

	1. Récupère l'adresse de quelques appels système
	2. Ecrit la fonction qui va allouer la mémoire noyau
	3. Sauvegarde sizeof(notre_fonction) octets de ces quelques appels système
	4. Ecrase quelques appels système avec notre_fonction
	5. Appel l'appel système nouvellement écrasé
	6. Rétabli l'appel système

test_kmalloc.c:

/*
 * Alloue de la mémoire noyau à partir de l'espace utilisateur
 *
 * L'algorithme pour allouer de la mémoire noyau est le suivant:
 *
 * 1. Récupère l'adresse de mkdir
 * 2. Ecrase mkdir avec la fonction appelantman 9 malloc()
 * 3. Appelle mkdir grâce à int {CONTENT}x80
 *    Ceci provoque l'execution par le noyau de notre "nouvel" appel système
 *    qui va appeler man 9 malloc() et s'évanouir dans l'espace noyau
 *    nouvellement alloué
 * 4. Restitution de l'appel système mkdir
 *
 * test_kmalloc.c,v 2.0 2005/06/24
 */


#include <stdio.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/module.h>


/*
 * Offset de l'instruction suivant l'instruction call
 * Démarre au début de la fonction kmalloc
 */

#define OFFSET_1	0x3a
#define OFFSET_2	0x56


/*
 * code de la fonction code
 */

unsigned char code[] =
	"x55"				/* push   %ebp			*/
	"xbax01x00x00x00"		/* mov    {CONTENT}x1,%edx		*/
	"x89xe5"			/* mov    %esp,%ebp		*/
	"x53"				/* push   %ebx			*/
	"x83xecx14"			/* sub    {CONTENT}x14,%esp		*/
	"x8bx5dx0c"			/* mov    0xc(%ebp),%ebx	*/
	"x8bx03"			/* mov    (%ebx),%eax		*/
	"x85xc0"			/* test   %eax,%eax		*/
	"x75x0b"			/* jne    20 <kmalloc+0x20>	*/
	"x83xc4x14"			/* add    {CONTENT}x14,%esp		*/
	"x89xd0"			/* mov    %edx,%eax		*/
	"x5b"				/* pop    %ebx			*/
	"xc9"				/* leave			*/
	"xc3"				/* ret				*/
	"x8dx76x00"			/* lea    0x0(%esi),%esi	*/
	"xc7x44x24x08x01x00x00"	/* movl   {CONTENT}x1,0x8(%esp)	*/
	"x00"
	"xc7x44x24x04x00x00x00"	/* movl   {CONTENT}x0,0x4(%esp)	*/
	"x00"
	"x8bx00"			/* mov    (%eax),%eax		*/
	"x89x04x24"			/* mov    %eax,(%esp)		*/
	"xe8xfcxffxffxff"		/* call   36 <kmalloc+0x36>	*/
	"x89x45xf8"			/* mov    %eax,0xfffffff8(%ebp)	*/
	"xc7x44x24x08x08x00x00"	/* movl   {CONTENT}x8,0x8(%esp)	*/
	"x00"
	"x8bx03"			/* mov    (%ebx),%eax		*/
	"x89x44x24x04"		/* mov    %eax,0x4(%esp)	*/
	"x8dx45xf4"			/* lea    0xfffffff4(%ebp),%eax	*/
	"x89x04x24"			/* mov    %eax,(%esp)		*/
	"xe8xfcxffxffxff"		/* call   52 <kmalloc+0x52>	*/
	"x83xc4x14"			/* add    {CONTENT}x14,%esp		*/
	"x89xc2"			/* mov    %eax,%edx		*/
	"x5b"				/* pop    %ebx			*/
	"xc9"				/* leave			*/
	"x89xd0"			/* mov    %edx,%eax		*/
	"xc3";				/* ret				*/


/*
 * structure utilisée pour sauvegarder l'adresse noyau
 */

struct kma_struct {

        unsigned long size;
        unsigned long *addr;
};


int main(int argc, char **argv) {

	int i = 0;
	char errbuf[_POSIX2_LINE_MAX];
	kvm_t *kd;
	u_int32_t offset_1;
	u_int32_t offset_2;
	struct nlist nl[] =
			  {{ NULL },{ NULL },{ NULL },{ NULL },{ NULL },};
	unsigned char origcode[sizeof(code)];
	struct kma_struct kma;


	if(argc != 2) {
                printf("Usage:n%s <size>n", argv[0]);
                exit(0);
        }


	/* Initialise l'accès a la mémoire virtuelle du noyau */

	kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
	if(kd == NULL) {
		fprintf(stderr, "ERROR: %sn", errbuf);
		exit(-1);
	}


	/* Trouve l'adresse de mkdir, M_TEMP, malloc et copyout */

	nl[0].n_name = "mkdir";
	nl[1].n_name = "M_TEMP";
	nl[2].n_name = "malloc";
	nl[3].n_name = "copyout";

	if(kvm_nlist(kd, nl) < 0) {
                fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));


                exit(-1);
        }

	for(i = 0; i < 4; i++) {
		if(!nl[i].n_value) {
                	fprintf(stderr, "ERROR: Symbol %s not foundn"
				, nl[i].n_name);
                	exit(-1);
        	}
	}


	/* Calcule l'offset courant */

	offset_1 = nl[0].n_value + OFFSET_1;
	offset_2 = nl[0].n_value + OFFSET_2;


	/* Mise a jour de code avec l'adresse correcte */

	*(unsigned long *)&code[44] = nl[1].n_value;
	*(unsigned long *)&code[54] = nl[2].n_value - offset_1;
	*(unsigned long *)&code[82] = nl[3].n_value - offset_2;


	/* sauvegarde de l'appel système mkdir */

	if(kvm_read(kd, nl[0].n_value, origcode, sizeof(code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}


	/* Patche mkdir */

	if(kvm_write(kd, nl[0].n_value, code, sizeof(code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}


	/* Allocation de mémoire noyau */

	kma.size = (unsigned long)atoi(argv[1]);
	syscall(136, &kma);
	printf("Address of kernel memory: 0x%xn", kma.addr);


	/* Rétablissement de mkdir */

	if(kvm_write(kd, nl[0].n_value, origcode, sizeof(code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}


	/* Fermeture de kd */

	if(kvm_close(kd) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	exit(0);
}

En utilisant ddb, on peut vérifier les résultats du programme précédent:

	[---------------------------------------------------------]

ghost@slavetwo:~#ls
test_kmalloc.c
ghost@slavetwo:~#gcc -o test_kmalloc test_kmalloc.c -lkvm
ghost@slavetwo:~#sudo ./test_kmalloc
Usage:
./test_kmalloc <size>
ghost@slavetwo:~#sudo ./test_kmalloc 10
Address of kernel memory: 0xc2580870
ghost@slavetwo:~#KDB: enter: manual escape to debugger
[thread pid 12 tid 100004 ]
Stopped at      kdb_enter+0x32: leave
db> examine/x 0xc2580870
0xc2580870:     70707070
db>
0xc2580874:     70707070
db>
0xc2580878:     dead7070
db> c

ghost@slavetwo:~#

	[---------------------------------------------------------]


--[ 5.0 - Mettre tout ça ensemble

	Savoir comment patcher et allouer de la mémoire noyau offre beaucoup de 
liberté. Nous allons voir dans cette dernière section comment détourner un 
appel en utilisant les techniques décrites dans les sections précédentes. 
Typiquement, les détournements d'appels sous FreeBSD sont fait en changeant
le sysent et en le faisant pointer sur une autre fonction, mais ce n'est pas 
ce que nous allons faire. À la place, nous allons utiliser l'algorithme suivant
(avec quelques petites modifications, montrées plus tard):

	1. Copie de l'appel système que nous voulons détourner
	2. Allocation de mémoire noyau (en utilisant la technique décrite dans la
	   section précédente)
	3. Mise en place d'une nouvelle routine dans l'espace nouvellement alloué
	4. Ecrasement des 7 premiers octets de l'appel système avec une instruction
	   pour sauter dans la nouvelle routine
	5. Execution de la nouvelle routine, et des premiers x octets de l'appel
	   système (cette partie sera plus claire toute à l'heure)
	6. Retour à l'appel système + offset où offset = x
	
	En volant l'idée à pragmatic de THC, nous allons détourner mkdir pour 
afficher un message de debug. Voici le kld utiliser en conjonction avec 
objdump pour extraire le bytecode requis pour le détournement d'appel.

hacked_mkdir.c:

/*
 * Détournement de mkdir
 *
 * Affiche un simple message de debug
 */


#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/linker.h>
#include <sys/sysproto.h>
#include <sys/syscall.h>


/* L'appel système piraté */

static int
hacked_mkdir (struct proc *p, struct mkdir_args *uap) {

	uprintf ("MKDIR SYSCALL : %sn", uap->path);
	return 0;
}


/* Le sysent de l'appel système piraté */

static struct sysent
hacked_mkdir_sysent = {
	1,		/* sy_narg */
	hacked_mkdir	/* sy_call */
};


/* L'offset dans sysent ou l'appel système est alloué */

static int offset = NO_SYSCALL;


/* Fonction appelée au chargement/déchargement */

static int
load (struct module *module, int cmd, void *arg) {

	int error = 0;

	switch (cmd) {
	case MOD_LOAD :
		uprintf ("syscall loaded at %dn", offset);
		break;
	case MOD_UNLOAD :
		uprintf ("syscall unloaded from %dn", offset);
		break;
	default :
		error = EINVAL;
		break;
	}
	return error;
}

SYSCALL_MODULE(hacked_mkdir, &offset, &hacked_mkdir_sysent, load, NULL);

	Voici un exemple de programme qui détourne mkdir pour afficher un simple
message de debug. Comme toujours, l'explication des nouveaux concepts se fait à
la fin du code source.

test_hook.c:

/*
 * Intercepte l'appel système mkdir, affiche un message d'erreur avant
 * d'executer mkdir.
 *
 * L'algorithme est le suivant:
 * 1. Copie l'appel système jusqu'à xe8 non inclus
 * 2. Alloue de la mémoire noyau
 * 3. Place la nouvelle routine dans l'espace nouvellement alloué
 * 4. Ecrase les 7 premiers octets de mkdir avec une instruction sautant dans
 *    la nouvelle routine.
 * 5. Execute la nouvelle routine et les x premiers octets de l'appel système 
 *    mkdir où x est égal au nombre d'octets copié dans le premier point
 * 6. Retour à l'appel système mkdir + offset, où offset est la localisation 
 *    xe8
 *
 * test_hook.c,v 3.0 2005/07/02
 */

#include <stdio.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/module.h>



/*
 * Offset de l'instruction suivant l'appel à call
 * Démarre au début de la fonction kmalloc
 */

#define KM_OFFSET_1	0x3a
#define KM_OFFSET_2	0x56

/*
 * Code de kmalloc
 */

unsigned char km_code[] =
	"x55"				/* push   %ebp			*/
	"xbax01x00x00x00"		/* mov    {CONTENT}x1,%edx		*/
	"x89xe5"			/* mov    %esp,%ebp		*/
	"x53"				/* push   %ebx			*/
	"x83xecx14"			/* sub    {CONTENT}x14,%esp		*/
	"x8bx5dx0c"			/* mov    0xc(%ebp),%ebx	*/
	"x8bx03"			/* mov    (%ebx),%eax		*/
	"x85xc0"			/* test   %eax,%eax		*/
	"x75x0b"			/* jne    20 <kmalloc+0x20>	*/
	"x83xc4x14"			/* add    {CONTENT}x14,%esp		*/
	"x89xd0"			/* mov    %edx,%eax		*/
	"x5b"				/* pop    %ebx			*/
	"xc9"				/* leave			*/
	"xc3"				/* ret				*/
	"x8dx76x00"			/* lea    0x0(%esi),%esi	*/
	"xc7x44x24x08x01x00x00"	/* movl   {CONTENT}x1,0x8(%esp)	*/
	"x00"
	"xc7x44x24x04x00x00x00"	/* movl   {CONTENT}x0,0x4(%esp)	*/
	"x00"
	"x8bx00"			/* mov    (%eax),%eax		*/
	"x89x04x24"			/* mov    %eax,(%esp)		*/
	"xe8xfcxffxffxff"		/* call   36 <kmalloc+0x36>	*/
	"x89x45xf8"			/* mov    %eax,0xfffffff8(%ebp)	*/
	"xc7x44x24x08x08x00x00"	/* movl   {CONTENT}x8,0x8(%esp)	*/
	"x00"
	"x8bx03"			/* mov    (%ebx),%eax		*/
	"x89x44x24x04"		/* mov    %eax,0x4(%esp)	*/
	"x8dx45xf4"			/* lea    0xfffffff4(%ebp),%eax	*/
	"x89x04x24"			/* mov    %eax,(%esp)		*/
	"xe8xfcxffxffxff"		/* call   52 <kmalloc+0x52>	*/
	"x83xc4x14"			/* add    {CONTENT}x14,%esp		*/
	"x89xc2"			/* mov    %eax,%edx		*/
	"x5b"				/* pop    %ebx			*/
	"xc9"				/* leave			*/
	"x89xd0"			/* mov    %edx,%eax		*/
	"xc3";				/* ret				*/



/*
 * Offset de la fonction suivant l'appel à call
 * Démarre au début de la fonction hacked_mkdir
 */

#define HA_OFFSET_1	0x2f

/*
 * fonction hacked_mkdir
 */

unsigned char ha_code[] =
	"x4d"				/* M				*/
	"x4b"				/* K				*/
	"x44"				/* D				*/
	"x49"				/* I				*/
	"x52"				/* R				*/
	"x20"				/* sp				*/
	"x53"				/* S				*/
	"x59"				/* Y				*/
	"x53"				/* S				*/
	"x43"				/* C				*/
	"x41"				/* A				*/
	"x4c"				/* L				*/
	"x4c"				/* L				*/
	"x20"				/* sp				*/
	"x3a"				/* :				*/
	"x20"				/* sp				*/
	"x25"				/* %				*/
	"x73"				/* s				*/
	"x0a"				/* nl				*/
	"x00"				/* null				*/
	"x55"				/* push   %ebp			*/
	"x89xe5"			/* mov    %esp,%ebp		*/
	"x83xecx08"			/* sub    {CONTENT}x8,%esp		*/
	"x8bx45x0c"			/* mov    0xc(%ebp),%eax	*/
	"x8bx00"			/* mov    (%eax),%eax		*/
	"xc7x04x24x0dx00x00x00"	/* movl   {CONTENT}xd,(%esp)		*/
	"x89x44x24x04"		/* mov    %eax,0x4(%esp)	*/
	"xe8xfcxffxffxff"		/* call   17 <hacked_mkdir+0x17>*/
	"x31xc0"			/* xor    %eax,%eax		*/
	"x83xc4x08"			/* add    {CONTENT}x8,%esp		*/
	"x5d";				/* pop    %ebp			*/



/*
 * code de saut
 */

unsigned char jp_code[] =
	"xb8x00x00x00x00"		/* movl   {CONTENT},%eax		*/
	"xffxe0";			/* jmp    *%eax			*/



/*
 * structure utilisée pour stocker l'adresse noyau
 */

struct kma_struct {

        unsigned long size;
        unsigned long *addr;
};



int main(int argc, char **argv) {

	int i = 0;
	char errbuf[_POSIX2_LINE_MAX];
	kvm_t *kd;
	u_int32_t km_offset_1;
	u_int32_t km_offset_2;
	u_int32_t ha_offset_1;
	struct nlist nl[] =
	{ { NULL },{ NULL },{ NULL },{ NULL },{ NULL },{ NULL},{ NULL }, };
	unsigned long diff;
	int position;
	unsigned char orig_code[sizeof(km_code)];
	struct kma_struct kma;



	/* Initialisation de l'accès à la mémoire virtuelle noyau */

	kd = kvm_openfiles(NULL, NULL, NULL, O_RDWR, errbuf);
	if(kd == NULL) {
		fprintf(stderr, "ERROR: %sn", errbuf);
		exit(-1);
	}

	/* Trouver l'adresse de mkdir, M_TEMP, malloc, copyout,
	   uprintf, et kern_rmdir */

	nl[0].n_name = "mkdir";
	nl[1].n_name = "M_TEMP";
	nl[2].n_name = "malloc";
	nl[3].n_name = "copyout";
	nl[4].n_name = "uprintf";
	nl[5].n_name = "kern_rmdir";

	if(kvm_nlist(kd, nl) < 0) {
                fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
                exit(-1);
        }

	for(i = 0; i <= 5; i++) {
		if(!nl[i].n_value) {
                	fprintf(stderr, "ERROR: Symbol %s not foundn"
				, nl[i].n_name);
                	exit(-1);
        	}
	}



	/* Déterminer la taille de l'appel système mkdir */

	diff = nl[5].n_value - nl[0].n_value;
	unsigned char mk_code[diff];

	/* Stocker une copie de mkdir */

	if(kvm_read(kd, nl[0].n_value, mk_code, diff) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	/* Trouver la position de 0xe8 */

	for(i = 0; i < (int)diff; i++) {
		if(mk_code[i] == 0xe8) {
			position = i;
		}
	}



	/* Calculer l'offset correct pour kmalloc */

	km_offset_1 = nl[0].n_value + KM_OFFSET_1;
	km_offset_2 = nl[0].n_value + KM_OFFSET_2;

	/* mise à jour de km_code avec les adresse correctes */

	*(unsigned long *)&km_code[44] = nl[1].n_value;
	*(unsigned long *)&km_code[54] = nl[2].n_value - km_offset_1;
	*(unsigned long *)&km_code[82] = nl[3].n_value - km_offset_2;

	/* sauvegarde de l'appel système mkdir */

	if(kvm_read(kd, nl[0].n_value, orig_code, sizeof(km_code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	/* Replacement de mkdir avec kmalloc */

	if(kvm_write(kd, nl[0].n_value, km_code, sizeof(km_code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	/* Allocation de mémoire noyau */

	kma.size = (unsigned long)sizeof(ha_code) + (unsigned long)position
		   + (unsigned long)sizeof(jp_code);
	syscall(136, &kma);

	/* restitution de mkdir */

	if(kvm_write(kd, nl[0].n_value, orig_code, sizeof(km_code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}



	/* Calcule de l'offset correct pour hacked_mkdir */

	ha_offset_1 = (unsigned long)kma.addr + HA_OFFSET_1;

	/* Mise à jour de ha_code avec les adresses correctes */

	*(unsigned long *)&ha_code[34] = (unsigned long)kma.addr;
	*(unsigned long *)&ha_code[43] = nl[4].n_value - ha_offset_1;

	/* Mise en place de la routine hacked_mkdir dans la mémoire noyau */

	if(kvm_write(kd, (unsigned long)kma.addr, ha_code, sizeof(ha_code))
	   < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	/* Mise en place de mk_code dans la mémoire noyau */

	if(kvm_write(kd, (unsigned long)kma.addr +
	  (unsigned long)sizeof(ha_code) - 1, mk_code, position) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	/* Mise à jour de jp_code pour avoir l'adresse correcte */

	*(unsigned long *)&jp_code[1] = nl[0].n_value +
					(unsigned long)position;

	/* Mise en place du code de saut dans la mémoire noyau */

	if(kvm_write(kd, (unsigned long)kma.addr +
	                 (unsigned long)sizeof(ha_code) - 1 +
			 (unsigned long)position
			 , jp_code, sizeof(jp_code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}



	/* Mise à jour de jp_code avec l'adresse correcte */

	*(unsigned long *)&jp_code[1] = (unsigned long)kma.addr + 0x14;

	if(kvm_write(kd, nl[0].n_value, jp_code, sizeof(jp_code)) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	printf("I love the PowerGlove. It's so bad!n");



	/* fermeture de kd */

	if(kvm_close(kd) < 0) {
		fprintf(stderr, "ERROR: %sn", kvm_geterr(kd));
		exit(-1);
	}

	exit(0);
}

Ce commentaire affirme que l'agorithme de ce programme est le suivant :

1. Copie de l'appel système mkdir jusqu'a xe8 non inclus.
2. Allocation de mémoire noyau.
3. Mise en place de la routine dans l'espace nouvellement alloué.
4. Ecrasement des 7 premiers octets de l'appel système avec une instruction
pour sauter dans la nouvelle routine
5. Execution de la nouvelle routine, et des premiers x octets de l'appel
système (cette partie sera plus claire toute à l'heure)
6. Retour à l'appel système + offset où offset = x

	La raison pour laquelle on recopie mkdir jusqu'a xe8 non inclus est que
suivant les versions de FreeBSD, le dessassemblage de mkdir ne donne pas la
même chose. Par conséquent, on ne peut pas déterminer une localisation 
statique à laquelle retourner. Par contre, dans toutes les versions de FreeBSD
mkdir appelle la fonction kern_mkdir, c'est pour cette raison que nous avons
choisi de faire notre retour d'appel à cet endroit. Cette sortie teminale 
illustre ce que je viens de dire.

	[---------------------------------------------------------]

ghost@slavezero:~#nm /boot/kernel/kernel | grep mkdir
c047c560 T devfs_vmkdir
c0620e40 t handle_written_mkdir
c0556ca0 T kern_mkdir
c0557030 T mkdir
c071d57c B mkdirlisthd
c048a3e0 t msdosfs_mkdir
c05e2ed0 t nfs4_mkdir
c05d8710 t nfs_mkdir
c05f9140 T nfsrv_mkdir
c06b4856 r nfsv3err_mkdir
c063a670 t ufs_mkdir
c0702f40 D vop_mkdir_desc
c0702f64 d vop_mkdir_vp_offsets
ghost@slavezero:~#nm /boot/kernel/kernel | grep kern_rmdir
c0557060 T kern_rmdir
ghost@slavezero:~#objdump -d --start-address=0xc0557030
--stop-address=0xc0557060 /boot/kernel/kernel | less

/boot/kernel/kernel:     file format elf32-i386-freebsd

Disassembly of section .text:

c0557030 <mkdir>:
c0557030:       55                      push   %ebp
c0557031:       31 c9                   xor    %ecx,%ecx
c0557033:       89 e5                   mov    %esp,%ebp
c0557035:       83 ec 10                sub    {CONTENT}x10,%esp
c0557038:       8b 55 0c                mov    0xc(%ebp),%edx
c055703b:       8b 42 04                mov    0x4(%edx),%eax
c055703e:       89 4c 24 08             mov    %ecx,0x8(%esp)
c0557042:       89 44 24 0c             mov    %eax,0xc(%esp)
c0557046:       8b 02                   mov    (%edx),%eax
c0557048:       89 44 24 04             mov    %eax,0x4(%esp)
c055704c:       8b 45 08                mov    0x8(%ebp),%eax
c055704f:       89 04 24                mov    %eax,(%esp)
c0557052:       e8 49 fc ff ff          call   c0556ca0 <kern_mkdir>
c0557057:       c9                      leave  
c0557058:       c3                      ret    
c0557059:       8d b4 26 00 00 00 00    lea    0x0(%esi),%esi

ghost@slavezero:~#

	[---------------------------------------------------------]


	[---------------------------------------------------------]

ghost@slavetwo:~#nm /boot/kernel/kernel | grep mkdir
c046f680 T devfs_vmkdir
c0608fd0 t handle_written_mkdir
c05415d0 T kern_mkdir
c0541900 T mkdir
c074a9bc B mkdirlisthd
c047d270 t msdosfs_mkdir
c05c7160 t nfs4_mkdir
c05bcfd0 t nfs_mkdir
c05db750 T nfsrv_mkdir
c06a2676 r nfsv3err_mkdir
c06216a0 t ufs_mkdir
c06fef40 D vop_mkdir_desc
c06fef64 d vop_mkdir_vp_offsets
ghost@slavetwo:~#nm /boot/kernel/kernel | grep kern_rmdir
c0541930 T kern_rmdir
ghost@slavetwo:~#objdump -dR --start-address=0xc0541900
--stop-address=0xc0541930 /boot/kernel/kernel | less

/boot/kernel/kernel:     file format elf32-i386-freebsd

Disassembly of section .text:

c0541900 <mkdir>:
c0541900:       55                      push   %ebp
c0541901:       89 e5                   mov    %esp,%ebp
c0541903:       83 ec 10                sub    {CONTENT}x10,%esp
c0541906:       8b 55 0c                mov    0xc(%ebp),%edx
c0541909:       8b 42 04                mov    0x4(%edx),%eax
c054190c:       c7 44 24 08 00 00 00    movl   {CONTENT}x0,0x8(%esp)
c0541913:       00
c0541914:       89 44 24 0c             mov    %eax,0xc(%esp)
c0541918:       8b 02                   mov    (%edx),%eax
c054191a:       89 44 24 04             mov    %eax,0x4(%esp)
c054191e:       8b 45 08                mov    0x8(%ebp),%eax
c0541921:       89 04 24                mov    %eax,(%esp)
c0541924:       e8 a7 fc ff ff          call   c05415d0 <kern_mkdir>
c0541929:       c9                      leave
c054192a:       c3                      ret
c054192b:       90                      nop
c054192c:       8d 74 26 00             lea    0x0(%esi),%esi

ghost@slavetwo:~#

	[---------------------------------------------------------]

	La sortie ci-dessus à été générée à partir de deux FreeBSD 5.4. On peut 
clairement remarquer que le dump de desassemblage de mkdir est différent
sur les deux.

	Dans test_hook, l'adresse de kern_rmdir est cherchée juste apres, parce
que rmdir viens après mkdir, donc son adresse est la frontière finale de 
mkdir.

Le bytecode du detournement d'appel est le suivant:

unsigned char ha_code[] =
        "x4d"                          /* M                            */
        "x4b"                          /* K                            */
        "x44"                          /* D                            */
        "x49"                          /* I                            */
        "x52"                          /* R                            */
        "x20"                          /* sp                           */
        "x53"                          /* S                            */
        "x59"                          /* Y                            */
        "x53"                          /* S                            */
        "x43"                          /* C                            */
        "x41"                          /* A                            */
        "x4c"                          /* L                            */
        "x4c"                          /* L                            */
        "x20"                          /* sp                           */
        "x3a"                          /* :                            */
        "x20"                          /* sp                           */
        "x25"                          /* %                            */
        "x73"                          /* s                            */
        "x0a"                          /* nl                           */
        "x00"                          /* null                         */
        "x55"                          /* push   %ebp                  */
        "x89xe5"                       /* mov    %esp,%ebp             */
        "x83xecx08"                    /* sub    {CONTENT}x8,%esp      */
        "x8bx45x0c"                    /* mov    0xc(%ebp),%eax        */
        "x8bx00"                       /* mov    (%eax),%eax           */
        "xc7x04x24x0dx00x00x00"        /* movl   {CONTENT}xd,(%esp)    */
        "x89x44x24x04"                 /* mov    %eax,0x4(%esp)        */
        "xe8xfcxffxffxff"              /* call   17 <hacked_mkdir+0x17>*/
        "x31xc0"                       /* xor    %eax,%eax             */
        "x83xc4x08"                    /* add    {CONTENT}x8,%esp      */
        "x5d";                         /* pop    %ebp                  */

	La chaîne à afficher se trouve dans les 20 premiers octets, donc lorsque
nous sautons dans cette fonction nous devons démarrer à un offset de 0x14, 
somme illustré par cette ligne de code:

	*(unsigned long *)&jp_code[1] = (unsigned long)kma.addr + 0x14;

	Les trois dernières insrtuctions du bytecode de hacked_mkdir mettent à
zéro le registre eax, restaure la pile et le registre ebp. C'est fait pour 
que mkdir puisse s'executer comme si jamais rien ne s'etait passé.

	Il faut se rappeler une chose à propos des tableaux de caractères en C.
Ils sont toujours terminés par des null. Par exemple si nous déclarons cette
variable,

	unsigned char example[] = "x41";

sizeof(exemple) va nous retourner 2. C'est la raison pour laquelle dans 
test_hook, nous soustrayons 1 à sizeof(ha_code), sinon, nous pourrions écrire
à un mauvais endroit.

Voici la sortie avant et après avoir executé test_hook:

	[---------------------------------------------------------]

ghost@slavetwo:~#ls
test_hook.c
ghost@slavetwo:~#gcc -o test_hook test_hook.c -lkvm
ghost@slavetwo:~#mkdir before
ghost@slavetwo:~#ls -F
before/      test_hook*   test_hook.c
ghost@slavetwo:~#sudo ./test_hook
Password:
I love the PowerGlove. It's so bad!
ghost@slavetwo:~#mkdir after
MKDIR SYSCALL : after
ghost@slavetwo:~#ls -F
after/       before/      test_hook*   test_hook.c
ghost@slavetwo:~#

	[---------------------------------------------------------]

On peut aussi utiliser find_syscall et ddb pour vérifier les résultats de 
test_hook.

--[ 6.0 - Remarques de conclusion

	Être capable de patcher et d'allouer de la mémoire noyau donne un certain
pouvoir sur un système. Tous les exemples de cet article sont triviaux car mon
intention était de montrer la pratique plutôt que la théorie. Les autres auteurs 
ont de meilleurs idées que moi sur l'utilisation que l'on peut en faire
(c.f. Reférences).

	J'aimerai m'excuser si certaines de mes explications ne sont pas claires, 
en esperant que le fait de regarder le code source et les sorties les rendent
plus claires.

	Finalement, j'aimerai remercier Silvio Cesare, pragmatic et Stephanie
Wehner, pour leurs idées/inspirations.

--[ 7.0 - Réferences

  [ Internet ]

  [1] Silvio Cesare, "Runtime Kernel Kmem Patching"
      http://reactor-core.org/runtime-kernel-patching.html

  [2] devik & sd, "Linux on-th-fly kernel patching without LKM"
      http://www.phrack.org/show.php?p=58&a=7

  [3] pragmatic, "Attacking FreeBSD with Kernel Modules"
      http://www.thc.org/papers/bsdkern.html

  [4] Andrew Reiter, "Dynamic Kernel Linker (KLD) Facility Programming
                      Tutorial"
      http://ezine.daemonnews.org/200010/blueprints.html

  [5] Stephanie Wehner, "Fun and Games with FreeBSD Kernel Modules"
      http://www.r4k.net/mod/fbsdfun.html

  [ Livres ]

  [6] Muhammad Ali Mazidi & Janice Gillispie Mazidi, "The 80x86 IBM PC And
      Compatible Computers: Assembly Language, Design, And Interfacing"
      (Prentice Hall)


|=[ EOF ]=---------------------------------------------------------------=|