source: zzuf/trunk/src/myfork.c @ 4393

Last change on this file since 4393 was 4393, checked in by Sam Hocevar, 10 years ago

New operating mode "copy". It uses temporary files instead of preloading
libzzuf into the process.

  • Property svn:keywords set to Id
File size: 14.2 KB
Line 
1/*
2 *  zzuf - general purpose fuzzer
3 *  Copyright (c) 2002-2010 Sam Hocevar <sam@hocevar.net>
4 *                All Rights Reserved
5 *
6 *  This program is free software. It comes without any warranty, to
7 *  the extent permitted by applicable law. You can redistribute it
8 *  and/or modify it under the terms of the Do What The Fuck You Want
9 *  To Public License, Version 2, as published by Sam Hocevar. See
10 *  http://sam.zoy.org/wtfpl/COPYING for more details.
11 */
12
13/*
14 *  myfork.c: launcher
15 */
16
17#include "config.h"
18
19#define _INCLUDE_POSIX_SOURCE /* for STDERR_FILENO on HP-UX */
20
21#if defined HAVE_STDINT_H
22#   include <stdint.h>
23#elif defined HAVE_INTTYPES_H
24#   include <inttypes.h>
25#endif
26#include <stdio.h>
27#include <stdlib.h>
28#if defined HAVE_UNISTD_H
29#   include <unistd.h>
30#endif
31#if defined HAVE_WINDOWS_H
32#   include <windows.h>
33#   include <imagehlp.h>
34#   include <tlhelp32.h>
35#endif
36#include <string.h>
37#include <fcntl.h> /* for O_BINARY */
38#if defined HAVE_SYS_RESOURCE_H
39#   include <sys/resource.h> /* for RLIMIT_AS */
40#endif
41
42#include "common.h"
43#include "opts.h"
44#include "random.h"
45#include "fd.h"
46#include "fuzz.h"
47#include "myfork.h"
48#include "md5.h"
49#include "timer.h"
50
51/* Handle old libtool versions */
52#if !defined LT_OBJDIR
53#   define LT_OBJDIR ".libs/"
54#endif
55
56#if defined RLIMIT_AS
57#   define ZZUF_RLIMIT_MEM RLIMIT_AS
58#elif defined RLIMIT_VMEM
59#   define ZZUF_RLIMIT_MEM RLIMIT_VMEM
60#elif defined RLIMIT_DATA
61#   define ZZUF_RLIMIT_MEM RLIMIT_DATA
62#else
63#   undef ZZUF_RLIMIT_MEM
64#endif
65
66#if defined RLIMIT_CPU
67#   define ZZUF_RLIMIT_CPU RLIMIT_CPU
68#else
69#   undef ZZUF_RLIMIT_CPU
70#endif
71
72static int run_process(struct child *child, struct opts *, int[][2]);
73
74#if defined HAVE_WINDOWS_H
75static void rep32(uint8_t *buf, void *addr);
76static int dll_inject(void *, void *, char const *);
77static intptr_t get_base_address(DWORD);
78static intptr_t get_entry_point_offset(char const *);
79#endif
80
81int myfork(struct child *child, struct opts *opts)
82{
83    int pipes[3][2];
84    pid_t pid;
85    int i;
86
87    /* Prepare communication pipe */
88    for(i = 0; i < 3; i++)
89    {
90        int ret;
91#if defined HAVE_PIPE
92        ret = pipe(pipes[i]);
93#elif defined HAVE__PIPE
94        ret = _pipe(pipes[i], 512, _O_BINARY | O_NOINHERIT);
95#endif
96        if(ret < 0)
97        {
98            perror("pipe");
99            return -1;
100        }
101    }
102
103    pid = run_process(child, opts, pipes);
104    if(pid < 0)
105    {
106        /* FIXME: close pipes */
107        fprintf(stderr, "error launching `%s'\n", child->newargv[0]);
108        return -1;
109    }
110
111    child->pid = pid;
112    for(i = 0; i < 3; i++)
113    {
114        close(pipes[i][1]);
115        child->fd[i] = pipes[i][0];
116    }
117
118    return 0;
119}
120
121#if !defined HAVE_SETENV
122static void setenv(char const *name, char const *value, int overwrite)
123{
124    char *str;
125
126    if(!overwrite && getenv(name))
127        return;
128
129    str = malloc(strlen(name) + 1 + strlen(value) + 1);
130    sprintf(str, "%s=%s", name, value);
131    putenv(str);
132}
133#endif
134
135static int run_process(struct child *child, struct opts *opts, int pipes[][2])
136{
137    char buf[64];
138#if defined HAVE_FORK
139    static int const files[] = { DEBUG_FILENO, STDERR_FILENO, STDOUT_FILENO };
140    char *libpath, *tmp;
141    int pid, j, len = strlen(opts->oldargv[0]);
142#   if defined __APPLE__
143#       define EXTRAINFO ""
144#       define PRELOAD "DYLD_INSERT_LIBRARIES"
145    setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1);
146#   elif defined __osf__
147#       define EXTRAINFO ":DEFAULT"
148#       define PRELOAD "_RLD_LIST"
149#   elif defined __sun && defined __i386
150#       define EXTRAINFO ""
151#       define PRELOAD "LD_PRELOAD_32"
152#   else
153#       define EXTRAINFO ""
154#       define PRELOAD "LD_PRELOAD"
155#   endif
156#elif HAVE_WINDOWS_H
157    PROCESS_INFORMATION pinfo;
158    STARTUPINFO sinfo;
159    HANDLE pid;
160    void *epaddr;
161    int ret;
162#endif
163
164#if defined HAVE_FORK
165    /* Fork and launch child */
166    pid = fork();
167    if(pid < 0)
168        perror("fork");
169    if(pid != 0)
170        return pid;
171
172    /* We loop in reverse order so that files[0] is done last,
173     * just in case one of the other dup2()ed fds had the value */
174    for(j = 3; j--; )
175    {
176        close(pipes[j][0]);
177        if(pipes[j][1] != files[j])
178        {
179            dup2(pipes[j][1], files[j]);
180            close(pipes[j][1]);
181        }
182    }
183#endif
184
185#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
186    if(opts->maxmem >= 0)
187    {
188        struct rlimit rlim;
189        rlim.rlim_cur = opts->maxmem * 1048576;
190        rlim.rlim_max = opts->maxmem * 1048576;
191        setrlimit(ZZUF_RLIMIT_MEM, &rlim);
192    }
193#endif
194
195#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU
196    if(opts->maxcpu >= 0)
197    {
198        struct rlimit rlim;
199        rlim.rlim_cur = opts->maxcpu;
200        rlim.rlim_max = opts->maxcpu + 5;
201        setrlimit(ZZUF_RLIMIT_CPU, &rlim);
202    }
203#endif
204
205    /* Set environment variables */
206    sprintf(buf, "%i", opts->seed);
207    setenv("ZZUF_SEED", buf, 1);
208    sprintf(buf, "%g", opts->minratio);
209    setenv("ZZUF_MINRATIO", buf, 1);
210    sprintf(buf, "%g", opts->maxratio);
211    setenv("ZZUF_MAXRATIO", buf, 1);
212
213#if defined HAVE_FORK
214    /* Make sure there is space for everything we might do. */
215    libpath = malloc(len + strlen(LIBDIR "/" LT_OBJDIR SONAME EXTRAINFO) + 1);
216    strcpy(libpath, opts->oldargv[0]);
217
218    /* If the binary name contains a '/', we look for a libzzuf in the
219     * same directory. Otherwise, we only look into the system directory
220     * to avoid shared library attacks. Write the result in libpath. */
221    tmp = strrchr(libpath, '/');
222    if(tmp)
223    {
224        strcpy(tmp + 1, LT_OBJDIR SONAME);
225        if(access(libpath, R_OK) < 0)
226            strcpy(libpath, LIBDIR "/" SONAME);
227    }
228    else
229        strcpy(libpath, LIBDIR "/" SONAME);
230
231    /* OSF1 only */
232    strcat(libpath, EXTRAINFO);
233
234    /* Do not clobber previous LD_PRELOAD values */
235    tmp = getenv(PRELOAD);
236    if(tmp && *tmp)
237    {
238        char *bigbuf = malloc(strlen(tmp) + strlen(libpath) + 2);
239        sprintf(bigbuf, "%s:%s", tmp, libpath);
240        free(libpath);
241        libpath = bigbuf;
242    }
243
244    /* Only preload the library in preload mode */
245    if (opts->opmode == OPMODE_PRELOAD)
246        setenv(PRELOAD, libpath, 1);
247    free(libpath);
248
249    if(execvp(child->newargv[0], child->newargv))
250    {
251        perror(child->newargv[0]);
252        exit(EXIT_FAILURE);
253    }
254
255    exit(EXIT_SUCCESS);
256    /* no return */
257    return 0;
258#elif HAVE_WINDOWS_H
259    pid = GetCurrentProcess();
260
261    memset(&sinfo, 0, sizeof(sinfo));
262    sinfo.cb = sizeof(sinfo);
263    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[0][1]), pid,
264        /* FIXME */ &sinfo.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
265    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[1][1]), pid,
266                    &sinfo.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS);
267    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[2][1]), pid,
268                    &sinfo.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
269    sinfo.dwFlags = STARTF_USESTDHANDLES;
270    ret = CreateProcess(NULL, child->newargv[0], NULL, NULL, FALSE,
271                        CREATE_SUSPENDED, NULL, NULL, &sinfo, &pinfo);
272    if(!ret)
273        return -1;
274
275    /* Get the child process's entry point address */
276    epaddr = (void *)get_entry_point(child->newargv[0],
277                                     pinfo.dwProcessId);
278    if(!epaddr)
279        return -1;
280
281    /* Insert the replacement code */
282    ret = dll_inject(pinfo.hProcess, epaddr, SONAME);
283    if(ret < 0)
284    {
285        TerminateProcess(pinfo.hProcess, -1);
286        return -1;
287    }
288
289    ret = ResumeThread(pinfo.hThread);
290    if(ret < 0)
291    {
292        TerminateProcess(pinfo.hProcess, -1);
293        return -1;
294    }
295
296    return (long int)pinfo.hProcess;
297#endif
298}
299
300#if defined HAVE_WINDOWS_H
301static void rep32(uint8_t *buf, void *addr)
302{
303    while(buf++)
304        if (memcmp(buf, "____", 4) == 0)
305        {
306            memcpy(buf, &addr, 4);
307            return;
308        }
309}
310
311static int dll_inject(void *process, void *epaddr, char const *lib)
312{
313    static uint8_t const loader[] =
314        /* Load the injected DLL into memory */
315        "\xb8____"       /* mov %eax, <library_name_address> */
316        "\x50"           /* push %eax */
317        "\xb8____"       /* mov %eax, <LoadLibraryA> */
318        "\xff\xd0"       /* call %eax */
319        /* Restore the clobbered entry point code using our backup */
320        "\xb8\0\0\0\0"   /* mov %eax,0 */
321        "\x50"           /* push %eax */
322        "\xb8____"       /* mov %eax, <jumper_length> */
323        "\x50"           /* push %eax */
324        "\xb8____"       /* mov %eax, <backuped_entry_point_address> */
325        "\x50"           /* push %eax */
326        "\xb8____"       /* mov %eax, <original_entry_point_address> */
327        "\x50"           /* push %eax */
328        "\xb8____"       /* mov %eax, <GetCurrentProcess> */
329        "\xff\xd0"       /* call %eax */
330        "\x50"           /* push %eax */
331        "\xb8____"       /* mov %eax, <WriteProcessMemory> */
332        "\xff\xd0"       /* call %eax */
333        /* Jump to the original entry point */
334        "\xb8____"       /* mov %eax, <original_entry_point_address> */
335        "\xff\xe0";      /* jmp %eax */
336
337    static uint8_t const jumper[] =
338        /* Jump to the injected loader */
339        "\xb8____"       /* mov eax, <loader_address> */
340        "\xff\xe0";      /* jmp eax */
341
342    /* code:
343     * +---------------+--------------------+--------------+-------------+
344     * |     loader    | entry point backup | library name |   jumper    |
345     * |  len(loader)  |    len(jumper)     |   len(lib)   | len(jumper) |
346     * +---------------+--------------------+--------------+-------------+ */
347    uint8_t code[1024];
348
349    void *kernel32;
350    uint8_t *loaderaddr;
351    size_t liblen, loaderlen, jumperlen;
352    DWORD tmp;
353
354    liblen = strlen(lib) + 1;
355    loaderlen = sizeof(loader) - 1;
356    jumperlen = sizeof(jumper) - 1;
357    if (loaderlen + jumperlen + liblen > 1024)
358        return -1;
359
360    /* Allocate memory in the child for our injected code */
361    loaderaddr = VirtualAllocEx(process, NULL, loaderlen + jumperlen + liblen,
362                                MEM_COMMIT, PAGE_EXECUTE_READWRITE);
363    if(!loaderaddr)
364        return -1;
365
366    /* Create the first shellcode (jumper).
367     *
368     * The jumper's job is simply to jump at the second shellcode's location.
369     * It is written at the original entry point's location, which will in
370     * turn be restored by the second shellcode.
371     */
372    memcpy(code + loaderlen + jumperlen + liblen, jumper, jumperlen);
373    rep32(code + loaderlen + jumperlen + liblen, loaderaddr);
374
375    /* Create the second shellcode (loader, backuped entry point, and library
376     * name).
377     *
378     * The loader's job is to load the library by calling LoadLibraryA(),
379     * restore the original entry point using the backup copy, and jump
380     * back to the original entry point as if the process had just started.
381     *
382     * The second shellcode is written at a freshly allocated memory location.
383     */
384    memcpy(code, loader, loaderlen);
385    memcpy(code + loaderlen + jumperlen, lib, liblen);
386
387    /* Backup the old entry point code */
388    ReadProcessMemory(process, epaddr, code + loaderlen,
389                      jumperlen, &tmp);
390    if(tmp != jumperlen)
391        return -1;
392
393    /* FIXME: the GetProcAddress calls assume the library was loaded at
394     * the same address in the child process. This is wrong since Vista
395     * and its address space randomisation. */
396    kernel32 = LoadLibrary("kernel32.dll");
397    if(!kernel32)
398        return -1;
399
400    rep32(code, loaderaddr + loaderlen + jumperlen);
401    rep32(code, GetProcAddress(kernel32, "LoadLibraryA"));
402    rep32(code, (void *)(uintptr_t)jumperlen);
403    rep32(code, loaderaddr + loaderlen);
404    rep32(code, epaddr);
405    rep32(code, GetProcAddress(kernel32, "GetCurrentProcess"));
406    rep32(code, GetProcAddress(kernel32, "WriteProcessMemory"));
407    rep32(code, epaddr);
408    FreeLibrary(kernel32);
409
410    /* Write our shellcodes into the target process */
411    WriteProcessMemory(process, epaddr, code + loaderlen + jumperlen + liblen,
412                       jumperlen, &tmp);
413    if(tmp != jumperlen)
414        return -1;
415
416    WriteProcessMemory(process, loaderaddr, code,
417                       loaderlen + jumperlen + liblen, &tmp);
418    if(tmp != loaderlen + jumperlen + liblen)
419        return -1;
420
421    return 0;
422}
423
424/* Find the process's entry point address offset. The information is in
425 * the file's PE header. */
426static intptr_t get_entry_point(char const *name, DWORD pid)
427{
428    PIMAGE_DOS_HEADER dos;
429    PIMAGE_NT_HEADERS nt;
430    intptr_t ret = 0;
431    void *file, *map, *base;
432
433    file = CreateFile(name, GENERIC_READ, FILE_SHARE_READ,
434                      NULL, OPEN_EXISTING, 0, NULL);
435    if(file == INVALID_HANDLE_VALUE)
436        return ret;
437
438    map = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);
439    if(!map)
440    {
441        CloseHandle(file);
442        return ret;
443    }
444
445    base = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
446    if(!base)
447    {
448        CloseHandle(map);
449        CloseHandle(file);
450        return ret;
451    }
452
453    /* Sanity checks */
454    dos = (PIMAGE_DOS_HEADER)base;
455    nt = (PIMAGE_NT_HEADERS)((char *)base + dos->e_lfanew);
456    if(dos->e_magic == IMAGE_DOS_SIGNATURE /* 0x5A4D */
457      && nt->Signature == IMAGE_NT_SIGNATURE /* 0x00004550 */
458      && nt->FileHeader.Machine == IMAGE_FILE_MACHINE_I386
459      && nt->OptionalHeader.Magic == 0x10b /* IMAGE_NT_OPTIONAL_HDR32_MAGIC */)
460    {
461        ret = get_base_address(pid);
462        /* Base address not found in the running process. Falling back
463         * to the header's information, which is unreliable because of
464         * Vista's address space randomisation. */
465        if (!ret)
466            ret = (intptr_t)nt->OptionalHeader.BaseOfCode;
467
468        ret += (intptr_t)nt->OptionalHeader.AddressOfEntryPoint;
469    }
470
471    UnmapViewOfFile(base);
472    CloseHandle(map);
473    CloseHandle(file);
474
475    return ret;
476}
477
478/* Find the process's base address once it is loaded in memory (the header
479 * information is unreliable because of Vista's ASLR). */
480static intptr_t get_base_address(DWORD pid)
481{
482    MODULEENTRY32 entry;
483    intptr_t ret = 0;
484    void *list;
485    int k;
486
487    list = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
488    entry.dwSize = sizeof(entry);
489    for(k = Module32First(list, &entry); k; k = Module32Next(list, &entry))
490    {
491        /* FIXME: how do we select the correct module? */
492        ret = (intptr_t)entry.modBaseAddr;
493    }
494    CloseHandle(list);
495
496    return ret;
497}
498
499#endif
Note: See TracBrowser for help on using the repository browser.