Skip to content

Commit 9504084

Browse files
haodongnjHao Dong
andauthored
Add a hook on syscall using Kprobes under x86 (#260)
For x86 architecture, the system call table cannot be used to invoke a system call after commit 1e3ad78 since v6.9. This commit has been backported to long term stable kernels, like v5.15.154+, v6.1.85+, v6.6.26+ and v6.8.5+[1]. In this case, thanks to Kprobes, a hook can be used instead on the system call entry to intercept the system call. [1] https://stackoverflow.com/a/78607015 Co-authored-by: Hao Dong <hao.dong.work@outlook.com>
1 parent 032d1b8 commit 9504084

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

examples/syscall-steal.c

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@
4343

4444
#if defined(CONFIG_KPROBES)
4545
#define HAVE_KPROBES 1
46+
#if defined(CONFIG_X86_64)
47+
/* If you have tried to use the syscall table to intercept syscalls and it
48+
* doesn't work, you can try to use Kprobes to intercept syscalls.
49+
* Set USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL to 1 to register a pre-handler
50+
* before the syscall.
51+
*/
52+
#define USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL 0
53+
#endif
4654
#include <linux/kprobes.h>
4755
#else
4856
#define HAVE_PARAM 1
@@ -58,12 +66,37 @@ module_param(sym, ulong, 0644);
5866

5967
#endif /* Version < v5.7 */
6068

61-
static unsigned long **sys_call_table_stolen;
62-
6369
/* UID we want to spy on - will be filled from the command line. */
6470
static uid_t uid = -1;
6571
module_param(uid, int, 0644);
6672

73+
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
74+
75+
/* syscall_sym is the symbol name of the syscall to spy on. The default is
76+
* "__x64_sys_openat", which can be changed by the module parameter. You can
77+
* look up the symbol name of a syscall in /proc/kallsyms.
78+
*/
79+
static char *syscall_sym = "__x64_sys_openat";
80+
module_param(syscall_sym, charp, 0644);
81+
82+
static int sys_call_kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs)
83+
{
84+
if (__kuid_val(current_uid()) != uid) {
85+
return 0;
86+
}
87+
88+
pr_info("%s called by %d\n", syscall_sym, uid);
89+
return 0;
90+
}
91+
92+
static struct kprobe syscall_kprobe = {
93+
.symbol_name = "__x64_sys_openat",
94+
.pre_handler = sys_call_kprobe_pre_handler,
95+
};
96+
#else
97+
98+
static unsigned long **sys_call_table_stolen;
99+
67100
/* A pointer to the original system call. The reason we keep this, rather
68101
* than call the original function (sys_openat), is because somebody else
69102
* might have replaced the system call before us. Note that this is not
@@ -202,9 +235,23 @@ static void disable_write_protection(void)
202235
clear_bit(16, &cr0);
203236
__write_cr0(cr0);
204237
}
238+
#endif
205239

206240
static int __init syscall_steal_start(void)
207241
{
242+
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
243+
244+
int err;
245+
/* use symbol name from the module parameter */
246+
syscall_kprobe.symbol_name = syscall_sym;
247+
err = register_kprobe(&syscall_kprobe);
248+
if (err) {
249+
pr_err("register_kprobe() on %s failed: %d\n", syscall_sym, err);
250+
pr_err("Please check the symbol name from 'syscall_sym' parameter.\n");
251+
return err;
252+
}
253+
254+
#else
208255
if (!(sys_call_table_stolen = acquire_sys_call_table()))
209256
return -1;
210257

@@ -218,13 +265,17 @@ static int __init syscall_steal_start(void)
218265

219266
enable_write_protection();
220267

221-
pr_info("Spying on UID:%d\n", uid);
268+
#endif
222269

270+
pr_info("Spying on UID:%d\n", uid);
223271
return 0;
224272
}
225273

226274
static void __exit syscall_steal_end(void)
227275
{
276+
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
277+
unregister_kprobe(&syscall_kprobe);
278+
#else
228279
if (!sys_call_table_stolen)
229280
return;
230281

@@ -239,6 +290,7 @@ static void __exit syscall_steal_end(void)
239290
disable_write_protection();
240291
sys_call_table_stolen[__NR_openat] = (unsigned long *)original_call;
241292
enable_write_protection();
293+
#endif
242294

243295
msleep(2000);
244296
}

lkmpg.tex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,11 @@ \section{System Calls}
15661566
When A is removed, it sees that the system call was changed to \cpp|B_openat| so that it is no longer pointing to \cpp|A_openat|, so it will not restore it to \cpp|sys_openat| before it is removed from memory.
15671567
Unfortunately, \cpp|B_openat| will still try to call \cpp|A_openat| which is no longer there, so that even without removing B the system would crash.
15681568

1569+
For x86 architecture, the system call table cannot be used to invoke a system call after commit
1570+
\href{https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1e3ad78334a69b36e107232e337f9d693dcc9df2}{1e3ad78} since v6.9.
1571+
This commit has been backported to long term stable kernels, like v5.15.154+, v6.1.85+, v6.6.26+ and v6.8.5+, see this \href{https://stackoverflow.com/a/78607015}{answer} for more details.
1572+
In this case, thanks to Kprobes, a hook can be used instead on the system call entry to intercept the system call.
1573+
15691574
Note that all the related problems make syscall stealing unfeasible for production use.
15701575
In order to keep people from doing potential harmful things \cpp|sys_call_table| is no longer exported.
15711576
This means, if you want to do something more than a mere dry run of this example, you will have to patch your current kernel in order to have \cpp|sys_call_table| exported.

0 commit comments

Comments
 (0)