Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add system call "stealing" sample using kprobe handler #260

Merged
merged 1 commit into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions examples/syscall-steal.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@

#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
jserv marked this conversation as resolved.
Show resolved Hide resolved
#include <linux/kprobes.h>
#else
#define HAVE_PARAM 1
Copy link
Collaborator

@linD026 linD026 Jun 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we need to add the other condition, the system is v5.9+ and x86 arch without kprobe support.
Maybe we can tell the user they are unable to run this example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe defining USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL to 0 by default is enough because users may turn on this macro if they get stuck when hacking.

Copy link
Collaborator

@linD026 linD026 Jun 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the module will be broken if we have such a scenario.
Moreover, the user may be confused about this and start googling your question again.
I would suggest that you provide a more comprehensive explanation or relevant links (e.g., the stack overflow answer you mentioned).
Additionally, in the code, we can print out some warnings about this instead of using the wrong symbol.

Expand All @@ -58,12 +66,37 @@ module_param(sym, ulong, 0644);

#endif /* Version < v5.7 */

static unsigned long **sys_call_table_stolen;

/* 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
Expand Down Expand Up @@ -202,9 +235,23 @@ static void disable_write_protection(void)
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;

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

enable_write_protection();

pr_info("Spying on UID:%d\n", uid);
#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;

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

msleep(2000);
}
Expand Down
5 changes: 5 additions & 0 deletions lkmpg.tex
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,11 @@ \section{System Calls}
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.
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.

For x86 architecture, the system call table cannot be used to invoke a system call after commit
\href{https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1e3ad78334a69b36e107232e337f9d693dcc9df2}{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+, see this \href{https://stackoverflow.com/a/78607015}{answer} for more details.
In this case, thanks to Kprobes, a hook can be used instead on the system call entry to intercept the system call.

Note that all the related problems make syscall stealing unfeasible for production use.
In order to keep people from doing potential harmful things \cpp|sys_call_table| is no longer exported.
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.
Expand Down