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

Last change on this file since 4291 was 4291, checked in by Sam Hocevar, 11 years ago

Fix a regression in myfork.c causing build failures when libtool does not
define LT_OBJDIR.

  • Property svn:keywords set to Id
File size: 14.1 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 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(opts, pipes);
104    if(pid < 0)
105    {
106        /* FIXME: close pipes */
107        fprintf(stderr, "error launching `%s'\n", opts->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 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    setenv(PRELOAD, libpath, 1);
245    free(libpath);
246
247    if(execvp(opts->newargv[0], opts->newargv))
248    {
249        perror(opts->newargv[0]);
250        exit(EXIT_FAILURE);
251    }
252
253    exit(EXIT_SUCCESS);
254    /* no return */
255    return 0;
256#elif HAVE_WINDOWS_H
257    pid = GetCurrentProcess();
258
259    memset(&sinfo, 0, sizeof(sinfo));
260    sinfo.cb = sizeof(sinfo);
261    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[0][1]), pid,
262        /* FIXME */ &sinfo.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
263    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[1][1]), pid,
264                    &sinfo.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS);
265    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[2][1]), pid,
266                    &sinfo.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
267    sinfo.dwFlags = STARTF_USESTDHANDLES;
268    ret = CreateProcess(NULL, opts->newargv[0], NULL, NULL, FALSE,
269                        CREATE_SUSPENDED, NULL, NULL, &sinfo, &pinfo);
270    if(!ret)
271        return -1;
272
273    /* Get the child process's entry point address */
274    epaddr = (void *)get_entry_point(opts->newargv[0],
275                                     pinfo.dwProcessId);
276    if(!epaddr)
277        return -1;
278
279    /* Insert the replacement code */
280    ret = dll_inject(pinfo.hProcess, epaddr, SONAME);
281    if(ret < 0)
282    {
283        TerminateProcess(pinfo.hProcess, -1);
284        return -1;
285    }
286
287    ret = ResumeThread(pinfo.hThread);
288    if(ret < 0)
289    {
290        TerminateProcess(pinfo.hProcess, -1);
291        return -1;
292    }
293
294    return (long int)pinfo.hProcess;
295#endif
296}
297
298#if defined HAVE_WINDOWS_H
299static void rep32(uint8_t *buf, void *addr)
300{
301    while(buf++)
302        if (memcmp(buf, "____", 4) == 0)
303        {
304            memcpy(buf, &addr, 4);
305            return;
306        }
307}
308
309static int dll_inject(void *process, void *epaddr, char const *lib)
310{
311    static uint8_t const loader[] =
312        /* Load the injected DLL into memory */
313        "\xb8____"       /* mov %eax, <library_name_address> */
314        "\x50"           /* push %eax */
315        "\xb8____"       /* mov %eax, <LoadLibraryA> */
316        "\xff\xd0"       /* call %eax */
317        /* Restore the clobbered entry point code using our backup */
318        "\xb8\0\0\0\0"   /* mov %eax,0 */
319        "\x50"           /* push %eax */
320        "\xb8____"       /* mov %eax, <jumper_length> */
321        "\x50"           /* push %eax */
322        "\xb8____"       /* mov %eax, <backuped_entry_point_address> */
323        "\x50"           /* push %eax */
324        "\xb8____"       /* mov %eax, <original_entry_point_address> */
325        "\x50"           /* push %eax */
326        "\xb8____"       /* mov %eax, <GetCurrentProcess> */
327        "\xff\xd0"       /* call %eax */
328        "\x50"           /* push %eax */
329        "\xb8____"       /* mov %eax, <WriteProcessMemory> */
330        "\xff\xd0"       /* call %eax */
331        /* Jump to the original entry point */
332        "\xb8____"       /* mov %eax, <original_entry_point_address> */
333        "\xff\xe0";      /* jmp %eax */
334
335    static uint8_t const jumper[] =
336        /* Jump to the injected loader */
337        "\xb8____"       /* mov eax, <loader_address> */
338        "\xff\xe0";      /* jmp eax */
339
340    /* code:
341     * +---------------+--------------------+--------------+-------------+
342     * |     loader    | entry point backup | library name |   jumper    |
343     * |  len(loader)  |    len(jumper)     |   len(lib)   | len(jumper) |
344     * +---------------+--------------------+--------------+-------------+ */
345    uint8_t code[1024];
346
347    void *kernel32;
348    uint8_t *loaderaddr;
349    size_t liblen, loaderlen, jumperlen;
350    DWORD tmp;
351
352    liblen = strlen(lib) + 1;
353    loaderlen = sizeof(loader) - 1;
354    jumperlen = sizeof(jumper) - 1;
355    if (loaderlen + jumperlen + liblen > 1024)
356        return -1;
357
358    /* Allocate memory in the child for our injected code */
359    loaderaddr = VirtualAllocEx(process, NULL, loaderlen + jumperlen + liblen,
360                                MEM_COMMIT, PAGE_EXECUTE_READWRITE);
361    if(!loaderaddr)
362        return -1;
363
364    /* Create the first shellcode (jumper).
365     *
366     * The jumper's job is simply to jump at the second shellcode's location.
367     * It is written at the original entry point's location, which will in
368     * turn be restored by the second shellcode.
369     */
370    memcpy(code + loaderlen + jumperlen + liblen, jumper, jumperlen);
371    rep32(code + loaderlen + jumperlen + liblen, loaderaddr);
372
373    /* Create the second shellcode (loader, backuped entry point, and library
374     * name).
375     *
376     * The loader's job is to load the library by calling LoadLibraryA(),
377     * restore the original entry point using the backup copy, and jump
378     * back to the original entry point as if the process had just started.
379     *
380     * The second shellcode is written at a freshly allocated memory location.
381     */
382    memcpy(code, loader, loaderlen);
383    memcpy(code + loaderlen + jumperlen, lib, liblen);
384
385    /* Backup the old entry point code */
386    ReadProcessMemory(process, epaddr, code + loaderlen,
387                      jumperlen, &tmp);
388    if(tmp != jumperlen)
389        return -1;
390
391    /* FIXME: the GetProcAddress calls assume the library was loaded at
392     * the same address in the child process. This is wrong since Vista
393     * and its address space randomisation. */
394    kernel32 = LoadLibrary("kernel32.dll");
395    if(!kernel32)
396        return -1;
397
398    rep32(code, loaderaddr + loaderlen + jumperlen);
399    rep32(code, GetProcAddress(kernel32, "LoadLibraryA"));
400    rep32(code, (void *)(uintptr_t)jumperlen);
401    rep32(code, loaderaddr + loaderlen);
402    rep32(code, epaddr);
403    rep32(code, GetProcAddress(kernel32, "GetCurrentProcess"));
404    rep32(code, GetProcAddress(kernel32, "WriteProcessMemory"));
405    rep32(code, epaddr);
406    FreeLibrary(kernel32);
407
408    /* Write our shellcodes into the target process */
409    WriteProcessMemory(process, epaddr, code + loaderlen + jumperlen + liblen,
410                       jumperlen, &tmp);
411    if(tmp != jumperlen)
412        return -1;
413
414    WriteProcessMemory(process, loaderaddr, code,
415                       loaderlen + jumperlen + liblen, &tmp);
416    if(tmp != loaderlen + jumperlen + liblen)
417        return -1;
418
419    return 0;
420}
421
422/* Find the process's entry point address offset. The information is in
423 * the file's PE header. */
424static intptr_t get_entry_point(char const *name, DWORD pid)
425{
426    PIMAGE_DOS_HEADER dos;
427    PIMAGE_NT_HEADERS nt;
428    intptr_t ret = 0;
429    void *file, *map, *base;
430
431    file = CreateFile(name, GENERIC_READ, FILE_SHARE_READ,
432                      NULL, OPEN_EXISTING, 0, NULL);
433    if(file == INVALID_HANDLE_VALUE)
434        return ret;
435
436    map = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);
437    if(!map)
438    {
439        CloseHandle(file);
440        return ret;
441    }
442
443    base = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
444    if(!base)
445    {
446        CloseHandle(map);
447        CloseHandle(file);
448        return ret;
449    }
450
451    /* Sanity checks */
452    dos = (PIMAGE_DOS_HEADER)base;
453    nt = (PIMAGE_NT_HEADERS)((char *)base + dos->e_lfanew);
454    if(dos->e_magic == IMAGE_DOS_SIGNATURE /* 0x5A4D */
455      && nt->Signature == IMAGE_NT_SIGNATURE /* 0x00004550 */
456      && nt->FileHeader.Machine == IMAGE_FILE_MACHINE_I386
457      && nt->OptionalHeader.Magic == 0x10b /* IMAGE_NT_OPTIONAL_HDR32_MAGIC */)
458    {
459        ret = get_base_address(pid);
460        /* Base address not found in the running process. Falling back
461         * to the header's information, which is unreliable because of
462         * Vista's address space randomisation. */
463        if (!ret)
464            ret = (intptr_t)nt->OptionalHeader.BaseOfCode;
465
466        ret += (intptr_t)nt->OptionalHeader.AddressOfEntryPoint;
467    }
468
469    UnmapViewOfFile(base);
470    CloseHandle(map);
471    CloseHandle(file);
472
473    return ret;
474}
475
476/* Find the process's base address once it is loaded in memory (the header
477 * information is unreliable because of Vista's ASLR). */
478static intptr_t get_base_address(DWORD pid)
479{
480    MODULEENTRY32 entry;
481    intptr_t ret = 0;
482    void *list;
483    int k;
484
485    list = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
486    entry.dwSize = sizeof(entry);
487    for(k = Module32First(list, &entry); k; k = Module32Next(list, &entry))
488    {
489        /* FIXME: how do we select the correct module? */
490        ret = (intptr_t)entry.modBaseAddr;
491    }
492    CloseHandle(list);
493
494    return ret;
495}
496
497#endif
Note: See TracBrowser for help on using the repository browser.