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

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

Improve the DLL injection code. Now seems to work rather
well under Windows. But it needs a lot of polishing.

  • Property svn:keywords set to Id
File size: 17.6 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(PROCESS_INFORMATION *, void *, char const *);
77static intptr_t get_base_address(DWORD);
78static intptr_t get_entry_point(char const *name, DWORD pid);
79static intptr_t get_proc_address(void *, DWORD, char const *);
80#endif
81
82int myfork(struct child *child, struct opts *opts)
83{
84    int pipes[3][2];
85    pid_t pid;
86    int i;
87
88    /* Prepare communication pipe */
89    for(i = 0; i < 3; i++)
90    {
91        int ret;
92#if defined HAVE_PIPE
93        ret = pipe(pipes[i]);
94#elif defined HAVE__PIPE
95        ret = _pipe(pipes[i], 512, _O_BINARY | O_NOINHERIT);
96#endif
97        if(ret < 0)
98        {
99            perror("pipe");
100            return -1;
101        }
102    }
103
104    pid = run_process(child, opts, pipes);
105    if(pid < 0)
106    {
107        /* FIXME: close pipes */
108        fprintf(stderr, "error launching `%s'\n", child->newargv[0]);
109        return -1;
110    }
111
112    child->pid = pid;
113    for(i = 0; i < 3; i++)
114    {
115        close(pipes[i][1]);
116        child->fd[i] = pipes[i][0];
117    }
118
119    return 0;
120}
121
122#if !defined HAVE_SETENV
123static void setenv(char const *name, char const *value, int overwrite)
124{
125    char *str;
126
127    if(!overwrite && getenv(name))
128        return;
129
130    str = malloc(strlen(name) + 1 + strlen(value) + 1);
131    sprintf(str, "%s=%s", name, value);
132    putenv(str);
133}
134#endif
135
136static int run_process(struct child *child, struct opts *opts, int pipes[][2])
137{
138    char buf[64];
139#if defined HAVE_FORK
140    static int const files[] = { DEBUG_FILENO, STDERR_FILENO, STDOUT_FILENO };
141    char *libpath, *tmp;
142    int pid, j, len = strlen(opts->oldargv[0]);
143#   if defined __APPLE__
144#       define EXTRAINFO ""
145#       define PRELOAD "DYLD_INSERT_LIBRARIES"
146    setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1);
147#   elif defined __osf__
148#       define EXTRAINFO ":DEFAULT"
149#       define PRELOAD "_RLD_LIST"
150#   elif defined __sun && defined __i386
151#       define EXTRAINFO ""
152#       define PRELOAD "LD_PRELOAD_32"
153#   else
154#       define EXTRAINFO ""
155#       define PRELOAD "LD_PRELOAD"
156#   endif
157#elif HAVE_WINDOWS_H
158    PROCESS_INFORMATION pinfo;
159    STARTUPINFO sinfo;
160    HANDLE pid;
161    void *epaddr;
162    int ret;
163#endif
164
165#if defined HAVE_FORK
166    /* Fork and launch child */
167    pid = fork();
168    if(pid < 0)
169        perror("fork");
170    if(pid != 0)
171        return pid;
172
173    /* We loop in reverse order so that files[0] is done last,
174     * just in case one of the other dup2()ed fds had the value */
175    for(j = 3; j--; )
176    {
177        close(pipes[j][0]);
178        if(pipes[j][1] != files[j])
179        {
180            dup2(pipes[j][1], files[j]);
181            close(pipes[j][1]);
182        }
183    }
184#endif
185
186#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_MEM
187    if(opts->maxmem >= 0)
188    {
189        struct rlimit rlim;
190        rlim.rlim_cur = opts->maxmem * 1048576;
191        rlim.rlim_max = opts->maxmem * 1048576;
192        setrlimit(ZZUF_RLIMIT_MEM, &rlim);
193    }
194#endif
195
196#if defined HAVE_SETRLIMIT && defined ZZUF_RLIMIT_CPU
197    if(opts->maxcpu >= 0)
198    {
199        struct rlimit rlim;
200        rlim.rlim_cur = opts->maxcpu;
201        rlim.rlim_max = opts->maxcpu + 5;
202        setrlimit(ZZUF_RLIMIT_CPU, &rlim);
203    }
204#endif
205
206    /* Set environment variables */
207    sprintf(buf, "%i", opts->seed);
208    setenv("ZZUF_SEED", buf, 1);
209    sprintf(buf, "%g", opts->minratio);
210    setenv("ZZUF_MINRATIO", buf, 1);
211    sprintf(buf, "%g", opts->maxratio);
212    setenv("ZZUF_MAXRATIO", buf, 1);
213
214#if defined HAVE_FORK
215    /* Make sure there is space for everything we might do. */
216    libpath = malloc(len + strlen(LIBDIR "/" LT_OBJDIR SONAME EXTRAINFO) + 1);
217    strcpy(libpath, opts->oldargv[0]);
218
219    /* If the binary name contains a '/', we look for a libzzuf in the
220     * same directory. Otherwise, we only look into the system directory
221     * to avoid shared library attacks. Write the result in libpath. */
222    tmp = strrchr(libpath, '/');
223    if(tmp)
224    {
225        strcpy(tmp + 1, LT_OBJDIR SONAME);
226        if(access(libpath, R_OK) < 0)
227            strcpy(libpath, LIBDIR "/" SONAME);
228    }
229    else
230        strcpy(libpath, LIBDIR "/" SONAME);
231
232    /* OSF1 only */
233    strcat(libpath, EXTRAINFO);
234
235    /* Do not clobber previous LD_PRELOAD values */
236    tmp = getenv(PRELOAD);
237    if(tmp && *tmp)
238    {
239        char *bigbuf = malloc(strlen(tmp) + strlen(libpath) + 2);
240        sprintf(bigbuf, "%s:%s", tmp, libpath);
241        free(libpath);
242        libpath = bigbuf;
243    }
244
245    /* Only preload the library in preload mode */
246    if (opts->opmode == OPMODE_PRELOAD)
247        setenv(PRELOAD, libpath, 1);
248    free(libpath);
249
250    if(execvp(child->newargv[0], child->newargv))
251    {
252        perror(child->newargv[0]);
253        exit(EXIT_FAILURE);
254    }
255
256    exit(EXIT_SUCCESS);
257    /* no return */
258    return 0;
259#elif HAVE_WINDOWS_H
260    pid = GetCurrentProcess();
261
262    memset(&sinfo, 0, sizeof(sinfo));
263    sinfo.cb = sizeof(sinfo);
264#if 0
265    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[0][1]), pid,
266        /* FIXME */ &sinfo.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
267    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[1][1]), pid,
268                    &sinfo.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS);
269    DuplicateHandle(pid, (HANDLE)_get_osfhandle(pipes[2][1]), pid,
270                    &sinfo.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
271    sinfo.dwFlags = STARTF_USESTDHANDLES;
272#endif
273    ret = CreateProcess(NULL, child->newargv[0], NULL, NULL, FALSE,
274                        CREATE_SUSPENDED, NULL, NULL, &sinfo, &pinfo);
275    if(!ret)
276        return -1;
277
278    /* Get the child process's entry point address */
279    epaddr = (void *)get_entry_point(child->newargv[0],
280                                     pinfo.dwProcessId);
281    if(!epaddr)
282        return -1;
283
284    /* Insert the replacement code */
285    ret = dll_inject(&pinfo, epaddr, SONAME);
286    if(ret < 0)
287    {
288        TerminateProcess(pinfo.hProcess, -1);
289        return -1;
290    }
291
292    ret = ResumeThread(pinfo.hThread);
293    if(ret < 0)
294    {
295        TerminateProcess(pinfo.hProcess, -1);
296        return -1;
297    }
298
299Sleep(5000);
300    return (long int)pinfo.hProcess;
301#endif
302}
303
304#if defined HAVE_WINDOWS_H
305static void rep32(uint8_t *buf, void *addr)
306{
307    while(buf++)
308        if (memcmp(buf, "____", 4) == 0)
309        {
310            memcpy(buf, &addr, 4);
311            return;
312        }
313}
314
315static int dll_inject(PROCESS_INFORMATION *pinfo,
316                      void *epaddr, char const *lib)
317{
318    static uint8_t const loader[] =
319        /* Load the injected DLL into memory */
320        "\xb8____"       /* mov %eax, <library_name_address> */
321        "\x50"           /* push %eax */
322        "\xb8____"       /* mov %eax, <LoadLibraryA> */
323        "\xff\xd0"       /* call %eax */
324        /* Restore the clobbered entry point code using our backup */
325        "\xb8\0\0\0\0"   /* mov %eax,0 */
326        "\x50"           /* push %eax */
327        "\xb8____"       /* mov %eax, <jumper_length> */
328        "\x50"           /* push %eax */
329        "\xb8____"       /* mov %eax, <backuped_entry_point_address> */
330        "\x50"           /* push %eax */
331        "\xb8____"       /* mov %eax, <original_entry_point_address> */
332        "\x50"           /* push %eax */
333        "\xb8____"       /* mov %eax, <GetCurrentProcess> */
334        "\xff\xd0"       /* call %eax */
335        "\x50"           /* push %eax */
336        "\xb8____"       /* mov %eax, <WriteProcessMemory> */
337        "\xff\xd0"       /* call %eax */
338        /* Jump to the original entry point */
339        "\xb8____"       /* mov %eax, <original_entry_point_address> */
340        "\xff\xe0";      /* jmp %eax */
341
342    static uint8_t const waiter[] =
343        "\xeb\xfe";      /* jmp <current> */
344
345    static uint8_t const jumper[] =
346        /* Jump to the injected loader */
347        "\xb8____"       /* mov eax, <loader_address> */
348        "\xff\xe0";      /* jmp eax */
349
350    void *process = pinfo->hProcess;
351    void *thread = pinfo->hThread;
352    DWORD pid = pinfo->dwProcessId;
353
354    /* code:
355     * +---------------+--------------------+--------------+-------------+
356     * |     loader    | entry point backup | library name |   jumper    |
357     * |  len(loader)  |    len(jumper)     |   len(lib)   | len(jumper) |
358     * +---------------+--------------------+--------------+-------------+ */
359    uint8_t code[1024];
360
361    uint8_t *loaderaddr;
362    size_t liblen, loaderlen, waiterlen, jumperlen;
363    DWORD tmp;
364
365    liblen = strlen(lib) + 1;
366    loaderlen = sizeof(loader) - 1;
367    waiterlen = sizeof(waiter) - 1;
368    jumperlen = sizeof(jumper) - 1;
369    if (loaderlen + jumperlen + liblen > 1024)
370        return -1;
371
372    /* Allocate memory in the child for our injected code */
373    loaderaddr = VirtualAllocEx(process, NULL, loaderlen + jumperlen + liblen,
374                                MEM_COMMIT, PAGE_EXECUTE_READWRITE);
375    if(!loaderaddr)
376        return -1;
377
378    /* Create the first shellcode (jumper).
379     *
380     * The jumper's job is simply to jump at the second shellcode's location.
381     * It is written at the original entry point's location, which will in
382     * turn be restored by the second shellcode.
383     */
384    memcpy(code + loaderlen + jumperlen + liblen, jumper, jumperlen);
385    rep32(code + loaderlen + jumperlen + liblen, loaderaddr);
386
387    /* Create the second shellcode (loader, backuped entry point, and library
388     * name).
389     *
390     * The loader's job is to load the library by calling LoadLibraryA(),
391     * restore the original entry point using the backup copy, and jump
392     * back to the original entry point as if the process had just started.
393     *
394     * The second shellcode is written at a freshly allocated memory location.
395     */
396    memcpy(code, loader, loaderlen);
397    memcpy(code + loaderlen + jumperlen, lib, liblen);
398
399    /* Backup the old entry point code */
400    ReadProcessMemory(process, epaddr, code + loaderlen, jumperlen, &tmp);
401    if(tmp != jumperlen)
402        return -1;
403
404    /* Replace the entry point code with a short jump to self, then resume
405     * the thread. This is necessary for CreateToolhelp32Snapshot() to
406     * work. */
407    WriteProcessMemory(process, epaddr, waiter, waiterlen, &tmp);
408    if(tmp != waiterlen)
409        return -1;
410    FlushInstructionCache(process, epaddr, waiterlen);
411    ResumeThread(thread);
412
413    /* Wait until the entry point is reached */
414    for (tmp = 0; tmp < 100; tmp++)
415    {
416        CONTEXT ctx;
417        ctx.ContextFlags = CONTEXT_FULL;
418        GetThreadContext(thread, &ctx);
419        if ((uintptr_t)ctx.Eip == (uintptr_t)epaddr)
420            break;
421        Sleep(10);
422    }
423    SuspendThread(thread);
424    if (tmp == 100)
425        return -1;
426
427    /* Remotely parse the target process's module list to get the addresses
428     * of the functions we need. This can only be done because we advanced
429     * the target's execution to the entry point. */
430    rep32(code, loaderaddr + loaderlen + jumperlen);
431    rep32(code, (uintptr_t)get_proc_address(process, pid, "LoadLibraryA"));
432    rep32(code, (void *)(uintptr_t)jumperlen);
433    rep32(code, loaderaddr + loaderlen);
434    rep32(code, epaddr);
435    rep32(code, (uintptr_t)get_proc_address(process, pid, "GetCurrentProcess"));
436    rep32(code, (uintptr_t)get_proc_address(process, pid, "WriteProcessMemory"));
437    rep32(code, epaddr);
438
439    /* Write our shellcodes into the target process */
440    WriteProcessMemory(process, epaddr, code + loaderlen + jumperlen + liblen,
441                       jumperlen, &tmp);
442    if(tmp != jumperlen)
443        return -1;
444    FlushInstructionCache(process, epaddr, waiterlen);
445
446    WriteProcessMemory(process, loaderaddr, code,
447                       loaderlen + jumperlen + liblen, &tmp);
448    if(tmp != loaderlen + jumperlen + liblen)
449        return -1;
450
451    return 0;
452}
453
454/* Find the process's entry point address offset. The information is in
455 * the file's PE header. */
456static intptr_t get_entry_point(char const *name, DWORD pid)
457{
458    PIMAGE_DOS_HEADER dos;
459    PIMAGE_NT_HEADERS nt;
460    intptr_t ret = 0;
461    void *file, *map, *base;
462
463    file = CreateFile(name, GENERIC_READ, FILE_SHARE_READ,
464                      NULL, OPEN_EXISTING, 0, NULL);
465    if(file == INVALID_HANDLE_VALUE)
466        return ret;
467
468    map = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);
469    if(!map)
470    {
471        CloseHandle(file);
472        return ret;
473    }
474
475    base = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
476    if(!base)
477    {
478        CloseHandle(map);
479        CloseHandle(file);
480        return ret;
481    }
482
483    /* Sanity checks */
484    dos = (PIMAGE_DOS_HEADER)base;
485    nt = (PIMAGE_NT_HEADERS)((char *)base + dos->e_lfanew);
486    if(dos->e_magic == IMAGE_DOS_SIGNATURE /* 0x5A4D */
487      && nt->Signature == IMAGE_NT_SIGNATURE /* 0x00004550 */
488      && nt->FileHeader.Machine == IMAGE_FILE_MACHINE_I386
489      && nt->OptionalHeader.Magic == 0x10b /* IMAGE_NT_OPTIONAL_HDR32_MAGIC */)
490    {
491        ret = get_base_address(pid);
492        /* Base address not found in the running process. Falling back
493         * to the header's information, which is unreliable because of
494         * Vista's address space randomisation. */
495        if (!ret)
496            ret = (intptr_t)nt->OptionalHeader.ImageBase;
497
498        ret += (intptr_t)nt->OptionalHeader.AddressOfEntryPoint;
499    }
500
501    UnmapViewOfFile(base);
502    CloseHandle(map);
503    CloseHandle(file);
504
505    return ret;
506}
507
508/* FIXME: this could probably be merged with get_entry_point */
509static intptr_t get_proc_address(void *process, DWORD pid, const char *func)
510{
511    char buf[1024];
512    size_t buflen = strlen(func) + 1;
513
514    MODULEENTRY32 entry;
515    intptr_t ret = 0;
516    DWORD tmp;
517    void *list;
518    int i, k;
519
520    list = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
521    entry.dwSize = sizeof(entry);
522    for(k = Module32First(list, &entry); k; k = Module32Next(list, &entry))
523    {
524        IMAGE_DOS_HEADER dos;
525        IMAGE_NT_HEADERS nt;
526        IMAGE_EXPORT_DIRECTORY expdir;
527
528        uint32_t exportaddr;
529        uint8_t const *base = entry.modBaseAddr;
530
531        if (strcmp("kernel32.dll", entry.szModule))
532            continue;
533
534        ReadProcessMemory(process, base, &dos, sizeof(dos), &tmp);
535        ReadProcessMemory(process, base + dos.e_lfanew, &nt, sizeof(nt), &tmp);
536
537        exportaddr = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
538        if (!exportaddr)
539            continue;
540
541        ReadProcessMemory(process, base + exportaddr, &expdir, sizeof(expdir), &tmp);
542
543        for (i = 0; i < (int)expdir.NumberOfNames; i++)
544        {
545            uint32_t nameaddr, funcaddr;
546            uint16_t j;
547
548            /* Look for our function name in the list of names */
549            ReadProcessMemory(process, base + expdir.AddressOfNames
550                                            + i * sizeof(DWORD),
551                              &nameaddr, sizeof(nameaddr), &tmp);
552            ReadProcessMemory(process, base + nameaddr, buf, buflen, &tmp);
553
554            if (strcmp(buf, func))
555                continue;
556
557            /* If we found a function with this name, return its address */
558            ReadProcessMemory(process, base + expdir.AddressOfNameOrdinals
559                                            + i * sizeof(WORD),
560                                &j, sizeof(j), &tmp);
561            ReadProcessMemory(process, base + expdir.AddressOfFunctions
562                                            + j * sizeof(DWORD),
563                                &funcaddr, sizeof(funcaddr), &tmp);
564
565            ret = (intptr_t)base + funcaddr;
566            goto _finished;
567        }
568    }
569
570_finished:
571    CloseHandle(list);
572    return ret;
573}
574
575/* Find the process's base address once it is loaded in memory (the header
576 * information is unreliable because of Vista's ASLR).
577 * FIXME: this does not work properly because CreateToolhelp32Snapshot()
578 * requires a certain level of initialisation. */
579static intptr_t get_base_address(DWORD pid)
580{
581    MODULEENTRY32 entry;
582    intptr_t ret = 0;
583
584    void *list;
585    int k;
586
587    list = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
588    entry.dwSize = sizeof(entry);
589    for(k = Module32First(list, &entry); k; k = Module32Next(list, &entry))
590    {
591        /* FIXME: how do we select the correct module? */
592        ret = (intptr_t)entry.modBaseAddr;
593    }
594    CloseHandle(list);
595
596    return ret;
597}
598
599#endif
Note: See TracBrowser for help on using the repository browser.