source: zzuf/trunk/src/libzzuf/sys.c @ 4838

Last change on this file since 4838 was 4838, checked in by Sam Hocevar, 9 years ago

win32: add more explicit error messages and add support for 0xb8 opcode.

  • Property svn:keywords set to Id
File size: 11.3 KB
Line 
1/*
2 *  zzuf - general purpose fuzzer
3 *  Copyright (c) 2006-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 *  sys.c: system-dependent initialisation
15 */
16
17#include "config.h"
18
19/* Need this for RTLD_NEXT */
20#define _GNU_SOURCE
21
22#if defined HAVE_STDINT_H
23#   include <stdint.h>
24#elif defined HAVE_INTTYPES_H
25#   include <inttypes.h>
26#endif
27
28#ifdef HAVE_DLFCN_H
29#   include <dlfcn.h>
30#endif
31
32#if defined HAVE_WINDOWS_H
33#   include <windows.h>
34#   include <imagehlp.h>
35#   include <tlhelp32.h>
36#   define import_t PIMAGE_IMPORT_DESCRIPTOR
37#   define thunk_t PIMAGE_THUNK_DATA
38#endif
39
40#include <stdio.h>
41
42#include "sys.h"
43#include "lib-load.h"
44
45#if defined HAVE_DLFCN_H
46void *_zz_dl_lib = RTLD_NEXT;
47#endif
48
49#if defined HAVE_WINDOWS_H
50static void insert_funcs(void);
51#endif
52
53void _zz_sys_init(void)
54{
55#if defined HAVE_WINDOWS_H
56
57    insert_funcs();
58
59#elif defined HAVE_DLFCN_H
60    /* If glibc is recent enough, we use dladdr() to get its address. This
61     * way we are sure that the symbols we load are the most recent version,
62     * or we may get weird problems. We choose fileno as a random symbol to
63     * get, because we know we don't divert it. */
64#   if HAVE_DLADDR
65    Dl_info di;
66    if (dladdr(&fileno, &di) != 0)
67    {
68        void *lib = dlopen(di.dli_fname, RTLD_NOW);
69        if (lib)
70            _zz_dl_lib = lib;
71    }
72#   endif
73#else
74    /* Nothing to do on our platform */
75#endif
76}
77
78#if defined HAVE_WINDOWS_H
79
80#define MK_JMP_JD(dst, src) ((dst) - ((src) + 5))
81
82static int modrm_sib_size(uint8_t* code)
83{
84    static uint8_t modrm_size[] = { 0, 1, 4, 0 }; /* [reg ...], [reg ... + sbyte], [reg ... + sdword], reg */
85    uint8_t modrm = *code;
86
87    if (modrm == 0x05) /* [(rip) + sdword] */ return 1 + 4;
88
89    /* Does this instruction have a SIB byte ? */
90    return 1 + (!!(((modrm & 0x7) == 0x4) && ((modrm >> 6) != 0x3))) + modrm_size[modrm >> 6];
91}
92
93/* zz_lde is a _very_ simple length disassemble engine. */
94static int zz_lde(uint8_t *code)
95{
96    int insn_size = 0;
97
98    uint8_t opcd = code[insn_size++];
99    int imm_size = 4; /* Iv */
100
101#ifdef _M_AMD64
102    // REX prefix
103    if ((opcd & 0xf8) == 0x48)
104    {
105        imm_size = 8; /* REX.W */
106        opcd = code[insn_size++];
107    }
108#endif
109
110    /* Simple instructions should be placed here */
111    switch (opcd)
112    {
113    case 0x68:
114        return (insn_size + 4); /* PUSH Iv */
115    case 0x6a:
116        return (insn_size + 1); /* PUSH Ib */
117    case 0x90:
118        return insn_size;       /* NOP     */
119    case 0xb8:
120    case 0xb9:
121    case 0xba:
122    case 0xbb:
123    case 0xbc:
124    case 0xbd:
125    case 0xbe:
126    case 0xbf:
127        return insn_size + 5;   /* MOV immediate */
128    default:
129        break;
130    }
131
132    /* PUSH/POP rv */
133    if ((opcd & 0xf0) == 0x50)
134        return insn_size;
135
136    /* MNEM E?, G? or G?, E? */
137    switch (opcd)
138    {
139    case 0x89: /* mov Ev, Gv */
140    case 0x8b: /* mov Gv, Ev */
141        return (insn_size + modrm_sib_size(code + insn_size));
142
143    case 0x80: /* Group#1 Eb, Ib */
144    case 0x82: /* Group#1 Eb, Ib */
145    case 0x83: /* Group#1 Ev, Ib */
146        return (insn_size + (modrm_sib_size(code + insn_size) + 1));
147
148    case 0x81: /* Group#1 Ev, Iz */
149        return (insn_size + (modrm_sib_size(code + insn_size) + 4));
150
151    case 0xff:
152        if ((code[insn_size] & 0x38) == 0x30) /* PUSH Ev */
153            return (insn_size + modrm_sib_size(code + insn_size));
154        break;
155
156    default:
157        fprintf(stderr, "unknown opcode %02x\n", opcd);
158        break;
159    }
160
161    return 0;
162}
163
164/* This function returns the required size to insert a patch */
165static int compute_patch_size(uint8_t *code, int required_size)
166{
167    int patch_size = 0;
168    while (patch_size < required_size)
169    {
170        int insn_size = zz_lde(code);
171        if (insn_size == 0)
172            return -1;
173        patch_size += insn_size;
174    }
175    return patch_size;
176}
177
178static void make_jmp32(uint8_t *src, uint8_t *dst, uint8_t *code)
179{
180    *(uint8_t  *)(code + 0) = 0xe9;             /* JMP Jd */
181    *(uint32_t *)(code + 1) = (uint32_t)MK_JMP_JD(dst, src);
182}
183
184static void make_jmp64(uint8_t *dst, uint8_t *code)
185{
186    memcpy(code, "\x48\xb8", 2);                /* MOV rAX, Iq */
187    *(uint64_t *)(code + 2) = (uint64_t)dst;
188    memcpy(code + 10, "\xff\xe0", 2);           /* JMP rAX */
189}
190
191/* This function allocates and fills a trampoline for the function pointed by code. It also tries to handle some relocations. */
192static int make_trampoline(uint8_t *code, size_t patch_size, uint8_t **trampoline_buf, size_t *trampoline_size)
193{
194    uint8_t *trampoline;
195
196    *trampoline_buf  = NULL;
197    *trampoline_size = 0;
198
199#ifdef _M_AMD64
200    {
201        size_t code_offset = 0;
202        size_t trampoline_offset = 0;
203        const size_t reloc_size  = -7 /* size of mov rax, [rip + ...] */ +10 /* size of mov rax, Iq */;
204
205        trampoline = malloc(patch_size + reloc_size + 13); /* Worst case */
206        if (trampoline == NULL)
207            return -1;
208        memset(trampoline, 0xcc, patch_size + 13);
209
210        while (code_offset < patch_size)
211        {
212            int insn_size = zz_lde(code + code_offset);
213            if (insn_size == 0)
214                return -1;
215
216            /* mov rax, [rip + ...] is the signature for stack cookie */
217            if (!memcmp(code + code_offset, "\x48\x8b\x05", 3))
218            {
219                uint64_t *cookie_address = (uint64_t *)(code + code_offset + insn_size + *(uint32_t *)(code + code_offset + 3));
220                patch_size              += reloc_size;
221
222                memcpy(trampoline + trampoline_offset, "\x48\xb8", 2); /* MOV rAX, Iq */
223                *(uint64_t *)(trampoline + trampoline_offset + 2) = *cookie_address;
224                trampoline_offset += 10;
225            }
226            else
227            {
228                *trampoline_size += insn_size;
229                memcpy(trampoline + trampoline_offset, code + code_offset, insn_size);
230                trampoline_offset += insn_size;
231            }
232
233            code_offset += insn_size;
234        }
235
236
237        /* We can't use make_jmp64 since rAX is used by the __security_cookie */
238        memcpy(trampoline + trampoline_offset, "\x49\xba", 2); /* MOV r10, Iq */
239        *(uint64_t *)(trampoline + trampoline_offset + 2) = (uint64_t)(code + code_offset);
240        memcpy(trampoline + trampoline_offset + 10, "\x41\xff\xe2", 3); /* JMP r10 */
241
242        *trampoline_buf  = trampoline;
243        *trampoline_size = trampoline_offset;
244        return 0;
245    }
246#elif _M_IX86
247    trampoline = malloc(patch_size + 5);
248    if (trampoline == NULL) return -1;
249    memcpy(trampoline, code, patch_size);
250    make_jmp32(trampoline + patch_size, code + patch_size, trampoline + patch_size);
251
252    *trampoline_size = patch_size;
253    *trampoline_buf  = trampoline;
254    return 0;
255#else
256#   error Unsupported architecture !
257#endif
258}
259
260/*
261 * Sometimes Windows APIs are a stub and contain only a JMP to the real function.
262 * To avoid to relocate a JMP, we use the destination address.
263 */
264static int relocate_hook(uint8_t **code)
265{
266        uint8_t *cur_code = *code;
267
268#ifdef _M_AMD64
269    // we ignore the REX prefix
270    if ((*cur_code & 0xf8) == 0x48)
271                ++cur_code;
272#endif
273
274    /* JMP Jd */
275    if (*cur_code == 0xe9)
276    {
277        *cur_code += (5 + *(uint32_t *)(cur_code + 1));
278        return 0;
279    }
280
281    /* JMP [(rip)+addr] */
282    else if (!memcmp(cur_code, "\xff\x25", 2))
283    {
284#ifdef _M_AMD64
285        uint8_t **dst_addr = (uint8_t **)(cur_code + 6 + *(uint32_t *)(cur_code + 2));
286        *code = *dst_addr;
287#elif _M_IX86
288        /* UNTESTED ! */
289        uint8_t **dst_addr = *(uint32_t *)(*cur_code + 2);
290        *code = *dst_addr;
291#else
292#   error Unsupported architecture !
293#endif
294        return 0;
295    }
296
297    return -1;
298}
299
300/* This function allows to hook any API. To do so, it disassembles the beginning of the
301 * targeted function and looks for, at least, 5 bytes (size of JMP Jd).
302 * Then it writes a JMP Jv instruction to make the new_api executed.
303 * Finally, trampoline_api contains a wrapper to call in order to execute the original API */
304static int hook_inline(uint8_t *old_api, uint8_t *new_api, uint8_t **trampoline_api)
305{
306    int res                 = -1;
307    int required_size       = 5;
308    int patch_size          = 0;
309    uint8_t jmp_prolog[12];
310    uint8_t *trampoline     = NULL;
311    size_t trampoline_size  = 0;
312    DWORD old_prot;
313    uint8_t *reloc_old_api  = old_api;
314
315    while ((relocate_hook(&reloc_old_api)) >= 0)
316        old_api = reloc_old_api;
317
318    *trampoline_api = NULL;
319
320    memset(jmp_prolog, 0xcc, sizeof(jmp_prolog));
321
322#ifdef _M_AMD64
323    if (new_api - old_api > 0xffffffff)
324    {
325        required_size = 12;
326        make_jmp64(new_api, jmp_prolog);
327    }
328    else make_jmp32(old_api, new_api, jmp_prolog);
329#elif _M_IX86
330    make_jmp32(old_api, new_api, jmp_prolog);
331#else
332#   error Unsupported architecture !
333#endif
334
335    /* if we can't get enough byte, we quit */
336    if ((patch_size = compute_patch_size(old_api, required_size)) == -1)
337    {
338        fprintf(stderr, "cannot compute patch size\n");
339        return -1;
340    }
341
342    if (make_trampoline(old_api, patch_size, &trampoline, &trampoline_size) < 0)
343    {
344        fprintf(stderr, "cannot make trampoline\n");
345        goto _out;
346    }
347
348    /* We must make the trampoline executable, this line is required because of DEP */
349    /* NOTE: We _must_ set the write protection, otherwise the heap allocator will crash ! */
350    if (!VirtualProtect(trampoline, trampoline_size, PAGE_EXECUTE_READWRITE, &old_prot))
351    {
352        fprintf(stderr, "cannot make the trampoline writable\n");
353        goto _out;
354    }
355
356    /* We patch the targeted API, so we must set it as writable */
357    if (!VirtualProtect(old_api, patch_size, PAGE_EXECUTE_READWRITE, &old_prot))
358    {
359        fprintf(stderr, "cannot make old API writable\n");
360        goto _out;
361    }
362    memcpy(old_api, jmp_prolog, patch_size);
363    VirtualProtect(old_api, patch_size, old_prot, &old_prot); /* we don't care if this functon fails */
364
365    *trampoline_api = trampoline;
366
367    res = 0;
368
369_out:
370    if (res < 0)
371    {
372        if (*trampoline_api)
373        {
374            free(*trampoline_api);
375            trampoline_api = NULL;
376        }
377    }
378
379    return res;
380}
381
382static void insert_funcs(void)
383{
384    static zzuf_table_t *list[] =
385    {
386        table_win32,
387    };
388
389    zzuf_table_t *diversion;
390    HMODULE lib;
391
392    for (diversion = *list; diversion->lib; diversion++)
393    {
394        uint8_t *old_api;
395        uint8_t *trampoline_api;
396
397        /* most of the time, the dll is already loaded */
398        if ((lib = GetModuleHandleA(diversion->lib)) == NULL)
399        {
400           if ((lib = LoadLibraryA(diversion->lib)) == NULL)
401           {
402               fprintf(stderr, "unable to load %s\n", diversion->lib);
403               continue;
404           }
405        }
406        if ((old_api = (uint8_t *)GetProcAddress(lib, diversion->name)) == NULL)
407        {
408            fprintf(stderr, "unable to get pointer to %s\n", diversion->name);
409            continue;
410        }
411        if (hook_inline(old_api, diversion->new, &trampoline_api) < 0)
412        {
413            fprintf(stderr, "hook_inline failed while hooking %s!%s\n", diversion->lib, diversion->name);
414            continue;
415        }
416        *diversion->old = trampoline_api;
417    }
418}
419
420#endif
Note: See TracBrowser for help on using the repository browser.