/* * Quick Fuzz Tester * by jimjones * * Greets to: Master , silvio, duke, phzyl0gik, and my brother sk8! * Really a simple concept, checks for simple buffer overflows and lack * of bounds checking by attempting to stuff environment variables and * command line arguments. * * Some custom env variables you might want to pass: * DYNAMIC,GLOBAL_OFFSET_TABLE_,JMALLOC_TRIM_THRESHOLD_,LANGUAGE,LC_MESSAGES, * LC_XXX,LOCALDOMAIN,LOCPATH,MALLOC_CHECK_,MALLOC_MMAP_MAX_,MALLOC_TOP_PAD_, * MALLOC_TRACE,NLSPATH,POSIX,POSIXLY_CORRECT,RES_OPTIONS * * Keep in mind, this is primarily for the use of testing binary files for * exploitation which you don't have the source for. If you're doing more * serious code auditing, please rm this and use its4 or just do it manually ;) * * To compile: gcc -o fuzztest fuzztest.c * To compile on NetBSD or OpenBSD: gcc -o fuzztest fuzztest.c -DFUNKY_BSD */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FUNKY_BSD #include #else #include #endif /* Provide ELF support for some whacky systems... like NetBSD ;> */ #ifndef ELFMAG #define ELFMAG Elf32_e_ident #define SELFMAG 4 #define ET_EXEC 2 #define EI_CLASS 4 #define ELFCLASS32 1 #endif #define FILLCHAR 'A' /* Search for 0x41 in GDB ;) */ #define FORMAT_BUG "%s %s %s %s %s" /* Really lame attempt */ //#define FORMAT_BUG "%p %p %p %p %p" Equally lame attempt #define DEFAULTSIZE 4096 /* Default overflow size */ #define TIMEOUT 10 #define HSIZE sizeof (Elf32_Ehdr) #define SSIZE sizeof (Elf32_Shdr) #define DATASECTION ".rodata" /* Intelligent mode's source */ #define OPT_CMDLINE 0 #define OPT_ENV 1 enum runmode { MODE_NORMAL = 0, MODE_USERENV = 1, MODE_USEROPT = 2, MODE_INTELLIGENT = 3 }; pid_t childproc; char elfopts[255], xargv0[255], **xargv, *elfenvs, *userenv, *usercmd; int progmode = MODE_NORMAL, numsims = 0, verbose = 0, formatbug = 0, bufsize = DEFAULTSIZE, elf_fd; void usage (char *program); int parse_elf (int fd); void alarmclock (); int isoptstring (char *optstring); int isenvvariable (char *envvariable); void auditoptstring (char *optstring); char **duplicatestrarray (char **src); char **getarrayptr (char **target); int execv31337 (const char *path, char *const args[], int mode, char *overflow) ; void dprintf (char *fmt, ...); void usage (char *program) { fprintf (stderr, "\nQuick Fuzz Tester: incorrect usage, try\n" "%s [-e env1,env2...] [-c opt1,opt2,...] [-s size] [-ifv] where\n" "-e specifies a list of environment variables and\n" "-c specifies a list of command line parameters delimited by commas, \n" "-s specifies a new default buffer size (default to 4096,\n" "-v throws QFT into verbose mode.\n" "-i attempts an intelligent fuzz test by parsing an ELF executable.\ n" "-f can be used to do a sloppy check for a format bug.\n" "The program should specify the path to the binary with optional arg uments.\n" "Note: The program can only test the -e, -c, and -i options independ ently.\n\n" "Ex: \"%s -e HOME,DISPLAY /usr/X11R6/bin/wmaker\"\n" "Ex: \"%s -c '-c,-o' /bin/sh -i\"\n", "-- You must specify the FULL path of the file to execute! --\n\n", program, program); exit (-1); } extern char **environ; int main (int argc, char **argv) { char *buf, *point, *optstring; int opt, letter; if (argc < 2) usage (argv[0]); while ((opt = getopt (argc, argv, "c:e:fis:v")) != EOF) switch (opt) { case 'c': if (progmode != MODE_NORMAL) usage (argv[0]); progmode = MODE_USEROPT; usercmd = strdup (optarg); break; case 'e': if (progmode != MODE_NORMAL) usage (argv[0]); progmode = MODE_USERENV; userenv = strdup (optarg); break; case 'f': formatbug = 1; break; case 'i': if (progmode != MODE_NORMAL) usage (argv[0]); progmode = MODE_INTELLIGENT; break; case 's': if (!(bufsize = atoi (optarg))) { fprintf (stderr, "Invalid buffer size argument specified! Exiting .\n"); exit (-1); } break; case 'v': verbose = 1; break; default: usage (argv[0]); break; } if (optind >= argc) usage (argv[0]); if (formatbug) bufsize = strlen (FORMAT_BUG); strncpy (xargv0, argv[optind], sizeof (xargv0)); xargv = duplicatestrarray (&argv[optind]); if (access (xargv0, X_OK) == -1) { fprintf (stderr, "Unable to access target file %s for execution! Exiting. \n", xargv0); exit (-1); } signal (SIGALRM, alarmclock); printf ("Launching fuzz tester on \"%s\" ...\n", xargv0); printf ("Using an overflow size of %d bytes.\n", bufsize); #define PARAM_SIZE (formatbug ? strlen (FORMAT_BUG) : bufsize) if (progmode == MODE_INTELLIGENT) { if ((elf_fd = open (xargv0, O_RDONLY)) == -1) { fprintf (stderr, "Unable to open ELF executable! Exiting.\n"); exit (-1); } if (parse_elf (elf_fd) == -1) { fprintf (stderr, "Read invalid ELF header! Exiting.\n"); exit (-1); } } if (progmode == MODE_NORMAL) { dprintf ("Finding environment variables...\n"); while (*(environ + numsims)) { if ((point = strchr (*(environ + numsims), '=')) != NULL) *point = 0x0; execv31337 (xargv0, xargv, OPT_ENV, *(environ + numsims)); numsims++; } /* Just enough room for a hyphen, letter, and NULL :) */ buf = (char *) malloc (3); for (letter = 'A'; letter < ('z' + 1); letter++) { if (isalpha (letter)) { sprintf (buf, "-%c", letter); execv31337 (xargv0, xargv, OPT_CMDLINE, buf); } } free (buf); } else if ((progmode == MODE_USERENV) || (progmode == MODE_USEROPT)) { optstring = (progmode == MODE_USERENV) ? userenv : usercmd; progmode = ((progmode == MODE_USERENV) ? OPT_ENV : OPT_CMDLINE); if ((point = strtok (optstring, ",")) == NULL) { printf ("Invalid user string! Exiting\n"); exit (-1); } execv31337 (xargv0, xargv, progmode, point); while ((point = strtok (NULL, ",")) != NULL) execv31337 (xargv0, xargv, progmode, point); } else if (progmode == MODE_INTELLIGENT) { } } /* Check to see if the specified binary specified in "intelligent mode" is in fact an ELF executable which we can parse for information */ int parse_elf (int fd) { Elf32_Ehdr eheader; Elf32_Shdr *sheader; char *buffer, *point; int i, size, bufsize; /* First we read in the main ELF header, and check to see that it is valid, and contains the target type of ELF file we wish to examine */ if (read (fd, &eheader, HSIZE) != HSIZE) return (-1); if (strncmp (eheader.e_ident, ELFMAG, SELFMAG) != 0) return (-1); if ((eheader.e_type != ET_EXEC) || (!(eheader.e_version)) || (eheader.e_ident[EI_CLASS] != ELFCLASS32)) return (-1); /* The ELF header is ok. Now we read in an array of section headers, and rifle through the names of each using the string header index until we find .rodat a */ size = (eheader.e_shnum * SSIZE); sheader = (Elf32_Shdr *) malloc (size); if (lseek (fd, eheader.e_shoff, SEEK_SET) == -1) return (-1); if (read (fd, sheader, size) != size) return (-1); bufsize = sheader[eheader.e_shstrndx].sh_size; buffer = calloc (bufsize, 1); if (lseek (fd, sheader[eheader.e_shstrndx].sh_offset, SEEK_SET) == -1) return (-1); if (read (fd, buffer, bufsize) != bufsize) return (-1); for (i = 0; (i < eheader.e_shnum); i++) if (strcmp ((buffer + sheader[i].sh_name), DATASECTION) == 0) { lseek (fd, sheader[i].sh_offset, SEEK_SET); realloc (buffer, sheader[i].sh_size); read (fd, buffer, sheader[i].sh_size); point = buffer; while (point < (buffer + sheader[i].sh_size)) { if (*point == 0) { point++; if (isenvvariable (point) == 0) execv31337 (xargv0, xargv, OPT_ENV, point); if (isoptstring (point) == 0) auditoptstring (point); } point++; } free (buffer); free (sheader); return 0; } free (buffer); return (-1); } /* Trap the SIGALRM and KILL!!!!!*/ void alarmclock () { dprintf ("Process execution timed out... killing PID!\n"); kill (childproc, SIGKILL); } /* Our code to grab optstrings from the ELF header is rather ghetto, as it * sacrifices accuracy for cross-portability. This is a funcion that is * designed to lex a potential optstring to determine if it is THE ONE. * We match strings with only letters, followed by only 1 or 2 colons each; * optstrings MUST contain a ':' to be overflowable by optarg, anyhow */ int isoptstring (char *optstring) { char lower = 'A', upper = 'z'; int i, curr; if ((strlen (optstring) < 2) || (strchr (optstring, ':') == NULL)) return (-1); for (i = 0; (i < strlen (optstring)); i++) { curr = *(optstring + i); if ((!(isalpha (curr))) && (curr != ':')) return (-1); } if (strstr (optstring, ":::") != NULL) return (-1); for (i = lower; (i < upper); i++) if ((strchr (optstring, i)) != (strrchr (optstring, i))) return (-1); return 0; } /* Written for the same reason as the function above; this guy just tests to se e * if the string qualifies as a valid environment variable name. * We match only uppercase letters and underscores */ int isenvvariable (char *envvariable) { int i, curr; if (!strlen (envvariable)) return (-1); for (i = 0; (i < strlen (envvariable)); i++) { curr = *(envvariable + i); if ((!(isupper (curr))) && (curr != '_') && (!isdigit(curr))) return (-1); } return 0; } /* Find the parameters in an optstring which take parameters (optarg), and pass them onto the execv31337 () function */ void auditoptstring (char *optstring) { char *curropt, *option; option = (char *) (calloc (2, 0)); for (curropt = optstring; curropt < optstring + strlen (optstring); curropt++ ) if ((*(curropt + 1) == ':') && (isalpha (*curropt))) { *option = *curropt; execv31337 (xargv0, xargv, OPT_CMDLINE, option); } free (option); } /* Duplicate an array of strings with room for TWO extra values - one for * the command line parameter name, and one for the parameter, and return * the new array for manipulation */ char ** duplicatestrarray (char **src) { char **point; static char **dest; int i, tot = 1; point = src; while (*point) { tot++; point++; } dest = (char **) (calloc ((tot + 2), sizeof (char *))); for (i = 0; i < tot; i++) *(dest + i) = *(src + i); return dest; } /* Return a pointer to the 2nd from last non-NULL argument to inject a malicious command line parameter */ char ** getarrayptr (char **target) { char **point; point = target; while (*point) point++; return point; } /* Simply a handy little wrapper that allows us to take user input (an * executable and relevant parameters) of variable length, and pass them on * to an execv() function */ int execv31337 (const char *path, char *const argv[], int mode, char *overflow) { char **cmdopt, *buffer; int childstatus; /* Give the process a bit of time to breathe, if it doesn't return then we * have to assume it ran correctly and send it a SIGKILL to continue the * fuzz testing */ alarm (TIMEOUT); if (!(childproc = fork())) { signal (SIGSEGV, SIG_DFL); buffer = (char *) malloc (bufsize); if (!formatbug) { memset (buffer, FILLCHAR, bufsize); *(buffer + bufsize) = 0x0; } else strcpy (buffer, FORMAT_BUG); if (mode == OPT_ENV) { setenv (overflow, buffer, 1); execv (xargv0, xargv); } else { cmdopt = getarrayptr (xargv); *cmdopt = overflow; *(cmdopt + 1) = buffer; *(cmdopt + 2) = 0x0; execv (xargv0, xargv); } } else if (childproc) { printf ("Launched executable target %s with ID %d (overflowing ", xargv0, childproc); printf ("%s %s with %d bytes)\n", ((mode == OPT_ENV) ? "environment variable" : "parameter"), overflow, bufsize); waitpid (childproc, &childstatus, 0); dprintf ("Executed process returned.\n"); if (WIFSIGNALED (childstatus)) if (WTERMSIG (childstatus) == SIGSEGV) printf ("Process exited with SIGSEGV! An overflow may exist.\n\n "); } return 0; } /* Just a printf wrapper.... prints only if verbose mode is on */ void dprintf (char *fmt, ...) { va_list ap; va_start (ap, fmt); if (verbose) vprintf (fmt, ap); va_end (ap); }