-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathsubstitute.h
309 lines (274 loc) · 12.5 KB
/
substitute.h
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
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>
#include <stdbool.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 = 1,
/* 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 = 2,
/* substitute_hook_functions: can't patch a function because one of the
* instructions within the patch region (other than the last instruction)
* is a call - meaning that a return address within the region (i.e. about
* to point to clobbered code) could be on some thread's stack, where we
* can't easily find and patch it. This check is skipped if
* SUBSTITUTE_NO_THREAD_SAFETY is set. */
SUBSTITUTE_ERR_FUNC_CALLS_AT_START = 3,
/* 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 = 4,
/* out of memory */
SUBSTITUTE_ERR_OOM = 5,
/* substitute_hook_functions: mmap, mprotect, vm_copy, or
* vm_remap failure
* substitute_hook_objc_message: vm_remap failure
* Most likely to come up with substitute_hook_functions if the kernel is
* preventing pages from being marked executable. */
SUBSTITUTE_ERR_VM = 6,
/* substitute_hook_functions: not on the main thread, and you did not pass
* SUBSTITUTE_NO_THREAD_SAFETY */
SUBSTITUTE_ERR_NOT_ON_MAIN_THREAD = 7,
/* substitute_hook_functions: when trying to patch the PC of other threads
* (in case they were inside the patched prolog when they were suspended),
* found a PC that was in the patch region but seemingly not at an
* instruction boundary
* The hooks were otherwise completed, but the thread in question will
* probably crash now that its code has changed under it. */
SUBSTITUTE_ERR_UNEXPECTED_PC_ON_OTHER_THREAD = 8,
/* substitute_hook_functions: destination was out of range, and mmap
* wouldn't give us a trampoline in range */
SUBSTITUTE_ERR_OUT_OF_RANGE = 9,
/* substitute_interpose_imports: couldn't redo relocation for an import
* because the type was unknown */
SUBSTITUTE_ERR_UNKNOWN_RELOCATION_TYPE = 10,
/* substitute_hook_objc_message: no such selector existed in the class's
* inheritance tree */
SUBSTITUTE_ERR_NO_SUCH_SELECTOR = 11,
/* substitute_hook_functions: OS error suspending other threads */
SUBSTITUTE_ERR_ADJUSTING_THREADS = 12,
_SUBSTITUTE_CURRENT_MAX_ERR_PLUS_ONE,
};
/* Get a string representation for a SUBSTITUTE_* error code. */
const char *substitute_strerror(int err);
struct substitute_function_hook {
/* The function to hook. (On ARM, Thumb functions are indicated as usual
* for function pointers.) */
void *function;
/* The replacement function. */
void *replacement;
/* Optional: out *pointer* to function pointer to call old implementation
* (i.e. given 'void (*old_foo)(...);', pass &old_foo) */
void *old_ptr;
/* Currently unused; pass 0. (Protip: When using C {} struct initializer
* syntax, you can just omit this.) */
int options;
};
/* substitute_hook_functions options */
enum {
SUBSTITUTE_NO_THREAD_SAFETY = 1,
};
/* Patch the machine code of the specified functions to redirect them to the
* specified replacements.
*
* After hooking, you can use the function pointer written to 'old_ptr' to call
* the original implementation. (It points to a trampoline that executes the
* original first few instructions, which were written over in the real
* function, then jumps there for the rest.)
*
* This function must be called from the main thread. In return, it attempts
* to be atomic in the face of concurrent calls to the functions being hooked.
* Since there is no way to do that directly, it resorts to pausing all other
* threads while doing its job; and since there is no way to do *that*
* atomically on currently supported platforms, it does so by pausing each
* thread one at a time. If multiple threads each tried to pause each other
* this way, the process would be deadlocked, so mutual exclusion must be
* implicitly provided by running on the main thread.
*
* You can disable the main thread check and all synchronization by passing
* SUBSTITUTE_NO_THREAD_SAFETY.
*
* Why not just use a mutex to prevent deadlock? That would work between
* multiple calls into libsubstitute, but there may be other libraries that
* want to do the same thing and would not know about our mutex. My hope is
* that using the main thread is sufficiently natural that the author of any
* other similar library which cares about atomicity, noticing the same
* concern, would independently come up with the same restriction - at least,
* if they do not find an easier method to avoid deadlocks. Note that all
* existing hooking libraries I know of make no attempt to do any
* synchronization at all; this is fine if hooking is only done during
* initialization while the process is single threaded, but I want to properly
* support dynamic injection. (Note - if there is such an easier method on OS
* X that does not involve spawning a separate process, I'd be curious to hear
* about it.)
*
*
* @hooks see struct substitute_function_hook
* @nhooks number of hooks
* @recordp if non-NULL, on success receives a pointer that can be used to
* cleanly undo the hooks; currently unimplemented, so pass NULL
* @options options - see above
* @return SUBSTITUTE_OK, or any of most of the SUBSTITUTE_ERR_*
*/
struct substitute_function_hook_record;
int substitute_hook_functions(const struct substitute_function_hook *hooks,
size_t nhooks,
struct substitute_function_hook_record **recordp,
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 void *, one per name; on return, each entry will be
* filled in with the corresponding symbol address, or NULL if the
* symbol wasn't found
* (on ARM, this will be | 1 for Thumb functions)
* @nsyms number of names
*
* @return SUBSTITUTE_OK (maybe errors in the future)
*/
int substitute_find_private_syms(struct substitute_image *handle,
const char **__restrict names,
void **__restrict 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;
/* Currently unused; pass 0. (Protip: When using C {} struct initializer
* syntax, you can just omit this.) */
int options;
};
/* 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:
*
* - 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
* @recordp if non-NULL, on success receives a pointer that can be used to
* cleanly undo the hooks; currently unimplemented, so pass NULL
* @options options - pass 0
* @return SUBSTITUTE_OK
* SUBSTITUTE_ERR_UNKNOWN_RELOCATION_TYPE
* SUBSTITUTE_ERR_VM - in the future with RELRO on Linux
*/
struct substitute_import_hook_record;
int substitute_interpose_imports(const struct substitute_image *handle,
const struct substitute_import_hook *hooks,
size_t nhooks,
struct substitute_import_hook_record **recordp,
int options);
#endif /* 1 */
#if defined(__APPLE__)
#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 (other APIs would call this an
* IMP, but that isn't in general the real type of the
* implementation, so declared as a void * here)
* @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 can
* be freed if desired with substitute_free_created_imp.
* @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, void *replacement,
void *old_ptr, bool *created_imp_ptr);
void substitute_free_created_imp(IMP imp);
#endif
#ifdef __cplusplus
} /* extern */
#endif