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

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

Refactor the shellcode generation to remove all hardcoded values.

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