==Phrack Inc.== Volume 0x0b, Issue 0x3f, Phile #0x07 of 0x14 |=-------=[ Playing Games With Kernel Memory ... FreeBSD Style ]=--------=| |=-----------------------------------------------------------------------=| |=-----------------=[ Joseph Kong ]=-----------------=| |=--------------------------=[ 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 #include #include #include #include #include #include #include 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 " " 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 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 #include #include #include #include #include #include /* * 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 #include #include #include 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 #include #include #include #include #include /* * 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 : 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 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 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 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 #include #include #include #include #include #include #include /* * 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 #include #include #include 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 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 #include #include #include #include #include #include #include /* * 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 */ "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 */ "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 */ "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 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 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 #include #include #include #include #include #include #include #include #include /* 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 #include #include #include #include #include #include #include /* * 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 */ "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 */ "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 */ "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 */ "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 : 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 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 : 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 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 */ "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 ]=---------------------------------------------------------------=|