-
Notifications
You must be signed in to change notification settings - Fork 6
/
rickroll.c
167 lines (134 loc) · 5.13 KB
/
rickroll.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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/string.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kamal Marhubi");
MODULE_DESCRIPTION("Rickroll module");
static char *rickroll_filename = NULL;
/*
* Set up a module parameter for the filename. The arguments are variable name,
* type, and permissions permissions for the parameter file in sysfs, something
* like
*
* /sys/module/<module_name>/parameters/<parameter_name>
*
* We're setting it writeable by root so it can be modified without reloading
* the module.
*/
module_param(rickroll_filename, charp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(rickroll_filename, "The location of the rick roll file");
/*
* cr0 is an x86 control register, and the bit we're twiddling here controls
* the write protection. We need to do this because the area of memory
* containing the system call table is write protected, and modifying it would
* cause a protection fault.
*/
#define DISABLE_WRITE_PROTECTION (write_cr0(read_cr0() & (~ 0x10000)))
#define ENABLE_WRITE_PROTECTION (write_cr0(read_cr0() | 0x10000))
static unsigned long **find_sys_call_table(void);
asmlinkage long rickroll_open(const char __user *filename, int flags, umode_t mode);
asmlinkage long (*original_sys_open)(const char __user *, int, umode_t);
asmlinkage unsigned long **sys_call_table;
/**
* Replace the open system call with our rickrolling version.
*/
static int __init rickroll_init(void) {
if (!rickroll_filename) {
/* TODO: could check the file exists here */
printk(KERN_ERR "No rick roll filename given.");
return -EINVAL; /* invalid argument */
}
sys_call_table = find_sys_call_table();
if (!sys_call_table) {
printk(KERN_ERR "Couldn't find sys_call_table.\n");
return -EPERM; /* operation not permitted; couldn't find general error */
}
/*
* Replace the entry for open with our own function. We save the location
* of the real sys_open so we can put it back when we're unloaded.
*/
DISABLE_WRITE_PROTECTION;
original_sys_open = (void *) sys_call_table[__NR_open];
sys_call_table[__NR_open] = (unsigned long *) rickroll_open;
ENABLE_WRITE_PROTECTION;
printk(KERN_INFO "Never gonna give you up!\n");
return 0; /* zero indicates success */
}
/**
* Open a file, rickrolling if its extension is '.mp3'.
*
* This forwards to the real sys_open unless the file name ends with .mp3, in
* which case it opens rickroll_filename instead.
*/
asmlinkage long rickroll_open(
const char __user *filename, int flags, umode_t mode) {
int len = strlen(filename);
/* See if we should hijack the open */
if (strcmp(filename + len - 4, ".mp3")) {
/* Just pass through to the real sys_open if the extension isn't .mp3 */
return (*original_sys_open)(filename, flags, mode);
} else {
/* Otherwise we're going to hijack the open */
mm_segment_t old_fs;
long fd;
/*
* sys_open checks to see if the filename is a pointer to user space
* memory. When we're hijacking, the filename we pass will be in kernel
* memory. To get around this, we juggle some segment registers. I
* believe fs is the segment used for user space, and we're temporarily
* changing it to be the segment the kernel uses.
*
* An alternative would be to use read_from_user() and copy_to_user()
* and place the rickroll filename at the location the user code passed
* in, saving and restoring the memory we overwrite.
*/
old_fs = get_fs();
set_fs(KERNEL_DS);
/* Open the rickroll file instead */
fd = (*original_sys_open)(rickroll_filename, flags, mode);
/*
* TODO: since the filename can be written from sysfs, if we decide to
* add a check that the file exists on module load, should we also
* enforce it here and return an error if it doesn't?
*/
/* Restore fs to its original value */
set_fs(old_fs);
return fd;
}
}
/**
* Restore the original sys_open.
*/
static void __exit rickroll_cleanup(void) {
printk(KERN_INFO "Ok, now we're gonna give you up. Sorry.\n");
DISABLE_WRITE_PROTECTION;
sys_call_table[__NR_open] = (unsigned long *) original_sys_open;
ENABLE_WRITE_PROTECTION;
}
/**
* Find the system call table's location in memory.
*
* This is necessary because the sys_call_table symbol is not exported. We find
* it by iterating through kernel space memory, and looking for a known system
* call's address. We use sys_close because all the examples I saw used
* sys_close. Since we know the offset of the pointer to sys_close in the table
* (__NR_close), we can get the table's base address.
*/
static unsigned long **find_sys_call_table() {
unsigned long offset;
unsigned long **sct;
for (offset = PAGE_OFFSET; offset < ULLONG_MAX; offset += sizeof(void *)) {
sct = (unsigned long **) offset;
if(sct[__NR_close] == (unsigned long *) sys_close) return sct;
}
/*
* Given the loop limit, it's somewhat unlikely we'll get here. I don't
* even know if we can attempt to fetch such high addresses from memory,
* and even if you can, it will take a while!
*/
return NULL;
}
module_init(rickroll_init);
module_exit(rickroll_cleanup);