forked from joe-lawrence/linux-inject
-
Notifications
You must be signed in to change notification settings - Fork 0
/
unject-x86_64.c
205 lines (169 loc) · 6.6 KB
/
unject-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
#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 handle, long dlcloseaddr)
{
// here are the assumptions I'm making about what data will be located
// where at the time the target executes this code:
//
// rdi = (previously injected) dynamic library handle
// rsi = address of __libc_dlclose() in target process
asm(
"callq %rsi \n"
);
// 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* strhandle = argv[3];
long handle = strtol(strhandle, NULL, 16);
char* processName = NULL;
pid_t target = 0;
if(!handle)
{
fprintf(stderr, "\tno handle!\n");
return 1;
}
if(!strcmp(command, "-n"))
{
processName = commandArg;
target = findProcessByName(processName);
if(target == -1)
{
fprintf(stderr, "\tdoesn't look like a process named \"%s\" is running right now\n", processName);
return 1;
}
printf("\ttargeting process \"%s\" with pid %d\n", processName, target);
}
else if(!strcmp(command, "-p"))
{
target = atoi(commandArg);
printf("\ttargeting process with pid %d\n", target);
}
else
{
usage(argv[0]);
return 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 dlcloseAddr = getFunctionAddress("__libc_dlclose");
// use the base address of libc to calculate offsets for the syscalls
// we want to use
long dlcloseOffset = dlcloseAddr - 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 targetDlcloseAddr = targetLibcAddr + dlcloseOffset;
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 = handle;
regs.rsi = targetDlcloseAddr;
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);
// 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 ret = dlopen_regs.rax;
// if rax is 0 here, then __libc_dlclose_mode failed, and we should bail
// out cleanly.
if(ret == 0)
{
printf("\t__libc_dlclose() unloaded handle 0x%x\n", handle);
restoreStateAndDetach(target, addr, backup, injectSharedLibrary_size, oldregs);
free(backup);
free(newcode);
return 1;
}
// 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;
}