-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathusctm.c
executable file
·333 lines (263 loc) · 9.31 KB
/
usctm.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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/*
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This module is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* @file usctm.c
* @brief This is the main source for the Linux Kernel Module which implements
* the runtime discovery of the syscall table position and of free entries (those
* pointing to sys_ni_syscall)
*
* @author Francesco Quaglia
*
* @date November 22, 2020
* @updated October 2, 2024
*/
#define EXPORT_SYMTAB
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/kprobes.h>
#include <linux/mutex.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/page.h>
#include <asm/cacheflush.h>
#include <asm/apic.h>
#include <linux/syscalls.h>
#include "./include/vtpmo.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Francesco Quaglia <francesco.quaglia@uniroma2.it>");
MODULE_DESCRIPTION("USCTM");
#define MODNAME "USCTM"
extern int sys_vtpmo(unsigned long vaddr);
#define ADDRESS_MASK 0xfffffffffffff000//to migrate
#define START 0xffffffff00000000ULL // use this as starting address --> this is a biased search since does not start from 0xffff000000000000
#define MAX_ADDR 0xfffffffffff00000ULL
#define FIRST_NI_SYSCALL 134
#define SECOND_NI_SYSCALL 174
#define THIRD_NI_SYSCALL 182
#define FOURTH_NI_SYSCALL 183
#define FIFTH_NI_SYSCALL 214
#define SIXTH_NI_SYSCALL 215
#define SEVENTH_NI_SYSCALL 236
#define ENTRIES_TO_EXPLORE 256
//avoid compiler warnings with the below prototypes
int good_area(unsigned long *);
int validate_page(unsigned long *);
void syscall_table_finder(void);
unsigned long *hacked_ni_syscall=NULL;
unsigned long **hacked_syscall_tbl=NULL;
unsigned long sys_call_table_address = 0x0;
module_param(sys_call_table_address, ulong, 0660);
unsigned long sys_ni_syscall_address = 0x0;
module_param(sys_ni_syscall_address, ulong, 0660);
int good_area(unsigned long * addr){
int i;
for(i=1;i<FIRST_NI_SYSCALL;i++){
if(addr[i] == addr[FIRST_NI_SYSCALL]) goto bad_area;
}
return 1;
bad_area:
return 0;
}
/* This routine checks if the page contains the begin of the syscall_table. */
int validate_page(unsigned long *addr){
int i = 0;
unsigned long page = (unsigned long) addr;
unsigned long new_page = (unsigned long) addr;
for(; i < PAGE_SIZE; i+=sizeof(void*)){
new_page = page+i+SEVENTH_NI_SYSCALL*sizeof(void*);
// If the table occupies 2 pages check if the second one is materialized in a frame
if(
( (page+PAGE_SIZE) == (new_page & ADDRESS_MASK) )
&& sys_vtpmo(new_page) == NO_MAP
)
break;
// go for patter matching
addr = (unsigned long*) (page+i);
if(
( (addr[FIRST_NI_SYSCALL] & 0x3 ) == 0 )
&& (addr[FIRST_NI_SYSCALL] != 0x0 ) // not points to 0x0
&& (addr[FIRST_NI_SYSCALL] > 0xffffffff00000000 ) // not points to a locatio lower than 0xffffffff00000000
//&& ( (addr[FIRST_NI_SYSCALL] & START) == START )
&& ( addr[FIRST_NI_SYSCALL] == addr[SECOND_NI_SYSCALL] )
&& ( addr[FIRST_NI_SYSCALL] == addr[THIRD_NI_SYSCALL] )
&& ( addr[FIRST_NI_SYSCALL] == addr[FOURTH_NI_SYSCALL] )
&& ( addr[FIRST_NI_SYSCALL] == addr[FIFTH_NI_SYSCALL] )
&& ( addr[FIRST_NI_SYSCALL] == addr[SIXTH_NI_SYSCALL] )
&& ( addr[FIRST_NI_SYSCALL] == addr[SEVENTH_NI_SYSCALL] )
&& (good_area(addr))
){
hacked_ni_syscall = (void*)(addr[FIRST_NI_SYSCALL]); // save ni_syscall
sys_ni_syscall_address = (unsigned long)hacked_ni_syscall;
hacked_syscall_tbl = (void*)(addr); // save syscall_table address
sys_call_table_address = (unsigned long) hacked_syscall_tbl;
return 1;
}
}
return 0;
}
/* This routine looks for the syscall table. */
void syscall_table_finder(void){
unsigned long k; // current page
unsigned long candidate; // current page
for(k=START; k < MAX_ADDR; k+=4096){
candidate = k;
if((sys_vtpmo(candidate) != NO_MAP)){
// check if candidate maintains the syscall_table
if(validate_page( (unsigned long *)(candidate)) ){
printk("%s: syscall table found at %px\n",MODNAME,(void*)(hacked_syscall_tbl));
printk("%s: sys_ni_syscall found at %px\n",MODNAME,(void*)(hacked_ni_syscall));
break;
}
}
}
}
#define MAX_FREE 15
int free_entries[MAX_FREE];
module_param_array(free_entries,int,NULL,0660);//default array size already known - here we expose what entries are free
#define SYS_CALL_INSTALL
#ifdef SYS_CALL_INSTALL
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
#define INST_LEN 5
char jump_inst[INST_LEN];
unsigned long x64_sys_call_addr;
int offset;
static struct kprobe kp_x64_sys_call = { .symbol_name = "x64_sys_call" };
//stuff here is using retpoline
static inline void call(struct pt_regs *regs, unsigned int nr){
asm volatile("mov (%1, %0, 8), %%rax\n\t"
"jmp __x86_indirect_thunk_rax\n\t"
:
: "r"((long)nr), "r"(hacked_syscall_tbl)
: "rax");
}
#endif
unsigned long cr0, cr4;
static inline void write_cr0_forced(unsigned long val){
unsigned long __force_order;
asm volatile("mov %0, %%cr0" : "+r"(val), "+m"(__force_order));
}
static inline void protect_memory(void){
write_cr0_forced(cr0);
}
static inline void unprotect_memory(void){
write_cr0_forced(cr0 & ~X86_CR0_WP);
}
static inline void write_cr4_forced(unsigned long val){
unsigned long __force_order;
asm volatile("mov %0, %%cr4" : "+r"(val), "+m"(__force_order));
}
static inline void conditional_cet_disable(void){
#ifdef X86_CR4_CET
if (cr4 & X86_CR4_CET)
write_cr4_forced(cr4 & ~X86_CR4_CET);
#endif
}
static inline void conditional_cet_enable(void){
#ifdef X86_CR4_CET
if (cr4 & X86_CR4_CET)
write_cr4_forced(cr4);
#endif
}
static inline void begin_syscall_table_hack(void){
preempt_disable();
cr0 = read_cr0();
cr4 = native_read_cr4();
conditional_cet_disable();
unprotect_memory();
}
static inline void end_syscall_table_hack(void){
protect_memory();
conditional_cet_enable();
preempt_enable();
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)
__SYSCALL_DEFINEx(2, _trial, unsigned long, A, unsigned long, B){
#else
asmlinkage long sys_trial(unsigned long A, unsigned long B){
#endif
printk("%s: thread %d requests a trial sys_call with %lu and %lu as parameters\n",MODNAME,current->pid,A,B);
return 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)
static unsigned long sys_trial = (unsigned long) __x64_sys_trial;
#else
#endif
#else
#endif
int init_module(void) {
int i,j;
printk("%s: initializing\n",MODNAME);
syscall_table_finder();
if(!hacked_syscall_tbl){
printk("%s: failed to find the sys_call_table\n",MODNAME);
return -1;
}
j=0;
for(i=0;i<ENTRIES_TO_EXPLORE;i++)
if(hacked_syscall_tbl[i] == hacked_ni_syscall){
printk("%s: found sys_ni_syscall entry at syscall_table[%d]\n", MODNAME,i);
free_entries[j++] = i;
if(j>=MAX_FREE) break;
}
#ifdef SYS_CALL_INSTALL
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
if (register_kprobe(&kp_x64_sys_call)) {
printk(KERN_ERR "%s: cannot register kprobe for x64_sys_call\n", MODNAME);
return -1;
}
x64_sys_call_addr = (unsigned long)kp_x64_sys_call.addr;
unregister_kprobe(&kp_x64_sys_call);
/* JMP opcode */
jump_inst[0] = 0xE9;
/* RIP points to the next instruction. Current instruction has length 5 */
offset = (unsigned long)call - x64_sys_call_addr - INST_LEN;
memcpy(jump_inst + 1, &offset, sizeof(int));
#endif
begin_syscall_table_hack();
hacked_syscall_tbl[FIRST_NI_SYSCALL] = (unsigned long*)sys_trial;
printk("%s: a sys_call with 2 parameters has been installed as a trial on the sys_call_table at displacement %d\n",MODNAME,FIRST_NI_SYSCALL);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
//these kernel versions are configured to avoid the usage of the syscall table
//this piece of code intercepts the activation of the syscall dispatcher and
//redirects control to the function that restores the usage of the syscall table
//it may be possible that I did not check all the kernel cofigurations
//the user can add here whichever configuration he wants that avoids the
//usage of the syscall table while dispatching syscalls
memcpy((unsigned char *)x64_sys_call_addr, jump_inst, INST_LEN);
#endif
end_syscall_table_hack();
#else
#endif
printk("%s: module correctly mounted\n",MODNAME);
return 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
#else
void cleanup_module(void) {
#ifdef SYS_CALL_INSTALL
begin_syscall_table_hack();
hacked_syscall_tbl[FIRST_NI_SYSCALL] = (unsigned long*)hacked_ni_syscall;
end_syscall_table_hack();
#else
#endif
printk("%s: shutting down\n",MODNAME);
}
#endif