forked from gaffe23/linux-inject
-
Notifications
You must be signed in to change notification settings - Fork 0
/
inject-x86_64.c
329 lines (284 loc) · 11.3 KB
/
inject-x86_64.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
#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 executing an "int $3" instruction. See the
* comments below for more details on how this works.
*
*/
void injectSharedLibrary(long mallocaddr, long freeaddr, long dlopenaddr)
{
// here are the assumptions I'm making about what data will be located
// where at the time the target executes this code:
//
// rdi = address of malloc() in target process
// rsi = address of free() in target process
// rdx = address of __libc_dlopen_mode() in target process
// rcx = size of the path to the shared library we want to load
// save addresses of free() and __libc_dlopen_mode() on the stack for later use
asm(
// rsi is going to contain the address of free(). it's going to get wiped
// out by the call to malloc(), so save it on the stack for later
"push %rsi \n"
// same thing for rdx, which will contain the address of _dl_open()
"push %rdx"
);
// call malloc() from within the target process
asm(
// save previous value of r9, because we're going to use it to call malloc()
"push %r9 \n"
// now move the address of malloc() into r9
"mov %rdi,%r9 \n"
// choose the amount of memory to allocate with malloc() based on the size
// of the path to the shared library passed via rcx
"mov %rcx,%rdi \n"
// now call r9; malloc()
"callq *%r9 \n"
// after returning from malloc(), pop the previous value of r9 off the stack
"pop %r9 \n"
// break in so that we can see what malloc() returned
"int $3"
);
// call __libc_dlopen_mode() to load the shared library
asm(
// get the address of __libc_dlopen_mode() off of the stack so we can call it
"pop %rdx \n"
// as before, save the previous value of r9 on the stack
"push %r9 \n"
// copy the address of __libc_dlopen_mode() into r9
"mov %rdx,%r9 \n"
// 1st argument to __libc_dlopen_mode(): filename = the address of the buffer returned by malloc()
"mov %rax,%rdi \n"
// 2nd argument to __libc_dlopen_mode(): flag = RTLD_LAZY
"movabs $1,%rsi \n"
// call __libc_dlopen_mode()
"callq *%r9 \n"
// restore old r9 value
"pop %r9 \n"
// break in so that we can see what __libc_dlopen_mode() returned
"int $3"
);
// call free() to free the buffer we allocated earlier.
//
// Note: I found that if you put a nonzero value in r9, free() seems to
// interpret that as an address to be freed, even though it's only
// supposed to take one argument. As a result, I had to call it using a
// register that's not used as part of the x64 calling convention. I
// chose rbx.
asm(
// at this point, rax should still contain our malloc()d buffer from earlier.
// we're going to free it, so move rax into rdi to make it the first argument to free().
"mov %rax,%rdi \n"
// pop rsi so that we can get the address to free(), which we pushed onto the stack a while ago.
"pop %rsi \n"
// save previous rbx value
"push %rbx \n"
// load the address of free() into rbx
"mov %rsi,%rbx \n"
// zero out rsi, because free() might think that it contains something that should be freed
"xor %rsi,%rsi \n"
// break in so that we can check out the arguments right before making the call
"int $3 \n"
// call free()
"callq *%rbx \n"
// restore previous rbx value
"pop %rbx"
);
// we already overwrote the RET instruction at the end of this function
// with an INT 3, so at this point the injector will regain control of
// the target's execution.
}
/*
* 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");
// 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;
// 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;
struct user_regs_struct oldregs, regs;
memset(&oldregs, 0, sizeof(struct user_regs_struct));
memset(®s, 0, sizeof(struct user_regs_struct));
ptrace_attach(target);
ptrace_getregs(target, &oldregs);
memcpy(®s, &oldregs, sizeof(struct user_regs_struct));
// 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 rip to
// it. we have to advance by 2 bytes here because rip gets incremented
// by the size of the current instruction, and the instruction at the
// start of the function to inject always happens to be 2 bytes long.
regs.rip = addr + 2;
// pass arguments to my function injectSharedLibrary() by loading them
// into the right registers. note that this will definitely only work
// on x64, because it relies on the x64 calling convention, in which
// arguments are passed via registers rdi, rsi, rdx, rcx, r8, and r9.
// see comments in injectSharedLibrary() for more details.
regs.rdi = targetMallocAddr;
regs.rsi = targetFreeAddr;
regs.rdx = targetDlopenAddr;
regs.rcx = 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;
// also figure out where the RET instruction at the end of
// injectSharedLibrary() lies so that we can overwrite it with an INT 3
// in order to break back into the target process. note that on x64,
// gcc and clang both force function addresses to be word-aligned,
// which means that functions are padded with NOPs. as a result, even
// though we've found the length of the function, it is very likely
// padded with NOPs, so we need to actually search to find the RET.
intptr_t injectSharedLibrary_ret = (intptr_t)findRet(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 to hold the code we're going to 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 a buffer.
memcpy(newcode, injectSharedLibrary, injectSharedLibrary_size - 1);
// overwrite the RET instruction with an INT 3.
newcode[injectSharedLibrary_ret] = INTEL_INT3_INSTRUCTION;
// 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_struct malloc_regs;
memset(&malloc_regs, 0, sizeof(struct user_regs_struct));
ptrace_getregs(target, &malloc_regs);
unsigned long long targetBuf = malloc_regs.rax;
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 current value of rax, which contains malloc's return value,
// 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 dlopen.
struct user_regs_struct dlopen_regs;
memset(&dlopen_regs, 0, sizeof(struct user_regs_struct));
ptrace_getregs(target, &dlopen_regs);
unsigned long long libAddr = dlopen_regs.rax;
// if rax 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;
}