545 lines
16 KiB
C
545 lines
16 KiB
C
/*
|
|
* zzuf - general purpose fuzzer
|
|
* Copyright (c) 2002-2010 Sam Hocevar <sam@hocevar.net>
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software. It comes without any warranty, to
|
|
* the extent permitted by applicable law. You can redistribute it
|
|
* and/or modify it under the terms of the Do What The Fuck You Want
|
|
* To Public License, Version 2, as published by Sam Hocevar. See
|
|
* http://sam.zoy.org/wtfpl/COPYING for more details.
|
|
*/
|
|
|
|
/*
|
|
* myfork.c: launcher
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#define _INCLUDE_POSIX_SOURCE /* for STDERR_FILENO on HP-UX */
|
|
|
|
#if defined HAVE_STDINT_H
|
|
# include <stdint.h>
|
|
#elif defined HAVE_INTTYPES_H
|
|
# include <inttypes.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#if defined HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#if defined HAVE_WINDOWS_H
|
|
# include <windows.h>
|
|
# include <imagehlp.h>
|
|
# include <tlhelp32.h>
|
|
#endif
|
|
#if defined HAVE_IO_H
|
|
# include <io.h>
|
|
#endif
|
|
#include <string.h>
|
|
#include <fcntl.h> /* for O_BINARY */
|
|
#if defined HAVE_SYS_RESOURCE_H
|
|
# include <sys/resource.h> /* for RLIMIT_AS */
|
|
#endif
|
|
|
|
#include "common.h"
|
|
#include "opts.h"
|
|
#include "random.h"
|
|
#include "fd.h"
|
|
#include "fuzz.h"
|
|
#include "myfork.h"
|
|
#include "md5.h"
|
|
#include "timer.h"
|
|
|
|
/* Handle old libtool versions */
|
|
#if !defined LT_OBJDIR
|
|
# define LT_OBJDIR ".libs/"
|
|
#endif
|
|
|
|
#if defined RLIMIT_AS
|
|
# define ZZUF_RLIMIT_MEM RLIMIT_AS
|
|
#elif defined RLIMIT_VMEM
|
|
# define ZZUF_RLIMIT_MEM RLIMIT_VMEM
|
|
#elif defined RLIMIT_DATA
|
|
# define ZZUF_RLIMIT_MEM RLIMIT_DATA
|
|
#else
|
|
# undef ZZUF_RLIMIT_MEM
|
|
#endif
|
|
|
|
#if defined RLIMIT_CPU
|
|
# define ZZUF_RLIMIT_CPU RLIMIT_CPU
|
|
#else
|
|
# undef ZZUF_RLIMIT_CPU
|
|
#endif
|
|
|
|
static int run_process(struct child *child, struct opts *, int[][2]);
|
|
|
|
#if defined HAVE_WINDOWS_H
|
|
static void rep32(uint8_t *buf, void *addr);
|
|
static int dll_inject(PROCESS_INFORMATION *, char const *);
|
|
static intptr_t get_proc_address(void *, DWORD, char const *);
|
|
#endif
|
|
|
|
int myfork(struct child *child, struct opts *opts)
|
|
{
|
|
int pipes[3][2];
|
|
pid_t pid;
|
|
int i;
|
|
|
|
/* Prepare communication pipe */
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
int ret;
|
|
#if defined HAVE_PIPE
|
|
ret = pipe(pipes[i]);
|
|
#elif defined HAVE__PIPE
|
|
int tmp;
|
|
/* The pipe is created with NOINHERIT otherwise both parts are
|
|
* inherited. We then duplicate the part we want. */
|
|
ret = _pipe(pipes[i], 512, _O_BINARY | O_NOINHERIT);
|
|
tmp = _dup(pipes[i][1]);
|
|
close(pipes[i][1]);
|
|
pipes[i][1] = tmp;
|
|
#endif
|
|
if(ret < 0)
|
|
{
|
|
perror("pipe");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
pid = run_process(child, opts, pipes);
|
|
if(pid < 0)
|
|
{
|
|
/* FIXME: close pipes */
|
|
fprintf(stderr, "error launching `%s'\n", child->newargv[0]);
|
|
return -1;
|
|
}
|
|
|
|
child->pid = pid;
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
close(pipes[i][1]);
|
|
child->fd[i] = pipes[i][0];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if !defined HAVE_SETENV
|
|
static void setenv(char const *name, char const *value, int overwrite)
|
|
{
|
|
char *str;
|
|
|
|
if(!overwrite && getenv(name))
|
|
return;
|
|
|
|
str = malloc(strlen(name) + 1 + strlen(value) + 1);
|
|
sprintf(str, "%s=%s", name, value);
|
|
putenv(str);
|
|
}
|
|
#endif
|
|
|
|
static int run_process(struct child *child, struct opts *opts, int pipes[][2])
|
|
{
|
|
char buf[64];
|
|
#if defined HAVE_FORK
|
|
static int const files[] = { DEBUG_FILENO, STDERR_FILENO, STDOUT_FILENO };
|
|
char *libpath, *tmp;
|
|
int pid, j, len = strlen(opts->oldargv[0]);
|
|
# if defined __APPLE__
|
|
# define EXTRAINFO ""
|
|
# define PRELOAD "DYLD_INSERT_LIBRARIES"
|
|
setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1);
|
|
# elif defined __osf__
|
|
# define EXTRAINFO ":DEFAULT"
|
|
# define PRELOAD "_RLD_LIST"
|
|
# elif defined __sun && defined __i386
|
|
# define EXTRAINFO ""
|
|
# define PRELOAD "LD_PRELOAD_32"
|
|
# else
|
|
# define EXTRAINFO ""
|
|
# define PRELOAD "LD_PRELOAD"
|
|
# endif
|
|
#elif HAVE_WINDOWS_H
|
|
PROCESS_INFORMATION pinfo;
|
|
STARTUPINFO sinfo;
|
|
HANDLE pid;
|
|
char *cmdline;
|
|
int i, ret, len;
|
|
#endif
|
|
|
|
#if defined HAVE_FORK
|
|
/* Fork and launch child */
|
|
pid = fork();
|
|
if(pid < 0)
|
|
perror("fork");
|
|
if(pid != 0)
|
|
return pid;
|
|
|
|
/* We loop in reverse order so that files[0] is done last,
|
|
* just in case one of the other dup2()ed fds had the value */
|
|
for(j = 3; j--; )
|
|
{
|
|
close(pipes[j][0]);
|
|
if(pipes[j][1] != files[j])
|
|
{
|
|
dup2(pipes[j][1], files[j]);
|
|
close(pipes[j][1]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
|
|
if(opts->maxmem >= 0)
|
|
{
|
|
struct rlimit rlim;
|
|
rlim.rlim_cur = opts->maxmem * 1048576;
|
|
rlim.rlim_max = opts->maxmem * 1048576;
|
|
setrlimit(ZZUF_RLIMIT_MEM, &rlim);
|
|
}
|
|
#endif
|
|
|
|
#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU
|
|
if(opts->maxcpu >= 0)
|
|
{
|
|
struct rlimit rlim;
|
|
rlim.rlim_cur = opts->maxcpu;
|
|
rlim.rlim_max = opts->maxcpu + 5;
|
|
setrlimit(ZZUF_RLIMIT_CPU, &rlim);
|
|
}
|
|
#endif
|
|
|
|
/* Set environment variables */
|
|
#if defined _WIN32
|
|
sprintf(buf, "%i", _get_osfhandle(pipes[0][1]));
|
|
#else
|
|
sprintf(buf, "%i", pipes[0][1]);
|
|
#endif
|
|
setenv("ZZUF_DEBUGFD", buf, 1);
|
|
sprintf(buf, "%i", opts->seed);
|
|
setenv("ZZUF_SEED", buf, 1);
|
|
sprintf(buf, "%g", opts->minratio);
|
|
setenv("ZZUF_MINRATIO", buf, 1);
|
|
sprintf(buf, "%g", opts->maxratio);
|
|
setenv("ZZUF_MAXRATIO", buf, 1);
|
|
|
|
#if defined HAVE_FORK
|
|
/* Make sure there is space for everything we might do. */
|
|
libpath = malloc(len + strlen(LIBDIR "/" LT_OBJDIR SONAME EXTRAINFO) + 1);
|
|
strcpy(libpath, opts->oldargv[0]);
|
|
|
|
/* If the binary name contains a '/', we look for a libzzuf in the
|
|
* same directory. Otherwise, we only look into the system directory
|
|
* to avoid shared library attacks. Write the result in libpath. */
|
|
tmp = strrchr(libpath, '/');
|
|
if(tmp)
|
|
{
|
|
strcpy(tmp + 1, LT_OBJDIR SONAME);
|
|
if(access(libpath, R_OK) < 0)
|
|
strcpy(libpath, LIBDIR "/" SONAME);
|
|
}
|
|
else
|
|
strcpy(libpath, LIBDIR "/" SONAME);
|
|
|
|
/* OSF1 only */
|
|
strcat(libpath, EXTRAINFO);
|
|
|
|
/* Do not clobber previous LD_PRELOAD values */
|
|
tmp = getenv(PRELOAD);
|
|
if(tmp && *tmp)
|
|
{
|
|
char *bigbuf = malloc(strlen(tmp) + strlen(libpath) + 2);
|
|
sprintf(bigbuf, "%s:%s", tmp, libpath);
|
|
free(libpath);
|
|
libpath = bigbuf;
|
|
}
|
|
|
|
/* Only preload the library in preload mode */
|
|
if (opts->opmode == OPMODE_PRELOAD)
|
|
setenv(PRELOAD, libpath, 1);
|
|
free(libpath);
|
|
|
|
if(execvp(child->newargv[0], child->newargv))
|
|
{
|
|
perror(child->newargv[0]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
exit(EXIT_SUCCESS);
|
|
/* no return */
|
|
return 0;
|
|
#elif HAVE_WINDOWS_H
|
|
pid = GetCurrentProcess();
|
|
|
|
/* Inherit standard handles */
|
|
memset(&sinfo, 0, sizeof(sinfo));
|
|
sinfo.cb = sizeof(sinfo);
|
|
sinfo.hStdInput = INVALID_HANDLE_VALUE;
|
|
sinfo.hStdOutput = (HANDLE)_get_osfhandle(pipes[2][1]);
|
|
sinfo.hStdError = (HANDLE)_get_osfhandle(pipes[1][1]);
|
|
sinfo.dwFlags = STARTF_USESTDHANDLES;
|
|
|
|
/* Build the commandline */
|
|
for (i = 0, len = 0; child->newargv[i]; i++)
|
|
len += strlen(child->newargv[i]) + 1;
|
|
cmdline = malloc(len);
|
|
for (i = 0, len = 0; child->newargv[i]; i++)
|
|
{
|
|
strcpy(cmdline + len, child->newargv[i]);
|
|
len += strlen(child->newargv[i]) + 1;
|
|
cmdline[len - 1] = ' ';
|
|
}
|
|
cmdline[len - 1] = '\0';
|
|
|
|
/* Create the process in suspended state */
|
|
ret = CreateProcess(child->newargv[0], cmdline, NULL, NULL, TRUE,
|
|
CREATE_SUSPENDED, NULL, NULL, &sinfo, &pinfo);
|
|
free(cmdline);
|
|
|
|
if (!ret)
|
|
return -1;
|
|
|
|
/* Insert the replacement code */
|
|
ret = dll_inject(&pinfo, SONAME);
|
|
if(ret < 0)
|
|
{
|
|
TerminateProcess(pinfo.hProcess, -1);
|
|
return -1;
|
|
}
|
|
|
|
ret = ResumeThread(pinfo.hThread);
|
|
if(ret < 0)
|
|
{
|
|
TerminateProcess(pinfo.hProcess, -1);
|
|
return -1;
|
|
}
|
|
|
|
return (long int)pinfo.hProcess;
|
|
#endif
|
|
}
|
|
|
|
#if defined HAVE_WINDOWS_H
|
|
static void rep32(uint8_t *buf, void *addr)
|
|
{
|
|
while(buf++)
|
|
if (memcmp(buf, "____", 4) == 0)
|
|
{
|
|
memcpy(buf, &addr, 4);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int dll_inject(PROCESS_INFORMATION *pinfo, char const *lib)
|
|
{
|
|
static uint8_t const loader[] =
|
|
/* Load the injected DLL into memory */
|
|
"\xb8____" /* mov %eax, <library_name_address> */
|
|
"\x50" /* push %eax */
|
|
"\xb8____" /* mov %eax, <LoadLibraryA> */
|
|
"\xff\xd0" /* call %eax */
|
|
/* Restore the clobbered entry point code using our backup */
|
|
"\xb8\0\0\0\0" /* mov %eax,0 */
|
|
"\x50" /* push %eax */
|
|
"\xb8____" /* mov %eax, <jumper_length> */
|
|
"\x50" /* push %eax */
|
|
"\xb8____" /* mov %eax, <backuped_entry_point_address> */
|
|
"\x50" /* push %eax */
|
|
"\xb8____" /* mov %eax, <original_entry_point_address> */
|
|
"\x50" /* push %eax */
|
|
"\xb8____" /* mov %eax, <GetCurrentProcess> */
|
|
"\xff\xd0" /* call %eax */
|
|
"\x50" /* push %eax */
|
|
"\xb8____" /* mov %eax, <WriteProcessMemory> */
|
|
"\xff\xd0" /* call %eax */
|
|
/* Jump to the original entry point */
|
|
"\xb8____" /* mov %eax, <original_entry_point_address> */
|
|
"\xff\xe0"; /* jmp %eax */
|
|
|
|
static uint8_t const waiter[] =
|
|
"\xeb\xfe"; /* jmp <current> */
|
|
|
|
static uint8_t const jumper[] =
|
|
/* Jump to the injected loader */
|
|
"\xb8____" /* mov eax, <loader_address> */
|
|
"\xff\xe0"; /* jmp eax */
|
|
|
|
CONTEXT ctx;
|
|
void *process = pinfo->hProcess;
|
|
void *thread = pinfo->hThread;
|
|
void *epaddr;
|
|
DWORD pid = pinfo->dwProcessId;
|
|
|
|
/* code:
|
|
* +---------------+--------------------+--------------+-------------+
|
|
* | loader | entry point backup | library name | jumper |
|
|
* | len(loader) | len(jumper) | len(lib) | len(jumper) |
|
|
* +---------------+--------------------+--------------+-------------+ */
|
|
uint8_t code[1024];
|
|
|
|
uint8_t *loaderaddr;
|
|
size_t liblen, loaderlen, waiterlen, jumperlen;
|
|
DWORD tmp;
|
|
|
|
liblen = strlen(lib) + 1;
|
|
loaderlen = sizeof(loader) - 1;
|
|
waiterlen = sizeof(waiter) - 1;
|
|
jumperlen = sizeof(jumper) - 1;
|
|
if (loaderlen + jumperlen + liblen > 1024)
|
|
return -1;
|
|
|
|
/* Allocate memory in the child for our injected code */
|
|
loaderaddr = VirtualAllocEx(process, NULL, loaderlen + jumperlen + liblen,
|
|
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
|
if(!loaderaddr)
|
|
return -1;
|
|
|
|
/* Create the first shellcode (jumper).
|
|
*
|
|
* The jumper's job is simply to jump at the second shellcode's location.
|
|
* It is written at the original entry point's location, which will in
|
|
* turn be restored by the second shellcode.
|
|
*/
|
|
memcpy(code + loaderlen + jumperlen + liblen, jumper, jumperlen);
|
|
rep32(code + loaderlen + jumperlen + liblen, loaderaddr);
|
|
|
|
/* Create the second shellcode (loader, backuped entry point, and library
|
|
* name).
|
|
*
|
|
* The loader's job is to load the library by calling LoadLibraryA(),
|
|
* restore the original entry point using the backup copy, and jump
|
|
* back to the original entry point as if the process had just started.
|
|
*
|
|
* The second shellcode is written at a freshly allocated memory location.
|
|
*/
|
|
memcpy(code, loader, loaderlen);
|
|
memcpy(code + loaderlen + jumperlen, lib, liblen);
|
|
|
|
/* Find the entry point address. It's simply in EAX. */
|
|
ctx.ContextFlags = CONTEXT_FULL;
|
|
GetThreadContext(thread, &ctx);
|
|
epaddr = (void *)(uintptr_t)ctx.Eax;
|
|
|
|
/* Backup the old entry point code */
|
|
ReadProcessMemory(process, epaddr, code + loaderlen, jumperlen, &tmp);
|
|
if(tmp != jumperlen)
|
|
return -1;
|
|
|
|
/* Replace the entry point code with a short jump to self, then resume
|
|
* the thread. This is necessary for CreateToolhelp32Snapshot() to
|
|
* work. */
|
|
WriteProcessMemory(process, epaddr, waiter, waiterlen, &tmp);
|
|
if(tmp != waiterlen)
|
|
return -1;
|
|
FlushInstructionCache(process, epaddr, waiterlen);
|
|
ResumeThread(thread);
|
|
|
|
/* Wait until the entry point is reached */
|
|
for (tmp = 0; tmp < 100; tmp++)
|
|
{
|
|
CONTEXT ctx;
|
|
ctx.ContextFlags = CONTEXT_FULL;
|
|
GetThreadContext(thread, &ctx);
|
|
if ((uintptr_t)ctx.Eip == (uintptr_t)epaddr)
|
|
break;
|
|
Sleep(10);
|
|
}
|
|
SuspendThread(thread);
|
|
if (tmp == 100)
|
|
return -1;
|
|
|
|
/* Remotely parse the target process's module list to get the addresses
|
|
* of the functions we need. This can only be done because we advanced
|
|
* the target's execution to the entry point. */
|
|
rep32(code, loaderaddr + loaderlen + jumperlen);
|
|
rep32(code, (void *)get_proc_address(process, pid, "LoadLibraryA"));
|
|
rep32(code, (void *)(uintptr_t)jumperlen);
|
|
rep32(code, loaderaddr + loaderlen);
|
|
rep32(code, epaddr);
|
|
rep32(code, (void *)get_proc_address(process, pid, "GetCurrentProcess"));
|
|
rep32(code, (void *)get_proc_address(process, pid, "WriteProcessMemory"));
|
|
rep32(code, epaddr);
|
|
|
|
/* Write our shellcodes into the target process */
|
|
WriteProcessMemory(process, epaddr, code + loaderlen + jumperlen + liblen,
|
|
jumperlen, &tmp);
|
|
if(tmp != jumperlen)
|
|
return -1;
|
|
FlushInstructionCache(process, epaddr, waiterlen);
|
|
|
|
WriteProcessMemory(process, loaderaddr, code,
|
|
loaderlen + jumperlen + liblen, &tmp);
|
|
if(tmp != loaderlen + jumperlen + liblen)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static intptr_t get_proc_address(void *process, DWORD pid, const char *func)
|
|
{
|
|
char buf[1024];
|
|
size_t buflen = strlen(func) + 1;
|
|
|
|
MODULEENTRY32 entry;
|
|
intptr_t ret = 0;
|
|
DWORD tmp;
|
|
void *list;
|
|
int i, k;
|
|
|
|
list = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
|
|
entry.dwSize = sizeof(entry);
|
|
for(k = Module32First(list, &entry); k; k = Module32Next(list, &entry))
|
|
{
|
|
IMAGE_DOS_HEADER dos;
|
|
IMAGE_NT_HEADERS nt;
|
|
IMAGE_EXPORT_DIRECTORY expdir;
|
|
|
|
uint32_t exportaddr;
|
|
uint8_t const *base = entry.modBaseAddr;
|
|
|
|
if (strcmp("kernel32.dll", entry.szModule))
|
|
continue;
|
|
|
|
ReadProcessMemory(process, base, &dos, sizeof(dos), &tmp);
|
|
ReadProcessMemory(process, base + dos.e_lfanew, &nt, sizeof(nt), &tmp);
|
|
|
|
exportaddr = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
|
if (!exportaddr)
|
|
continue;
|
|
|
|
ReadProcessMemory(process, base + exportaddr, &expdir, sizeof(expdir), &tmp);
|
|
|
|
for (i = 0; i < (int)expdir.NumberOfNames; i++)
|
|
{
|
|
uint32_t nameaddr, funcaddr;
|
|
uint16_t j;
|
|
|
|
/* Look for our function name in the list of names */
|
|
ReadProcessMemory(process, base + expdir.AddressOfNames
|
|
+ i * sizeof(DWORD),
|
|
&nameaddr, sizeof(nameaddr), &tmp);
|
|
ReadProcessMemory(process, base + nameaddr, buf, buflen, &tmp);
|
|
|
|
if (strcmp(buf, func))
|
|
continue;
|
|
|
|
/* If we found a function with this name, return its address */
|
|
ReadProcessMemory(process, base + expdir.AddressOfNameOrdinals
|
|
+ i * sizeof(WORD),
|
|
&j, sizeof(j), &tmp);
|
|
ReadProcessMemory(process, base + expdir.AddressOfFunctions
|
|
+ j * sizeof(DWORD),
|
|
&funcaddr, sizeof(funcaddr), &tmp);
|
|
|
|
ret = (intptr_t)base + funcaddr;
|
|
goto _finished;
|
|
}
|
|
}
|
|
|
|
_finished:
|
|
CloseHandle(list);
|
|
return ret;
|
|
}
|
|
|
|
#endif
|