Exploit root Vmsplice


<< La vulnérabilité de vmsplice() NULL Pointer Dereference >>


III°) Empêcher l'exploitation sans reboot

   Il n'est pas possible de patcher entièrement ce type de vulnérabilité à chaud : effectivement, la seule manière de réellement corriger la faille est de corriger le code source (ce qui a été fait sur tous les miroirs depuis) en ajoutant la validation des pointeurs iov_base par access_ok() dans les fonctions concernées. Sur un système non patché, il convient plutôt d'interdire l'appel système correspondant, sys_vmsplice, qui est très peu utilisé et n'est pas obligatoire puisque il peut être réalisé à l'aide d'un enchaînement d'autres appels systèmes. Ici, nous ne prenons pas une mesure aussi radicale. Nous vérifions si le pointeur est valide ou non avant d'accepter l'appel système. Il faut être conscient que le code vulnérable est encore présent en mémoire et que la correction n'est pas forcément complète (mais paraît equilibrée entre un système vulnérable et un appel système en moins). Si besoin, nous fournissons plus loin dans cette page la ligne à rajouter pour compléter la correction.
Nous avons donc codé un module qui va détourner l'appel système correspondant, logguer tout appel contenant un pointeur invalide puis retourner EFAULT (Bad Address). Voici donc le hotfix pour Intel x86 que nous vous proposons. Les parties importantes sont abondamment commentées :
    /*
    ###############################################################
    # Vmsplice HotFix par François GOICHON pour bases-hacking.org #
    ###############################################################
    */


    // ATTENTION ! FAIRE SYNC AVANT *TOUT* INSMOD || RMMOD

    //Les headers standards quand on fait du kernel module
    #include <linux/kernel.h> // On travaille dans le kernel
    #include <linux/module.h> // On fait un module
    #include <linux/syscalls.h> //Les symboles des syscalls
    #include <asm/unistd.h> // La liste des syscalls pour __NR

    #include <linux/tty.h> // Pour l'utilisationd des terminaux
    #include <linux/version.h> // Pour LINUX_VERSION_CODE
    #include <linux/sched.h> // Besoin pour init_mm & stuff et current
    #include <asm/uaccess.h> // Pour get_user()

    #include <asm/cacheflush.h> // Pour global_flush_tlb() , change_page_attr()
    #include <asm/page.h> // Macro virt_to_page()
    #include <linux/init.h> // Pour KERNEL_PAGE

    /*
    ################
    # Infos Module #
    ################
    */
    MODULE_LICENSE("GPL"); //Pour avoir accès complet au système, ne pas souiller le kernel
    MODULE_AUTHOR("François GOICHON, www.bases-hacking.org");
    MODULE_DESCRIPTION("Hotfix contre l'exploit root Vmsplice, moche et dangereux ;) (02/2008)");


    /*
    #####################
    # Ecriture Terminal #
    #####################
    */

    [...]
    //Définition de la fonction printf(char *) qui va écrire une chaîne sur le terminal
    //courant ainsi que dans les logs systèmes
    //Ceci ne nous intéresse pas réellement ici, ce n'est finalement que de la présentation



    /*
    #########################
    # Réécriture du syscall #
    #########################
    */

    /*
    * On synchronise le déchargement du module : le module ne se déchargera que quand
    * plus personne n'utilisera notre syscall
    */

    static int synchro = 0;

    /*
    * La table des appels systèmes n'est plus exportée
    * dans les kernels 2.6.x, pour ça qu'il n'y a pas l'extern
    * qu'on peut voir dans toutes les références
    *
    * Elle sera remplie par get_sct()
    * Static (on ne rigole pas avec la table des sycalls..)
    */
    static void **sys_call_table = NULL;

    /*
    * On va remplacer un appel système. On va donc garder un
    * pointeur vers le syscall original (au cas uù quelqu'un l'aurait
    * modifié avant nous)
    */

    asmlinkage long (*vmsplice_original) (int fd, const struct iovec __user *iov, unsigned long nr_segs, unsigned int flags);

    /*
    * On redéfinit le syscall
    */

    //Syscall de raccordement de pages utilisateurs à un tube
    asmlinkage long notre_sys_vmsplice (int fd, const struct iovec __user *iov, unsigned long nr_segs, unsigned int flags) {

      synchro++; //printf n'est pas atomique
      if (unlikely(!access_ok(VERIFY_READ, iov->iov_base, iov->iov_len)))
      {
      printk(KERN_ALERT "Exploit root vmsplice sûrement tenté par %d\n",current->uid); //current pointe le contexte courant
      synchro--;
      return -EFAULT;
      }

      synchro--;

      return vmsplice_original(fd,iov,nr_segs,flags);
    }

    static int get_sct (void) {
      unsigned long *ptr;

      /*
      * le symbole sys_call_table n'étant plus exportée, on doit
      * la retrouver manuellement, "the hackish way" comme qui dirait.
      *
      * La syscall table est contenue dans le segment Kernel Data
      * situé à la suite de Kernel Code (entre end_code et end_data donc)
      * On le repère grâce à  trois appels choisis arbitrairement
      * Ici, sys_close() puis confirmation avec sys_read() et sys_open
      *
      * Consulter asm/unistd.h pour plus de compréhension de l'algo et
      * des constantes utilisées
      *
      * Cet algo n'est pas portable
      */

      ptr=(unsigned long *)((init_mm.end_code + 4) & 0xfffffffc);

      printf("Recherche de l'adresse de sys_call_table ...");
      printk("Début: 0x%p Fin: 0x%p\n",(unsigned long *)init_mm.end_code,(unsigned long *)init_mm.end_data);
      printk("Ptr: 0x%p\n",(unsigned long *)ptr);


      /* On fouille la section des données */
      while((unsigned long )ptr < (unsigned long)init_mm.end_data) {
        if ((unsigned long *)*ptr == (unsigned long *)sys_close) {

          printk (KERN_INFO " -> Appel sys_close() trouvé à  0x%p\n", ptr);

          if ((unsigned long *)*((ptr-__NR_close)+__NR_read) == (unsigned long *) sys_read && *((ptr-__NR_close)+__NR_open) == (unsigned long) sys_open ) {

            printk (" -> table des syscalls possible à 0x%p\n", ptr-__NR_close);
            printk (" -> sys_write à 0x%p\n", (unsigned long *)*(ptr-__NR_close+4));
            printk (" -> sys_fork à 0x%p\n", (unsigned long *)*(ptr-__NR_close+2));

            sys_call_table = (void **) ((unsigned long *)(ptr-__NR_close));

            break;
          }

        }
        ptr++;
      }

      printk(KERN_INFO"sys_call_table trouvée à : 0x%p\n", sys_call_table);

      if (sys_call_table == NULL) return 0;
      else return 1;

    }


    static int is_address_writable(unsigned long address)
    //Vérifie si on peut écrire à l'adresse address, retourne <= 0 en cas d'erreur, 1 sinon
    {
      pgd_t *pgd = pgd_offset_k(address);
    #ifdef PUD_SIZE
      pud_t *pud;
    #endif
      pmd_t *pmd;
      pte_t *pte;

      if (pgd_none(*pgd))
      return -1;
    #ifdef PUD_SIZE
      pud = pud_offset(pgd, address);
      if (pud_none(*pud))
      return -1;
      pmd = pmd_offset(pud, address);
    #else
      pmd = pmd_offset(pgd, address);
    #endif
      if (pmd_none(*pmd))
        return -1;

      if (pmd_large(*pmd))
        pte = (pte_t *)pmd;
      else
        pte = pte_offset_kernel(pmd, address);

      if (!pte || !pte_present(*pte))
        return -1;

      return pte_write(*pte) ? 1 : 0;
    }


    /*
    #########################
    # Init/Cleanup du module#
    #########################
    */

    //Initialisation du module - remplacement de open()
    int init_module(void)
    {

      //Attention - trop tard, mais peut-être pour la prochaine fois..

      printf("Module dangereux : sync nécessaire avant chargement\n");
      printf("La fin du module est encore plus dangereuse ! Sync avant rmmod si votre système de fichiers vous importe...\n");

      printk(KERN_INFO "Hotfix pour l'exploit Vmsplice chargé\n");

      if (!get_sct())
        { //Si le remplissage de la syscall table n'a pas marché
        printf("Table des appels système introuvable. Abandon...\n");
        return 1;
      }

      printf("Table des appels système trouvée\n");

      if (!is_address_writable((unsigned long)sys_call_table))
      {
        printk("La table des appels système est peut-être en read-only\n");
        change_page_attr(virt_to_page(sys_call_table), 1, PAGE_KERNEL);
        global_flush_tlb();
      }

      //On garde en mémoire l'ancien syscall et on le remplace


      //On pourait vérifier avant avec ls -l /boot/vmlinuz-`uname -r` que la date du fichier < 8 Février 2008
      //mais l'administrateur est censé savoir si son système est vulnérable avant d'appliquer ce genre de patch
      vmsplice_original = sys_call_table[__NR_vmsplice];
      sys_call_table[__NR_vmsplice] = notre_sys_vmsplice;


      printf("Système patché\n");

      return 0;
    }


    //Fin du module : on nettoie nos bêtises
    void cleanup_module(void)
    {

      printf("Retour des syscalls à la normale...\n");

      //Retourner la table des syscalls à la normale
      if (sys_call_table[__NR_vmsplice] != notre_sys_vmsplice) {
        printf("Quelqu'un d'autre a joué avec la table des appels système\n");
        printf("Le système a de fortes chances de s'en retirer dans un état instable..\n");
      }

      sys_call_table[__NR_vmsplice] = vmsplice_original;


      printk(KERN_INFO "En attente de synchronisation\n");
      while(synchro);

      printk(KERN_INFO "Hotfix pour l'exploit Vmsplice déchargé\n");
    }

Encore une fois, le principe n'est pas si compliqué que ça : dans un premier temps, on appelle la fonction get_sct() qui, à l'aide de quelques appels systèmes dont les positions sont standard et connues, va repérer la table des appels systèmes parmi les données du noyau. Ensuite, on vérifie que la table est accessible en écriture avec la fonction is_address_writable() qui va lire le descripteur correspondant (dans la Page Table). Enfin, on remplace l'adresse du syscall présente dans la table des appels à l'offset correspondant par notre fonction personnalisée.
Cette fonction loggue simplement l'utilisation de vmsplice puis retourne en état d'erreur pour indiquer que l'appel système a echoué. En réalité, le code vulnérable demeure présent en mémoire quelque part comme nous l'avons déjà rappellé. On peut aussi
    memset(vmsplice_original,0xc3,1);
afin de remplacer le premier octet du code vulnérable par un retour à l'appelant, ce qui devrait corriger la faille de manière redoutable mais interdira l'appel système. Ceci dit, ce n'est pas très bon d'une manière générale de réécrire les appels (ce qui est différent de simplement le détourner comme nous faisons).
Enfin, la partie déchargement du module vérifie que personne n'utilise notre appel puis rétabli l'ancien appel à sa place. Afin de compiler, nous fournissons dans nos sources un Makefile adéquat. make insmod permettra de charger le module et make rmmod de le décharger proprement. Illustration avec une tentative avant déchargement :
    # make insmod
    make[1]: entrant dans le répertoire « /var/www/hacking/sources/Systeme/Vmsplice/src »
    make -C /lib/modules/2.6.24-1-686/build M=/var/www/hacking/sources/Systeme/Vmsplice/src modules
    make[2]: entrant dans le répertoire « /usr/src/linux-headers-2.6.24-1-686 »
       CC [M]   /var/www/hacking/sources/Systeme/Vmsplice/src/espion.o
       Building modules, stage 2.
       MODPOST 1 modules
       CC      /var/www/hacking/sources/Systeme/Vmsplice/src/espion.mod.o
       LD [M]   /var/www/hacking/sources/Systeme/Vmsplice/src/espion.ko
    make[2]: quittant le répertoire « /usr/src/linux-headers-2.6.24-1-686 »
    make[1]: quittant le répertoire « /var/www/hacking/sources/Systeme/Vmsplice/src »
    sync
    insmod src/espion.ko
    Module dangereux : sync nécessaire avant chargement

    La fin du module est encore plus dangereuse ! Sync avant rmmod si votre système de fichiers vous importe...

    Recherche de l'adresse de sys_call_table ...
    Table des appels système trouvée

    Détournement des syscalls afin de surveiller le système

    OK.
    #
On effectue une tentative avec un utilisateur normal puis on décharge :
    # make rmmod
    sync
    rmmod src/espion.ko
    Retour des syscalls à la normale...

    # dmesg
    [...]
    Module dangereux : sync nécessaire avant chargement
    La fin du module est encore plus dangereuse ! Sync avant rmmod si votre système de fichiers vous importe...
    Hotfix pour l'exploit Vmsplice chargé
    Recherche de l'adresse de sys_call_table ...Début: 0xc02be915 Fin: 0xc0371384
    Ptr: 0xc02be918
    -> Appel sys_close() trouvé à  0xc02c3718
    -> table des syscalls possible à 0xc02c3700
    -> sys_write à 0xc0178fb4
    -> sys_fork à 0xc01021d7
    sys_call_table trouvée à : 0xc02c3700
    Table des appels système trouvée
    Détournement des syscalls afin de surveiller le système
    Exploit root vmsplice sûrement tenté par 1000
    Retour des syscalls à la normale...
    En attente de synchronisation
    Hotfix pour l'exploit Vmsplice déchargé
    #
Le module se charge correctement et capte les tentatives d'utilisation de l'appel : le hotfix paraît donc fonctionnel.

<< La vulnérabilité de vmsplice() NULL Pointer Dereference >>



0 Commentaires




Commentaires désactivés.

Apprendre la base du hacking - Liens sécurité informatique/hacking - Contact

Copyright © Bases-Hacking 2007-2014. All rights reserved.