-
Notifications
You must be signed in to change notification settings - Fork 249
/
Copy pathinject-arm.c
348 lines (303 loc) · 10.9 KB
/
inject-arm.c
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/user.h>
#include <wait.h>
#include "utils.h"
#include "ptrace.h"
/*
* injectSharedLibrary()
*
* This is the code that will actually be injected into the target process.
* This code is responsible for loading the shared library into the target
* process' address space. First, it calls malloc() to allocate a buffer to
* hold the filename of the library to be loaded. Then, it calls
* __libc_dlopen_mode(), libc's implementation of dlopen(), to load the desired
* shared library. Finally, it calls free() to free the buffer containing the
* library name. Each time it needs to give control back to the injector
* process, it breaks back in by calling raise() to produce a SIGTRAP signal.
* See the comments below for more details on how this works.
*
*/
void injectSharedLibrary(long mallocaddr, long freeaddr, long dlopenaddr)
{
// r1 = address of raise()
// r2 = address of malloc()
// r3 = address of __libc_dlopen_mode()
// r4 = address of free()
// r5 = size of the path to the shared library we want to load
//
// unfortunately, each function call we make will wipe out these
// register values, so in order to avoid losing the function addresses,
// we need to save them on the stack.
//
// here's the sequence of calls we're going to make:
//
// * malloc() - allocate a buffer to store the path to the shared
// library we're injecting
//
// * raise() - raise a SIGTRAP signal to break into the target process
// so that we can check the return value of malloc() in order to know
// where to copy the shared library path to
//
// * __libc_dlopen_mode() - load the shared library
//
// * raise() - raise a SIGTRAP signal to break into the target process
// to check the return value of __libc_dlopen_mode() in order to see
// whether it succeeded
//
// * free() - free the buffer containing the path to the shared library
//
// * raise() - raise a SIGTRAP signal to break into the target process
// so that we can restore the parts of memory that we overwrote
//
// we need to push the addresses of the functions we want to call in
// the reverse of the order we want to call them in (except for the
// first call):
asm("push {r1}"); // raise()
asm("push {r4}"); // free()
asm("push {r1}"); // raise()
asm("push {r3}"); // __libc_dlopen_mode()
asm("push {r1}"); // raise()
// call malloc() to allocate a buffer to store the path to the shared
// library to inject.
asm(
// choose the amount of memory to allocate with malloc() based
// on the size of the path to the shared library passed via r5
"mov r0, r5 \n"
// call malloc(), whose address is already in r2
"blx r2 \n"
// copy the return value (which is in r0) into r5 so that it
// doesn't get wiped out later
"mov r5, r0"
);
// call raise(SIGTRAP) to get back control of the target.
asm(
// pop off the stack to get the address of raise()
"pop {r1} \n"
// specify SIGTRAP as the first argument
"mov r0, #5 \n"
// call raise()
"blx r1"
);
// call __libc_dlopen_mode() to actually load the shared library.
asm(
// pop off the stack to get the address of __libc_dlopen_mode()
"pop {r2} \n"
// copy r5 (the address of the malloc'd buffer) into r0 to make
// it the first argument to __libc_dlopen_mode()
"mov r0, r5 \n"
// set the second argument to RTLD_LAZY
"mov r1, #1 \n"
// call __libc_dlopen_mode()
"blx r2 \n"
// copy the return value (which is in r0) into r4 so that it
// doesn't get wiped out later
"mov r4, r0"
);
// call raise(SIGTRAP) to get back control of the target.
asm(
// pop off the stack to get the address of raise()
"pop {r1} \n"
// specify SIGTRAP as the first argument
"mov r0, #5 \n"
// call raise()
"blx r1"
);
// call free() in order to free the buffer containing the path to the
// shared library.
asm(
// pop off the stack to get the address of free()
"pop {r2} \n"
// copy r5 (the malloc'd buffer) into r0 to make it the first
// argument to free()
"mov r0, r5 \n"
// call __libc_dlopen_mode()
"blx r2 \n"
// copy return value r0 into r4 so that it doesn't get wiped
// out later
"mov r4, r0"
);
// call raise(SIGTRAP) to get back control of the target.
asm(
// pop off the stack to get the address of raise()
"pop {r1} \n"
// specify SIGTRAP as the first argument
"mov r0, #5 \n"
// call raise()
"blx r1"
);
}
/*
* injectSharedLibrary_end()
*
* This function's only purpose is to be contiguous to injectSharedLibrary(),
* so that we can use its address to more precisely figure out how long
* injectSharedLibrary() is.
*
*/
void injectSharedLibrary_end()
{
}
int main(int argc, char** argv)
{
if(argc < 4)
{
usage(argv[0]);
return 1;
}
char* command = argv[1];
char* commandArg = argv[2];
char* libname = argv[3];
char* libPath = realpath(libname, NULL);
char* processName = NULL;
pid_t target = 0;
if(!libPath)
{
fprintf(stderr, "can't find file \"%s\"\n", libname);
return 1;
}
if(!strcmp(command, "-n"))
{
processName = commandArg;
target = findProcessByName(processName);
if(target == -1)
{
fprintf(stderr, "doesn't look like a process named \"%s\" is running right now\n", processName);
return 1;
}
printf("targeting process \"%s\" with pid %d\n", processName, target);
}
else if(!strcmp(command, "-p"))
{
target = atoi(commandArg);
printf("targeting process with pid %d\n", target);
}
else
{
usage(argv[0]);
return 1;
}
int libPathLength = strlen(libPath) + 1;
int mypid = getpid();
long mylibcaddr = getlibcaddr(mypid);
// find the addresses of the syscalls that we'd like to use inside the
// target, as loaded inside THIS process (i.e. NOT the target process)
long mallocAddr = getFunctionAddress("malloc");
long freeAddr = getFunctionAddress("free");
long dlopenAddr = getFunctionAddress("__libc_dlopen_mode");
long raiseAddr = getFunctionAddress("raise");
// use the base address of libc to calculate offsets for the syscalls
// we want to use
long mallocOffset = mallocAddr - mylibcaddr;
long freeOffset = freeAddr - mylibcaddr;
long dlopenOffset = dlopenAddr - mylibcaddr;
long raiseOffset = raiseAddr - mylibcaddr;
// get the target process' libc address and use it to find the
// addresses of the syscalls we want to use inside the target process
long targetLibcAddr = getlibcaddr(target);
long targetMallocAddr = targetLibcAddr + mallocOffset;
long targetFreeAddr = targetLibcAddr + freeOffset;
long targetDlopenAddr = targetLibcAddr + dlopenOffset;
long targetRaiseAddr = targetLibcAddr + raiseOffset;
struct user_regs oldregs, regs;
memset(&oldregs, 0, sizeof(struct user_regs));
memset(®s, 0, sizeof(struct user_regs));
ptrace_attach(target);
ptrace_getregs(target, &oldregs);
memcpy(®s, &oldregs, sizeof(struct user_regs));
// find a good address to copy code to
long addr = freespaceaddr(target) + sizeof(long);
// now that we have an address to copy code to, set the target's
// program counter to it.
//
// subtract 4 bytes from the actual address, because ARM's PC actually
// refers to the next instruction rather than the current instruction.
regs.uregs[15] = addr - 4;
// pass arguments to my function injectSharedLibrary() by loading them
// into the right registers. see comments in injectSharedLibrary() for
// more details.
regs.uregs[1] = targetRaiseAddr;
regs.uregs[2] = targetMallocAddr;
regs.uregs[3] = targetDlopenAddr;
regs.uregs[4] = targetFreeAddr;
regs.uregs[5] = libPathLength;
ptrace_setregs(target, ®s);
// figure out the size of injectSharedLibrary() so we know how big of a buffer to allocate.
size_t injectSharedLibrary_size = (intptr_t)injectSharedLibrary_end - (intptr_t)injectSharedLibrary;
// back up whatever data used to be at the address we want to modify.
char* backup = malloc(injectSharedLibrary_size * sizeof(char));
ptrace_read(target, addr, backup, injectSharedLibrary_size);
// set up a buffer containing the code that we'll inject into the target process.
char* newcode = malloc(injectSharedLibrary_size * sizeof(char));
memset(newcode, 0, injectSharedLibrary_size * sizeof(char));
// copy the code of injectSharedLibrary() to the buffer.
memcpy(newcode, injectSharedLibrary, injectSharedLibrary_size);
// copy injectSharedLibrary()'s code to the target address inside the
// target process' address space.
ptrace_write(target, addr, newcode, injectSharedLibrary_size);
// now that the new code is in place, let the target run our injected code.
ptrace_cont(target);
// at this point, the target should have run malloc(). check its return
// value to see if it succeeded, and bail out cleanly if it didn't.
struct user_regs malloc_regs;
memset(&malloc_regs, 0, sizeof(struct user_regs));
ptrace_getregs(target, &malloc_regs);
unsigned long long targetBuf = malloc_regs.uregs[5];
// if r5 is 0 here, then malloc failed, and we should bail out cleanly.
if(targetBuf == 0)
{
fprintf(stderr, "malloc() failed to allocate memory\n");
restoreStateAndDetach(target, addr, backup, injectSharedLibrary_size, oldregs);
free(backup);
free(newcode);
return 1;
}
// if we get here, then malloc likely succeeded, so now we need to copy
// the path to the shared library we want to inject into the buffer
// that the target process just malloc'd. this is needed so that it can
// be passed as an argument to __libc_dlopen_mode later on.
// read the buffer returned by malloc() and copy the name of our shared
// library to that address inside the target process.
ptrace_write(target, targetBuf, libPath, libPathLength);
// continue the target's execution again in order to call
// __libc_dlopen_mode.
ptrace_cont(target);
// check out what the registers look like after calling
// __libc_dlopen_mode.
struct user_regs dlopen_regs;
memset(&dlopen_regs, 0, sizeof(struct user_regs));
ptrace_getregs(target, &dlopen_regs);
unsigned long long libAddr = dlopen_regs.uregs[4];
// if r4 is 0 here, then __libc_dlopen_mode() failed, and we should
// bail out cleanly.
if(libAddr == 0)
{
fprintf(stderr, "__libc_dlopen_mode() failed to load %s\n", libname);
restoreStateAndDetach(target, addr, backup, injectSharedLibrary_size, oldregs);
free(backup);
free(newcode);
return 1;
}
// now check /proc/pid/maps to see whether injection was successful.
if(checkloaded(target, libname))
{
printf("\"%s\" successfully injected\n", libname);
}
else
{
fprintf(stderr, "could not inject \"%s\"\n", libname);
}
// as a courtesy, free the buffer that we allocated inside the target
// process. we don't really care whether this succeeds, so don't
// bother checking the return value.
ptrace_cont(target);
// at this point, if everything went according to plan, we've loaded
// the shared library inside the target process, so we're done. restore
// the old state and detach from the target.
restoreStateAndDetach(target, addr, backup, injectSharedLibrary_size, oldregs);
free(backup);
free(newcode);
return 0;
}