-
Notifications
You must be signed in to change notification settings - Fork 538
/
syscall-steal.c
299 lines (252 loc) · 8.51 KB
/
syscall-steal.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
/*
* syscall-steal.c
*
* System call "stealing" sample.
*
* Disables page protection at a processor level by changing the 16th bit
* in the cr0 register (could be Intel specific).
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h> /* which will have params */
#include <linux/unistd.h> /* The list of system calls */
#include <linux/cred.h> /* For current_uid() */
#include <linux/uidgid.h> /* For __kuid_val() */
#include <linux/version.h>
/* For the current (process) structure, we need this to know who the
* current user is.
*/
#include <linux/sched.h>
#include <linux/uaccess.h>
/* The way we access "sys_call_table" varies as kernel internal changes.
* - Prior to v5.4 : manual symbol lookup
* - v5.5 to v5.6 : use kallsyms_lookup_name()
* - v5.7+ : Kprobes or specific kernel module parameter
*/
/* The in-kernel calls to the ksys_close() syscall were removed in Linux v5.11+.
*/
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0))
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 4, 0)
#define HAVE_KSYS_CLOSE 1
#include <linux/syscalls.h> /* For ksys_close() */
#else
#include <linux/kallsyms.h> /* For kallsyms_lookup_name */
#endif
#else
#if defined(CONFIG_KPROBES)
#define HAVE_KPROBES 1
#if defined(CONFIG_X86_64)
/* If you have tried to use the syscall table to intercept syscalls and it
* doesn't work, you can try to use Kprobes to intercept syscalls.
* Set USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL to 1 to register a pre-handler
* before the syscall.
*/
#define USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL 0
#endif
#include <linux/kprobes.h>
#else
#define HAVE_PARAM 1
#include <linux/kallsyms.h> /* For sprint_symbol */
/* The address of the sys_call_table, which can be obtained with looking up
* "/boot/System.map" or "/proc/kallsyms". When the kernel version is v5.7+,
* without CONFIG_KPROBES, you can input the parameter or the module will look
* up all the memory.
*/
static unsigned long sym = 0;
module_param(sym, ulong, 0644);
#endif /* CONFIG_KPROBES */
#endif /* Version < v5.7 */
/* UID we want to spy on - will be filled from the command line. */
static uid_t uid = -1;
module_param(uid, int, 0644);
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
/* syscall_sym is the symbol name of the syscall to spy on. The default is
* "__x64_sys_openat", which can be changed by the module parameter. You can
* look up the symbol name of a syscall in /proc/kallsyms.
*/
static char *syscall_sym = "__x64_sys_openat";
module_param(syscall_sym, charp, 0644);
static int sys_call_kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
if (__kuid_val(current_uid()) != uid) {
return 0;
}
pr_info("%s called by %d\n", syscall_sym, uid);
return 0;
}
static struct kprobe syscall_kprobe = {
.symbol_name = "__x64_sys_openat",
.pre_handler = sys_call_kprobe_pre_handler,
};
#else
static unsigned long **sys_call_table_stolen;
/* A pointer to the original system call. The reason we keep this, rather
* than call the original function (sys_openat), is because somebody else
* might have replaced the system call before us. Note that this is not
* 100% safe, because if another module replaced sys_openat before us,
* then when we are inserted, we will call the function in that module -
* and it might be removed before we are.
*
* Another reason for this is that we can not get sys_openat.
* It is a static variable, so it is not exported.
*/
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long (*original_call)(const struct pt_regs *);
#else
static asmlinkage long (*original_call)(int, const char __user *, int, umode_t);
#endif
/* The function we will replace sys_openat (the function called when you
* call the open system call) with. To find the exact prototype, with
* the number and type of arguments, we find the original function first
* (it is at fs/open.c).
*
* In theory, this means that we are tied to the current version of the
* kernel. In practice, the system calls almost never change (it would
* wreck havoc and require programs to be recompiled, since the system
* calls are the interface between the kernel and the processes).
*/
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long our_sys_openat(const struct pt_regs *regs)
#else
static asmlinkage long our_sys_openat(int dfd, const char __user *filename,
int flags, umode_t mode)
#endif
{
int i = 0;
char ch;
if (__kuid_val(current_uid()) != uid)
goto orig_call;
/* Report the file, if relevant */
pr_info("Opened file by %d: ", uid);
do {
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
get_user(ch, (char __user *)regs->si + i);
#else
get_user(ch, (char __user *)filename + i);
#endif
i++;
pr_info("%c", ch);
} while (ch != 0);
pr_info("\n");
orig_call:
/* Call the original sys_openat - otherwise, we lose the ability to
* open files.
*/
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
return original_call(regs);
#else
return original_call(dfd, filename, flags, mode);
#endif
}
static unsigned long **acquire_sys_call_table(void)
{
#ifdef HAVE_KSYS_CLOSE
unsigned long int offset = PAGE_OFFSET;
unsigned long **sct;
while (offset < ULLONG_MAX) {
sct = (unsigned long **)offset;
if (sct[__NR_close] == (unsigned long *)ksys_close)
return sct;
offset += sizeof(void *);
}
return NULL;
#endif
#ifdef HAVE_PARAM
const char sct_name[15] = "sys_call_table";
char symbol[40] = { 0 };
if (sym == 0) {
pr_alert("For Linux v5.7+, Kprobes is the preferable way to get "
"symbol.\n");
pr_info("If Kprobes is absent, you have to specify the address of "
"sys_call_table symbol\n");
pr_info("by /boot/System.map or /proc/kallsyms, which contains all the "
"symbol addresses, into sym parameter.\n");
return NULL;
}
sprint_symbol(symbol, sym);
if (!strncmp(sct_name, symbol, sizeof(sct_name) - 1))
return (unsigned long **)sym;
return NULL;
#endif
#ifdef HAVE_KPROBES
unsigned long (*kallsyms_lookup_name)(const char *name);
struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name",
};
if (register_kprobe(&kp) < 0)
return NULL;
kallsyms_lookup_name = (unsigned long (*)(const char *name))kp.addr;
unregister_kprobe(&kp);
#endif
return (unsigned long **)kallsyms_lookup_name("sys_call_table");
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
static inline void __write_cr0(unsigned long cr0)
{
asm volatile("mov %0,%%cr0" : "+r"(cr0) : : "memory");
}
#else
#define __write_cr0 write_cr0
#endif
static void enable_write_protection(void)
{
unsigned long cr0 = read_cr0();
set_bit(16, &cr0);
__write_cr0(cr0);
}
static void disable_write_protection(void)
{
unsigned long cr0 = read_cr0();
clear_bit(16, &cr0);
__write_cr0(cr0);
}
#endif
static int __init syscall_steal_start(void)
{
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
int err;
/* use symbol name from the module parameter */
syscall_kprobe.symbol_name = syscall_sym;
err = register_kprobe(&syscall_kprobe);
if (err) {
pr_err("register_kprobe() on %s failed: %d\n", syscall_sym, err);
pr_err("Please check the symbol name from 'syscall_sym' parameter.\n");
return err;
}
#else
if (!(sys_call_table_stolen = acquire_sys_call_table()))
return -1;
disable_write_protection();
/* keep track of the original open function */
original_call = (void *)sys_call_table_stolen[__NR_openat];
/* use our openat function instead */
sys_call_table_stolen[__NR_openat] = (unsigned long *)our_sys_openat;
enable_write_protection();
#endif
pr_info("Spying on UID:%d\n", uid);
return 0;
}
static void __exit syscall_steal_end(void)
{
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
unregister_kprobe(&syscall_kprobe);
#else
if (!sys_call_table_stolen)
return;
/* Return the system call back to normal */
if (sys_call_table_stolen[__NR_openat] != (unsigned long *)our_sys_openat) {
pr_alert("Somebody else also played with the ");
pr_alert("open system call\n");
pr_alert("The system may be left in ");
pr_alert("an unstable state.\n");
}
disable_write_protection();
sys_call_table_stolen[__NR_openat] = (unsigned long *)original_call;
enable_write_protection();
#endif
msleep(2000);
}
module_init(syscall_steal_start);
module_exit(syscall_steal_end);
MODULE_LICENSE("GPL");