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

Last change on this file since 4290 was 4290, checked in by sam, 5 years ago

Use LD_PRELOAD_32 on OpenSolaris? i386.

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