NDT :Dans le jargon, on utilise souvent hook et hooking, ... tel quels, sans les traduire. C'est un mot qui porte beaucoup de sens et pas facile à traduire.

D'habitude, on traduit par détournement/redirection, qui expriment clairement l'idée d'un hook de fonction. Mais ces terme perdent toute la saveur et les idées qu'il y a derrière "hooking".

Ici, on traduira par "crochet" et "crochetage", qui gardent cette idée de détourner/contourner quelque chose, avec toute la saveur que le crochetage implique.

Ces termes étants expliqués, bonne lecture.

Version txt, cet article a été traduit très gentillement par Thiébaud Weksteen et relu par mayhem.

                             ==Phrack Inc.==

               Volume 0x0b, Issue 0x3a, Phile #0x08 of 0x0e

|=-------------=[ Crochetage avancé de fonction sur IA32 ]=--------------=|
|=----------------=[ (IA32 ADVANCED FUNCTION HOOKING) ]=-----------------=|
|=-----------------------------------------------------------------------=|
|=-------------------=[ mayhem   ]=---------------------=|
|=------=[ Traduit par Thiébaud Weksteen   ]=-------=|
|=-----------------------=[  08 Décembre 2001  ]=------------------------=|

 
--[ Sommaire

 1 - Introduction
   1.1 - Histoire
   1.2 - Nouvelle demande

 2 - Les bases du crochetage
   2.1 - Techniques usuelles
   2.2 - Choses à ne pas oublier

 3 - Le code expliqué

 4 - Utilisation de la bibliothèque
   4.1 - L'API
   4.2 - Résolution des symboles du noyau
   4.3 - Les rouages de LKH : l'objet hook_t

 5 - Tester le code
   5.1 - Chargement du module
   5.2 - Amusons-nous un peu
   5.3 - Le code

 6 - Références
   


--[ 1 - Introduction

  Les abus, l'enregistrement des traces, les corrections ou même le débogage :
  voilà de bonnes raisons de penser que le crochetage (hooking) à son 
  importance. Nous allons essayer de comprendre comment cela fonctionne. Le 
  contexte de démonstration est l'environnement du noyau Linux. On trouvera, 
  à la fin de l'article une bibliothèque universelle de détournement pour la 
  série du noyau linux 2.4, développée sous 2.4.5 et tournant sur une 
  architecture IA32. Elle s'appelle LKH, Linux Kernel Hooker [Crocheteur de 
  noyau Linux]. 


----[ 1.1 - Histoire

  Un des documents de référence sur le sujet du crochetage de fonction a été 
  publié en Novembre 1999 et son auteur est Silvio Cesare (salut mec ;-). Cette
  implémentation était assez simple puisque le crochetage consistait à 
  modifier les premiers octets de la fonction pour sauter vers un autre
  code, en vue de filtrer l'accès à la fonction acct_process
  du noyau, pour éviter que certains processus ne soient pris en compte.


----[ 1.2 - Nouveaux besoins


 Plusieurs travaux ont été effectués depuis cette époque :

 - l'utilisation pragmatique des redirections nécessite souvent, si ce n'est
   toujours, les paramètres originaux, quelque soit leur nombre ou leur
   taille (par exemple, si l'on désire modifier et transférer des paquets IP).

 - on peut avoir besoin de désactiver un crochet à la demande, ce qui est 
   parfait pour la configuration du noyau en cours de fonctionnement. On peut
   aussi vouloir appeler les fonctions originales (crochetage discret, utilisé
   par les programmes de surveillance) ou non (crochetage agressif, utilisé par
   les rustines de sécurité pour gérer ACL - Access Control Lists - ) sur les
   objets du noyau.
  
 - dans certains cas, on peut aussi avoir besoin de détruire le crochet juste 
   après le premier appel, par exemple, pour faire des statistiques (on peut 
   crocheter une fois par seconde ou par minute).




--[ 2 - Les bases du crochetage


----[ 2.1 Techniques usuelles


 Bien sûr, le code de base du crochetage doit être réalisé en langage 
 assembleur, mais le code de formatage du crochetage est réalisé en C. 
 L'interface de haut-niveau LKH est décrite dans la section API. 
 Commençons par comprendre les bases du crochetage.
 
 Voici en quoi consiste, en gros, le crochetage :

 - modifier le début du code d'une fonction pour pointer vers un autre
   code (nommé 'code de crochetage'). C'est une façon de faire ce qui ne
   date pas d'hier et qui est efficace. On peut aussi poser une rustine sur 
   chaque appel référençant cette fonction dans le segment du code. Cette 
   seconde méthode a quelques avantages (c'est vraiment discret) mais 
   l'implémentation est un peu plus compliquée (il faut analyser les blocs 
   de mémoire, puis examiner le code) et n'est pas très rapide. 

 - modifier, lors de l'exécution, l'adresse de retour de la fonction
   pour prendre le contrôle lorsque l'exécution de la fonction crochetée
   est terminée.

 - le code de crochetage doit posséder deux parties différentes. La 
   première doit être executée avant la fonction (préparer la pile 
   pour accéder aux paramètres, lancer les fonctions de rappel [callback],
   restaurer le code de l'ancienne fonction). La seconde doit être exécutée
   après (réinitialiser de nouveau le crochet si nécessaire). 

 - les paramètres par défaut (définissant le comportement du crochet) doivent
   être attribués lors de la création du crochet (avant de modifier le code
   de la fonction). Les paramètres dépendant de la fonction doivent être
   fixés à ce moment-là.

 - ajouter des fonctions de rappel. Chaque fonction de rappel peut accéder aux
   paramètres de la fonction originale et même les modifier.

 - activer, désactiver, changer les paramètres, ajouter ou retirer des 
   fonctions de rappel en fonction de nos besoins.




----[ 2.2 - Choses à ne pas oublier


  -> Fonctions sans pointeur de cadre:

  Une caractéristique importante est la capacité de crocheter des fonctions 
  compilées avec l'option gcc -fomit-frame-pointer. Cette caractéristique
  implique l'absence de %ebp dans le code du crochetage, c'est pourquoi nous
  allons utiliser uniquement %esp pour les opérations sur la pile. Nous allons
  aussi devoir faire quelques mises à jour (quelques octets par-ci, par-là)
  pour corriger les décalages relatifs d'%ebp dans le code du crochet. Pour 
  plus d'informations à ce sujet, voir khook_create() dans lkh.c.

  Le code du crochet doit également être indépendant de sa position. Ceci 
  explique pourquoi tellement de décalages sont corrigés lors de l'exécution 
  (Comme nous sommes dans le noyau, les décalages doivent être corrigés lors 
  de la création du crochet, mais des techniques tout à fait comparables sont
  possibles pour le crochetage de fonction dans les processus *à l'exécution*).


  -> Récursivité
 
  Il faut pouvoir appeler la fonction originale à partir d'une fonction
  de rappel, donc le code original doit être restauré avant l'exécution de 
  n'importe quelle fonction de rappel.


 -> Valeurs de retour

  Il faut retourner la valeur correcte dans %eax, qu'il y ait des fonctions de
  rappel ou non, que la fonction originale soit appelée ou non. Dans la 
  démonstration, la valeur de retour est celle de la dernière fonction de 
  rappel si la fonction originale n'est pas appelée. Si ni les fonctions de
  rappel ni la fonction originale ne sont appelées, la valeur de retour est 
  indépendante de notre volonté.


 -> Les fonctions de rappel POST

  Il est impossible d'accéder aux paramètres de la fonction si des fonctions
  de rappel ont été exécutées après la fonction originale. C'est donc quelque
  chose à éviter si possible. Voici quand même comment on le fait :

  - définir le crochet comme agressif.
  
  - appeler les fonctions de rappel PRE.

  - appeler la fonction originale à partir d'une fonction de rappel avec 
    ses propres paramètres.

  - appeler les fonctions de rappel POST.


--[ 3 - Le code expliqué


    Premièrement, il faut installer le crochet.
 
    A - Écraser, avec un saut indirect vers la zone de code du crochet, les 7 
        premiers octets de la routine détournée.

        L'adresse virtuelle introduit dans %eax est l'adresse absolue du
        code du crochet. Ainsi à chaque appel de la fonction 
        piratez_moi(), le code du crochet prendra le contrôle.

        Avant le détournement:

        0x80485ec :          mov    0x4(%esp,1),%eax
        0x80485f0 :        push   %eax
        0x80485f1 :        push   $0x8048e00
        0x80485f6 :       call   0x80484f0 
        0x80485fb :       add    $0x8,%esp


        Après le détournement:

        0x80485ec :          mov    $0x804a323,%eax
        0x80485f1 :        jmp    *%eax
        0x80485f3 :        movl   (%eax,%ecx,1),%es
        0x80485f6 :       call   0x80484f0 
        0x80485fb :       add    $0x8,%esp
        
        Les 3 instructions affichées après le saut ne veulent rien dire,
        puisque gdb est induit en erreur par notre crochet.
    

    B - Réinitialiser les octets originaux de la fonction crochetée. En effet, 
        il faut pouvoir appeler la fonction originale sans tout casser.
        
           pusha
           movl        $0x00, %esi                     (1)
           movl        $0x00, %edi                     (2)
           push        %ds
           pop         %es
           cld
           xor         %ecx, %ecx
           movb        $0x07, %cl
           rep movsl                   


        Les deux décalages NULL ont, en fait, été modifiés pendant la création
        du crochet (comme leurs valeurs dépendent du décalage de la fonction
        crochetée, il faut corriger le code de crochetage lors de l'exécution).
        (1) est fixé par le décalage du tampon contenant les 7 premiers octets
        enregistrés de la fonction originale. (2) est fixé par l'adresse de la
        fonction originale. Si vous connaissez bien le langage assembleur x86,
        vous savez que ces instructions copient les %ecx octets de %ds:%esi 
        vers %ds:%edi. Voir [2] pour connaître les instructions spécifiques à 
        INTEL.


    C - Initialiser la pile pour autoriser l'accès en lecture/écriture aux 
        paramètres et le lancement de fonctions de rappel. On copie
        l'adresse du premier paramètre original dans %eax puis on l'empile.       

           leal        8(%esp), %eax
           push        %eax
           nop; nop; nop; nop; nop
           nop; nop; nop; nop; nop
           nop; nop; nop; nop; nop
           nop; nop; nop; nop; nop
           nop; nop; nop; nop; nop
           nop; nop; nop; nop; nop
           nop; nop; nop; nop; nop
           nop; nop; nop; nop; nop             


        Notez que les emplacements vides sont plein d'instruction NOP (code
        opération 0x90).
        Cela signifie aucune opération. Lorsqu'un emplacement est rempli 
        (en utilisant la fonction khook_add_entry), 5 octets sont utilisés :

        - le code opération de 'call' (code opération 0xE8)

        - l'adresse virtuelle de la fonction de rappel (adresse relative de 4 octets) 

    On a choisi de fixer un maximum de 8 fonctions de rappel. Chaque
    fonction de rappel insérée est appelée avec un paramètre (la valeur empilée
    de %eax contenant l'adresse des paramètres de la fonction originale, posés
    sur la pile). 

                  
    D - Réinitialiser la pile.

           add $0x04, %esp             

        On retire alors l'adresse du paramètre de la fonction originale
        insérée en (C). De cette façon, %esp est réinitialisée à son ancienne
        valeur (celle avant d'arriver à l'étape C). À cet instant, la pile ne 
        contient plus le cadre de pile de la fonction originale puisque 
        celui-ci a été écrasé lors de l'étape (A). 


    E - Modifier l'adresse de retour de la fonction originale sur la pile.
        Sur les processeurs INTEL, les adresses de retour de fonction sont
        enregistrés sur la pile, ce qui n'est pas une très bonne idée du
        point de vue sécurité ;-). Avec cette modification, on retourne
        où on veut (vers le code du crochet) après l'exécution de la fonction
        originale. On appelle alors la fonction originale. Au retour, le 
        code de crochetage récupère le contrôle. Regardons ce processus 
        attentivement :


        -> Premièrement, nous récupérons l'%eip courant et l'enregistrons 
           dans %esi (l'étiquette "end" fait référence à du code que l'on
           peut facilement identifier lors de l'étape E5). On utilise toujours 
           cette astuce pour du code indépendant de sa position.

        1.  jmp         end
            begin:
            pop         %esi                    

        -> Puis nous récupérons l'ancienne adresse de retour qui se trouve
           à 4(%esp) et on l'enregistre dans %eax.  

        2.  movl        4(%esp), %eax

        -> On utilise cette adresse de retour sauvegardée comme un
           décalage de 4 octets à la fin du code de crochetage (voir le
           pointeur NULL dans l'étape G), afin de pouvoir revenir au bon endroit
           à la fin du processus de crochetage.

        3.  movl        %eax, 20(%esi)          

        -> On modifie l'adresse de retour de la fonction originale
           pour pouvoir y retourner juste après l'instruction 'call begin'.

        4.  movl        %esi, 4(%esp)
            movl        $0x00, %eax

        -> On appelle la fonction originale. L'étiquette 'end' est utilisée
           à l'étape 1, et l'étiquette 'begin' fait référence au code
           qui suit immédiatement l'instruction 'jmp end' (toujours à l'étape 1).
           La fonction originale se terminera juste après l'instruction 
           'call begin' puisque nous avons modifié son adresse de retour.

        5.  jmp         *%eax
            end:
            call        begin


     F - Retour au code de crochetage. On réaffecte les 7 octets malicieux
         dans le code de la fonction originale. Ces octets ont été réinitialisé
         à leur valeur originale avant l'appel à la fonction, on doit donc
         crocheter de nouveau la fonction (comme dans l'étape A). 

         Cette étape est remplacée par des symboles de non-opération (NOP) 
         si le crochet est à usage unique (non permanent), pour que les 7 
         octets de notre saut malicieux indirect (étape A) ne soient pas copiés.
         Cette étape est vraiment proche de l'étape (B) puisqu'elle utilise
         le même mécanisme de copie (en utilisant les instructions movs* de 
         manière répétitive), donc pour plus d'explications, voir cette étape.
         Les décalages NULL dans le code doivent être fixés lors de la création
         du crochet : 

         - le premier (le tampon source) est remplacé par l'adresse du tampon 
           des octets malicieux.

         - le second (le tampon destination) est remplacé par l'adresse du 
           point d'entrée de la fonction originale.          

            movl        $0x00, %esi
            movl        $0x00, %edi
            push        %ds
            pop         %es
            cld
            xor         %ecx, %ecx
            movb        $0x07, %cl
            rep movsb                   


    G - On utilise l'adresse de retour originale (enregistrée lors de l'étape 
        E2) et on retourne à la fonction appelante originale. Le décalage NULL
        que l'on peut voir (*) doit être fixé à l'étape E2 avec l'adresse
        de retour de la fonction originale. La valeur de %ecx est alors
        empilée sur la pile de sorte que la prochaine instruction 'ret' 
        l'utilisera comme s'il s'agissait d'un %eip enregistré sur la pile. 
        Cela renvoie à l'endroit (correct) original.  
 
            movl        $0x00, %ecx	*
            pushl       %ecx
            ret



--[ 4 - Utilisation de la bibliothèque

----[ 4.1 - L'API


 L'API de LKH est assez simple à utiliser :
  
 hook_t        *khook_create(int addr, int mask);

        Crée un crochet sur l'adresse 'addr'. Accepte aussi le type par défaut
        (HOOK_PERMANENT [permanent] ou HOOK_SINGLESHOT [à usage unique]), 
        l'état par défaut (HOOK_ENABLED [activé] ou HOOK_DISABLED [désactivé])
        et le mode par défaut (HOOK_AGGRESSIVE [intrusif] ou HOOK_DISCRETE 
        [discret]). Le type, l'état et le mode sont combinés à l'aide de 
        l'opération logique OU dans le paramètre 'mask'.


 void khook_destroy(hook_t *h);

        Désactive, supprime et libère les ressources associées à un crochet.


 int khook_add_entry(hook_t *h, char *routine, int range);

        Ajoute un fonction de rappel au crochet, au rang 'range'. Renvoie -1
        si le rang fourni est invalide. Sinon, renvoie 0.


 int khook_remove_entry(hook_t *h, int range);

        Supprime la fonction de rappel mise dans l'emplacement 'range'. Renvoie
        -1 si le rang fourni est invalide. Sinon, renvoie 0.


 void khook_purge(hook_t *h);

        Supprime toutes les fonctions de rappel associées à ce crochet.


 int khook_set_type(hook_t *h, char type);

        Change le type du crochet 'h'. Le type peut être HOOK_PERMANENT
        (le code de crochetage est exécuté à chaque appel de la fonction)
        ou HOOK_SINGLESHOT (le code de crochetage est exécuté seulement pour
        un détournement, ensuite, le crochet est retiré proprement).
        

 int khook_set_state(hook_t *h, char state);

        Change l'état du crochet 'h'. L'état peur être HOOK_ENABLED (le crochet
        est activé) ou HOOK_DISABLED (le crochet est désactivé).


 int khook_set_mode(hook_t *h, char mode);

        Change le mode du crochet 'h'. Ce mode peut être HOOK_AGGRESSIVE
        (le crochet n'appelle pas la fonction détournée) ou HOOK_DISCRETE
        (le crochet appelle la fonction détournée après avoir exécuté
        les fonctions de rappel). Une partie du code du crochet est remplacée    
        par des symboles de non-opération si le crochet est agressif (étape
        E).


 int khook_set_attr(hook_t *h, int mask);

        Change le mode, l'état et/ou le type en utilisant un unique
        appel de fonction. Cette fonction retourne 0 en cas de succès
        ou -1 si le masque spécifié contient des options incompatibles.


 Il est possible d'ajouter et de supprimer des entrées à volonté, quel que
 soit l'état, le type et le mode utilisé par le crochet.

 
----[ 4.2 - Résolution des symboles du noyau

 Une fonction de résolution des symboles a été ajoutée à LKH, permettant
 d'accéder aux valeurs des fonctions exportées.

 int ksym_lookup(char *name);

 Cela renvoi NULL si le symbole reste non-résolu. Cette recherche peut résoudre
 les symboles contenus dans la section __ksymtab du noyau, une liste
 exhaustive de ces symboles est affichées lors de l'exécution de la 
 commande 'ksyms -a' :

 bash-2.03# ksyms -a | wc -l
   1136
 bash-2.03# wc -l /boot/System.map 
  14647 /boot/System.map
 bash-2.03# elfsh -f /usr/src/linux/vmlinux -s   # affiche les sections

 [SECTION HEADER TABLE]

 (nil)      ---             foffset:    (nil)        0 bytes [*Unknown*] 
 (...)
 0xc024d9e0 a-- __ex_table  foffset: 0x14e9e0     5520 bytes [Program data] 
 0xc024ef70 a-- __ksymtab   foffset: 0x14ff70     9008 bytes [Program data] 
 0xc02512a0 aw- .data       foffset: 0x1522a0    99616 bytes [Program data] 
 (...)
 (nil)      --- .shstrtab   foffset: 0x1ad260      216 bytes [String table] 
 (nil)      --- .symtab     foffset: 0x1ad680   245440 bytes [Symbol table] 
 (nil)      --- .strtab     foffset: 0x1e9540   263805 bytes [String table] 

 [END]
 

 En fait, la section __ksymtab mappée dans la mémoire ne contient pas tous
 les symboles du noyau que l'on désire détourner.
 D'un autre côté, la section non-mappée .symtab est assurément plus grande
 (245440 octets contre 9008 octets). Lorsqu'on utilise 'ksyms', l'appel système
 __NR_query_module (ou __NR_get_kernel_syms pour les anciens noyaux) est 
 utilisé en interne. Cet appel système n'a accès qu'à la section __ksymtab 
 puisque la table complète des symboles du noyau contenu dans  __ksymtab n'est
 pas chargée en mémoire. La solution pour accéder à la table complète des
 symboles consiste à prendre les octets de décalages dans le fichier 
 System.map (on le crée en utilisant la commande `nm -a vmlinux > System.map`).
 

 bash-2.03# ksyms -a | grep sys_fork
 bash-2.03# grep sys_fork /boot/System.map 
 c0105898 T sys_fork
 bash-2.03# 
 

 #define        SYS_FORK        0xc0105898

  if ((s = khook_create((int) SYS_FORK, HOOK_PERMANENT, HOOK_ENABLED)) == NULL)
    KFATAL("init_module: impossible de poser le crochet sur la fonction *sys_fork* ! \n", -1);
  khook_add_entry(s, (int) fork_callback, 0);

 #undef SYS_FORK


 Pour les systèmes qui n'ont pas de fichier System.map ni d'images
 de noyau décompressée (vmlinux), il est possible de décompresser le fichier
 vmlinuz (attention, ce n'est pas un format gzip standard ! Pour des 
 informations utiles à ce sujet, voir [3]) et de créer manuellement un nouveau
 fichier System.map.

 Une autre manière de résoudre les symboles non-exportés du noyau peut être
 la recherche par statistique : l'analyse des références dans le code 
 hexadécimal du noyau peut nous permettre de prédire les valeurs des symboles 
 (en allant chercher les instructions call ou jmp). La difficulté de cet 
 outil serait la portabilité, puisque les codes du noyau changent d'une
 version à l'autre.


 N'oubliez pas de changer SYS_FORK en y mettant votre propre valeur de décalage de sys_fork.
 

----[ 4.3 - Les rouages de LKH : l'objet hook_t

 Jetons un coup d'œil à la structure hook_t (l'entité crochet dans la mémoire) :

typedef struct        s_hook
{
  int                 addr;                        
  int                 offset;                        
  char                saved_bytes[7];                
  char                voodoo_bytes[7];        
  char                hook[HOOK_SIZE];        
  char                cache1[CACHE1_SIZE];    
  char                cache2[CACHE2_SIZE];        
}		      hook_t;

 

 h->addr            Adresse de la fonction originale, utilisée pour activer
                    ou désactiver le crochet.            

 h->offset          Ce champ contient le décalage d'octet à partir de h->addr
                    où commencer l'écrasement pour mettre en place le 
                    détournement. Sa valeur est 3 ou 0, cela dépend si la
                    fonction possède un cadre de pile ou non.

 h->original_bytes  Les sept octets écrasés de la fonction originale.

 h->voodoo_bytes    Les sept octets que l'on doit placer au début de la fonction
                    pour la rediriger (contient le code d'un saut indirect 
                    détaillé dans l'étape A du paragraphe 3).

 h->hook            Tampon des codes-opération contenant le code du crochetage,
                    où on insère les références des fonctions de rappel en utilisant
                    khook_add_entry() .

 Les tampons cache1 et cache2 sont utilisés pour sauvegarder une partie du code 
 du crochet lorsque le mode HOOK_AGGRESSIVE est activé (comme il faut remplir
 l'appel de la fonction original avec des codes de non-opération, il est nécessaire 
 de sauvegarder ce code, pour finalement remettre en mode discret le crochet). 
     
 Chaque fois qu'un crochet est créé, une instance de hook_t est déclarée
 et allouée. Il faut créer un crochet par fonction à détourner si on le souhaite.


----[ 5 - Tester le code

 Rendez-vous d'abord sur http://www.devhell.org/~mayhem/ pour obtenir une version
 récente du code. Le paquet (version 1.1) est fourni à la fin de cet article.

 Il faut juste faire #include "lkh.c" et jouer ! Dans ce module donné en exemple
 et qui utilise LKH, on veut détourner :

 
 - la fonction hijack_me(), ici on peut vérifier que le passage des paramètres
   s'est bien déroulé et que leur modification, à travers les fonctions de rappel,
   est correct. 

 - la fonction schedule(), détournement unique [SINGLESHOT].

 - la fonction sys_fork(), détournement permanent [PERMANENT].


------[ 5.1 - Chargement du module

bash-2.03# make load
insmod lkh.o
Test d'un crochet permanent, agressif et activé avec 3 fonctions de rappel:
A in hijack_one  = 0 -OK- 
B in hijack_one  = 1 -OK- 
A in hijack_zero = 1 -OK- 
B in hijack_zero = 2 -OK- 
A in hijack_two  = 2 -OK- 
B in hijack_two  = 3 -OK- 
--------------------
Test d'un crochet désactivé:
A in HIJACKME!!! = 10 -OK-
B in HIJACKME!!! = 20 -OK-
--------------------
Appel à hijack_me après la destruction du crochet
A in HIJACKME!!! = 1  -OK-
B in HIJACKME!!! = 2  -OK-
SCHEDULING!

------[ 5.2 - Amusons-nous un peu

bash-2.05# ls
FORKING!
Makefile  doc  example.c  lkh.c  lkh.h  lkh.o  user  user.c  user.h  user.o
bash-2.05# pwd
/usr/src/coding/LKH

(N'affiche pas FORKING! puisque pwd est une commande intégré au terminal :)


bash-2.05# make unload
FORKING!
rmmod lkh;
LKH unloaded - sponsorized by the /dev/hell crew!
bash-2.05# ls
Makefile  doc  example.c  lkh.c  lkh.h  lkh.o  user  user.c  user.h  user.o
bash-2.05# 


On peut voir "FORKING!" à chaque appel de la fonction sys_fork() du noyau
(le crochet est permanent) et "SCHEDULING!" lors du premier appel de la fonction 
schedule() du noyau (puisqu'il s'agit d'un crochet à usage unique, la fonction
schedule() est détournée seulement une fois, puis le crochet est retiré).

Voici le code commenté pour cette démo:


------[ 5.3 - Le code

/*
** Code de démonstration de LKH, développé et testé sous Linux x86 2.4.5
**
** Le code de la bibliothèque est joint.
** Pour les mises à jour, voir http://www.devhell.org/~mayhem/.
**
** Cette archive contient un code mode utlisateur [userland] (qu'on peut lancer 
** à partir de GDB), le module noyau LKH et son fichier d'inclusion, et 
** ce fichier (lkm-example.c). 
** 
** Toutes suggestions {et, ou} rapport de bogue sont les bienvenues ! LKH 1.2
** est déjà en développement.
**
** Un merci particulier à b1nf pour le contrôle de la qualité ;)
** Un grand hourra pour kraken, continue comme ça, mec, tu fais du bon 
** boulot avec psh!
** 
** Merci à csp0t (il n'y a qu'un mot pour te décrire : *elite*)
** et cma4 (la puissance d'EPITECH, mon pro favori du noyau win32)  
**
** Gros bisous à l'équipe devhell (r1x et nitrogen fux0r)
** Lightman, Gab et Xfred de chx-labs (arrêtez de fumer, bande de drogués ;)
**
** Merci à l'équipe de Phrack et particulièrement skyper pour son
** super soutien. Le Havre en force ! Case mais oui je t'aime ;)
*/
#include "lkh.c"


int        hijack_me(int a, int b);	/* fonction détournée */
int        hijack_zero(void *ptr);	/* première fonction de rappel */
int        hijack_one(void *ptr);	/* deuxième fonction de rappel */
int        hijack_two(void *ptr);	/* troisième fonction de rappel */
void       hijack_fork(void *ptr);	/* fonction de rappel sys_fork */
void       hijack_schedule(void *ptr);	/* fonction de rappel schedule */

static  hook_t        *h = NULL;
static  hook_t        *i = NULL;
static  hook_t        *j = NULL;


int
init_module()
{
  int                ret;

  printk(KERN_ALERT "Change the SYS_FORK value then remove the return \n");
  return (-1);

  /*
  ** Crée les crochets
  */

#define        SYS_FORK 0xc010584c

  j = khook_create(SYS_FORK
                 , HOOK_PERMANENT
                 | HOOK_ENABLED
                 | HOOK_DISCRETE);

#undef        SYS_FORK

  h = khook_create(ksym_lookup("hijack_me")
                 , HOOK_PERMANENT
                 | HOOK_ENABLED
                 | HOOK_AGGRESSIVE);

  i = khook_create(ksym_lookup("schedule")
                 , HOOK_SINGLESHOT
                 | HOOK_ENABLED 
                 | HOOK_DISCRETE);


  /*
  ** Encore une autre vérification
  */
  if (!h || !i || !j)
    {
      printk(KERN_ALERT "Impossible de crocheter les fonctions du noyau \n");
      return (-1);
    }


  /*
  ** Ajout de quelques fonctions de rappel pour les fonctions sys_fork et schedule
  */
  khook_add_entry(i, (int) hijack_schedule, 0);
  khook_add_entry(j, (int) hijack_fork, 0);



  /*
  ** Test du crochet hijack_me
  */
  printk(KERN_ALERT "LKH: permanent, agressif et activé avec 3 fonctions de rappel:\n");
  khook_add_entry(h, (int) hijack_zero, 1);
  khook_add_entry(h, (int) hijack_one, 0);
  khook_add_entry(h, (int) hijack_two, 2);
  ret = hijack_me(0, 1);

  printk(KERN_ALERT "--------------------\n");
  printk(KERN_ALERT "Test d'un crochet désactivé:\n");
  khook_set_state(h, HOOK_DISABLED);
  ret = hijack_me(10, 20);

  khook_destroy(h);
  printk(KERN_ALERT "------------------\n");
  printk(KERN_ALERT "Appel à hijack_me après la destruction du crochet\n");
  hijack_me(1, 2);

  return (0);
}



void
cleanup_module()
{
  khook_destroy(i);
  khook_destroy(j);
  printk(KERN_ALERT "LKH unloaded - sponsorized by the /dev/hell crew!\n");
}




/*
** Fonction à détourner
*/
int
hijack_me(int a, int b)
{
  printk(KERN_ALERT "A in HIJACKME!!! = %u \t -OK- \n", a);
  printk(KERN_ALERT "B in HIJACKME!!! = %u \t -OK- \n", b);
  return (42);
}



/*
** Première fonction de rappel pour hijack_me()
*/
int
hijack_zero(void *ptr)
{
  int        *a;
  int        *b;

  a = ptr;
  b = a + 1;
  printk(KERN_ALERT "A in hijack_zero = %u \t -OK- \n", *a);
  printk(KERN_ALERT "B in hijack_zero = %u \t -OK- \n", *b);
  (*b)++;
  (*a)++;
  return (0);
}



/*
** Deuxième fonction de rappel pour hijack_me()
*/
int
hijack_one(void *ptr)
{
  int        *a;
  int        *b;
  
  a = ptr;
  b = a + 1;
  printk(KERN_ALERT "A in hijack_one  = %u \t -OK- \n", *a);
  printk(KERN_ALERT "B in hijack_one  = %u \t -OK- \n", *b);
  (*a)++;
  (*b)++;
  return (1);
}



/*
** Troisième fonction de rappel pour hijack_me()
*/
int
hijack_two(void *ptr)
{
  int        *a;
  int        *b;

  a = ptr;
  b = a + 1;
  printk(KERN_ALERT "A in hijack_two  = %u \t -OK- \n", *a);
  printk(KERN_ALERT "B in hijack_two  = %u \t -OK- \n", *b);
  (*a)++;
  (*b)++;
  return (2);
}




/*
** Fonction de rappel pour schedule() (symbole exporté du noyau)
*/
void        hijack_schedule(void *ptr)
{
  printk(KERN_ALERT "SCHEDULING! \n");
}



/*
** Fonction de rappel pour sys_fork() (symbole non exporté du noyau)
*/
void
hijack_fork(void *ptr)
{
  printk(KERN_ALERT "FORKING! \n");
}




--[ 6 - Références

 [1] Détournement de fonction du noyau
     http://www.big.net.au/~silvio/
 [2] Manuel des développeurs INTEL
     http://developers.intel.com/design/pentiu m4/manuals/
 [3] Rouages internes du noyau Linux
     http://www.linuxdoc.org/guides.html


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

le 11/01/2009 par mayhem [trad Thiébaud Weksteen]