aboutsummaryrefslogtreecommitdiff
path: root/lib/substitute.h
blob: 181fb7cfd062be4b91eb71c3f2f77f25767bb3ed (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
    libsubstitute - https://github.com/comex/substitute
    This header file itself is in the public domain (or in any jusrisdiction
    where the former is ineffective, CC0 1.0).
*/

#pragma once

#include <stdlib.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Error codes */
enum {
    /* TODO add numbers */
    SUBSTITUTE_OK = 0,

    /* substitute_hook_functions: can't patch a function because it's too short-
     * i.e. there's an unconditional return instruction inside the patch region
     * (and not at its end) */
    SUBSTITUTE_ERR_FUNC_TOO_SHORT,

    /* substitute_hook_functions: can't patch a function because one of the
     * instructions within the patch region is one of a few special problematic
     * cases - if you get this on real code, the library should probably be
     * updated to handle that case properly */
    SUBSTITUTE_ERR_FUNC_BAD_INSN_AT_START,

    /* substitute_hook_functions: can't patch a function because the (somewhat
     * cursory) jump analysis found a jump later in the function to within the
     * patch region at the beginning */
    SUBSTITUTE_ERR_FUNC_JUMPS_TO_START,

    /* mmap or mprotect failure other than ENOMEM (preserved in errno on return
     * from the substitute_* function).  Most likely to come up with
     * substitute_hook_functions, if the kernel is preventing pages from being
     * marked executable. */
    SUBSTITUTE_ERR_VM,

    /* substitute_interpose_imports: couldn't redo relocation for an import
     * because the type was unknown */
    SUBSTITUTE_ERR_UNKNOWN_RELOCATION_TYPE,

    /* substitute_hook_objc_message: no such selector existed in the class's
     * inheritance tree */
    SUBSTITUTE_ERR_NO_SUCH_SELECTOR,
};

struct substitute_function_hook {
    void *function;
    void *replacement;
    void *old_ptr; /* optional: out pointer to function pointer to call old impl */
};

/* TODO doc */
int substitute_hook_functions(const struct substitute_function_hook *hooks,
                              size_t nhooks,
                              int options);

#if 1 /* declare dynamic linker-related stuff? */

#ifdef __APPLE__
#include <mach-o/nlist.h>
#ifdef __LP64__
typedef struct nlist_64 substitute_sym;
#else
typedef struct nlist substitute_sym;
#endif
#else
#error No definition for substitute_sym!
#endif

struct substitute_image {
#ifdef __APPLE__
    intptr_t slide;
    void *dlhandle;
    const void *image_header;
#endif
    /* possibly private fields... */
};

/* Look up an image currently loaded into the process.
 *
 * @filename the executable/library path (c.f. dyld(3) on Darwin)
 * @return   a handle, or NULL if the image wasn't found
 */
struct substitute_image *substitute_open_image(const char *filename);

/* Release a handle opened with substitute_open_image.
 *
 * @handle a banana
 */
void substitute_close_image(struct substitute_image *handle);

/* Look up private symbols in an image currently loaded into the process.
 *
 * @handle handle opened with substitute_open_image
 * @names  an array of symbol names to search for
 * @syms   an array of substitute_sym *, one per name; on return, each entry
 *         will be a pointer into the symbol table for that image, or NULL if
 *         the symbol wasn't found
 * @nsyms  number of names
 *
 * @return SUBSTITUTE_OK (maybe errors in the future)
 */
int substitute_find_private_syms(struct substitute_image *handle,
                                 const char **names,
                                 substitute_sym **syms,
                                 size_t nsyms);

/* Get a pointer corresponding to a loaded symbol table entry.
 * @handle handle containing the symbol
 * @sym    symbol
 * @return the pointer - on ARM, this can be | 1 for Thumb, like everything
 * else
 */
void *substitute_sym_to_ptr(struct substitute_image *handle, substitute_sym *sym);

struct substitute_import_hook {
    /* The symbol name - this is raw, so C++ symbols are mangled, and on OS X
     * most symbols have '_' prepended. */
    const char *name;
    /* The new import address. */
    void *replacement;
    /* Optional: out pointer to old value.  if there are multiple imports for
     * the same symbol, only one address is returned (hopefully they are all
     * equal) */
    void *old_ptr;
};

/* Directly modify the GOT/PLT entries from a specified image corresponding to
 * specified symbols.
 *
 * This can be used to 'hook' functions or even exported variables.  Compared
 * to substitute_hook_functions, it has the following advantages:
 *
 * - Because it does not require the ability to patch executable code;
 *   accordingly, it can (from a technical rather than policy perspective) be
 *   used in sandboxed environments like iOS or PaX MPROTECT.
 * - On platforms without RELRO or similar, it is thread safe, as the patches
 *   are done using atomic instructions.
 * - It does not require architecture specific code.
 * - It can be used to modify a single library's view of the world without
 *   affecting the rest of the program.
 *
 * ...and the following disadvantages:
 *
 * - It only works for exported functions, and even then will not catch calls
 *   from a library to its own exported functions.
 * - At present, it *only* works for a single importing library at a time.
 *   Although it is not difficult on most platforms to iterate loaded libraries
 *   in order to hook all of them, substitute does not currently provide this
 *   functionality, traversing all libraries' symbol tables may be slow, and in
 *   any case there is the matter of new importers being loaded after the fact.
 *
 * @handle   handle of the importing library
 * @hooks    see struct substitute_import_hook
 * @nhooks   number of hooks
 * @options  options - pass 0.
 * @return   SUBSTITUTE_OK
 *           SUBSTITUTE_ERR_UNKNOWN_RELOCATION_TYPE
 *           SUBSTITUTE_ERR_VM - in the future with RELRO on Linux
 */
int substitute_interpose_imports(const struct substitute_image *handle,
                                 const struct substitute_import_hook *hooks,
                                 size_t nhooks, int options);


#endif /* 1 */

#if defined(__APPLE__) && __BLOCKS__
#include <objc/runtime.h>
/* Hook a method implementation for a given Objective-C class.  By itself, this
 * function is thread safe: it is simply a wrapper for the atomic Objective-C
 * runtime call class_replaceMethod, plus the superclass-call generation
 * functionality described below, and some code to ensure atomicity if the
 * method is called while the function is in progress.  However, it will race
 * with code that modifies class methods without using atomic runtime calls,
 * such as Substrate.
 *
 * @klass            the class
 * @selector         the selector
 * @replacement      the new implementation
 * @old_ptr          optional - out pointer to the 'old implementation'.
 *                   If there is no old implementation, a custom IMP is
 *                   returned that delegates to the superclass.  This IMP is
 *                   created with imp_implementationWithBlock, so it can be
 *                   freed if desired with imp_removeBlock.
 * @created_imp_ptr  optional - out pointer to whether a fake superclass-call
 *                   IMP has been placed in <old_ptr>
 *
 * @return           SUBSTITUTE_OK
 *                   SUBSTITUTE_ERR_NO_SUCH_SELECTOR
 */
int substitute_hook_objc_message(Class klass, SEL selector, IMP replacement,
                                 IMP *old_ptr, bool *created_imp_ptr);
#endif

#ifdef __cplusplus
} /* extern */
#endif