412 lines
9.7 KiB
C
412 lines
9.7 KiB
C
|
|
||
|
#define pr_fmt(fmt) "nocrypt: " fmt
|
||
|
|
||
|
#include <linux/ftrace.h>
|
||
|
#include <linux/kallsyms.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/linkage.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/kprobes.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <asm/signal.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/sched/signal.h>
|
||
|
#include <linux/mm.h>
|
||
|
|
||
|
MODULE_DESCRIPTION("Detect and kill ransomware");
|
||
|
MODULE_AUTHOR("niveb");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
|
||
|
unsigned int max_rename = 12;
|
||
|
module_param(max_rename, int, 0);
|
||
|
bool behaviour_detection = false;
|
||
|
module_param(behaviour_detection, bool, 0);
|
||
|
|
||
|
unsigned int rename_count = 0;
|
||
|
unsigned int target_pid = 0;
|
||
|
//Add here your custom extensions to block
|
||
|
#define BLACKLIST_SIZE 7
|
||
|
char *blacklist_ext[] = {"Clop","iFire","conti","monti","PUUUK", "Cheers","lockbit"};
|
||
|
|
||
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
|
||
|
static unsigned long lookup_name(const char *name)
|
||
|
{
|
||
|
struct kprobe kp = {
|
||
|
.symbol_name = name
|
||
|
};
|
||
|
unsigned long retval;
|
||
|
|
||
|
if (register_kprobe(&kp) < 0) return 0;
|
||
|
retval = (unsigned long) kp.addr;
|
||
|
unregister_kprobe(&kp);
|
||
|
return retval;
|
||
|
}
|
||
|
#else
|
||
|
static unsigned long lookup_name(const char *name)
|
||
|
{
|
||
|
return kallsyms_lookup_name(name);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0)
|
||
|
#define FTRACE_OPS_FL_RECURSION FTRACE_OPS_FL_RECURSION_SAFE
|
||
|
#endif
|
||
|
|
||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0)
|
||
|
#define ftrace_regs pt_regs
|
||
|
|
||
|
static __always_inline struct pt_regs *ftrace_get_regs(struct ftrace_regs *fregs)
|
||
|
{
|
||
|
return fregs;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* There are two ways of preventing vicious recursive loops when hooking:
|
||
|
* - detect recusion using function return address (USE_FENTRY_OFFSET = 0)
|
||
|
* - avoid recusion by jumping over the ftrace call (USE_FENTRY_OFFSET = 1)
|
||
|
*/
|
||
|
#define USE_FENTRY_OFFSET 0
|
||
|
|
||
|
/**
|
||
|
* struct ftrace_hook - describes a single hook to install
|
||
|
*
|
||
|
* @name: name of the function to hook
|
||
|
*
|
||
|
* @function: pointer to the function to execute instead
|
||
|
*
|
||
|
* @original: pointer to the location where to save a pointer
|
||
|
* to the original function
|
||
|
*
|
||
|
* @address: kernel address of the function entry
|
||
|
*
|
||
|
* @ops: ftrace_ops state for this function hook
|
||
|
*
|
||
|
* The user should fill in only &name, &hook, &orig fields.
|
||
|
* Other fields are considered implementation details.
|
||
|
*/
|
||
|
struct ftrace_hook {
|
||
|
const char *name;
|
||
|
void *function;
|
||
|
void *original;
|
||
|
|
||
|
unsigned long address;
|
||
|
struct ftrace_ops ops;
|
||
|
};
|
||
|
|
||
|
static int fh_resolve_hook_address(struct ftrace_hook *hook)
|
||
|
{
|
||
|
hook->address = lookup_name(hook->name);
|
||
|
|
||
|
if (!hook->address) {
|
||
|
pr_debug("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;
|
||
|
}
|
||
|
|
||
|
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip,
|
||
|
struct ftrace_ops *ops, struct ftrace_regs *fregs)
|
||
|
{
|
||
|
struct pt_regs *regs = ftrace_get_regs(fregs);
|
||
|
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
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_install_hooks() - register and enable a single hook
|
||
|
* @hook: a hook to install
|
||
|
*
|
||
|
* Returns: zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
int fh_install_hook(struct ftrace_hook *hook)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = fh_resolve_hook_address(hook);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
/*
|
||
|
* We're going to modify %rip register so we'll need IPMODIFY flag
|
||
|
* and SAVE_REGS as its prerequisite. ftrace's anti-recursion guard
|
||
|
* is useless if we change %rip so disable it with RECURSION.
|
||
|
* We'll perform our own checks for trace function reentry.
|
||
|
*/
|
||
|
hook->ops.func = fh_ftrace_thunk;
|
||
|
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS
|
||
|
| FTRACE_OPS_FL_RECURSION
|
||
|
| FTRACE_OPS_FL_IPMODIFY;
|
||
|
|
||
|
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
|
||
|
if (err) {
|
||
|
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
err = register_ftrace_function(&hook->ops);
|
||
|
if (err) {
|
||
|
pr_debug("register_ftrace_function() failed: %d\n", err);
|
||
|
ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_remove_hooks() - disable and unregister a single hook
|
||
|
* @hook: a hook to remove
|
||
|
*/
|
||
|
void fh_remove_hook(struct ftrace_hook *hook)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = unregister_ftrace_function(&hook->ops);
|
||
|
if (err) {
|
||
|
pr_debug("unregister_ftrace_function() failed: %d\n", err);
|
||
|
}
|
||
|
|
||
|
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
|
||
|
if (err) {
|
||
|
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_install_hooks() - register and enable multiple hooks
|
||
|
* @hooks: array of hooks to install
|
||
|
* @count: number of hooks to install
|
||
|
*
|
||
|
* If some hooks fail to install then all hooks will be removed.
|
||
|
*
|
||
|
* Returns: zero on success, negative error code otherwise.
|
||
|
*/
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fh_remove_hooks() - disable and unregister multiple hooks
|
||
|
* @hooks: array of hooks to remove
|
||
|
* @count: number of hooks to remove
|
||
|
*/
|
||
|
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]);
|
||
|
}
|
||
|
|
||
|
#ifndef CONFIG_X86_64
|
||
|
#error Currently only x86_64 architecture is supported
|
||
|
#endif
|
||
|
|
||
|
#if defined(CONFIG_X86_64) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0))
|
||
|
#define PTREGS_SYSCALL_STUBS 1
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Tail call optimization can interfere with recursion detection based on
|
||
|
* return address on the stack. Disable it to avoid machine hangups.
|
||
|
*/
|
||
|
#if !USE_FENTRY_OFFSET
|
||
|
#pragma GCC optimize("-fno-optimize-sibling-calls")
|
||
|
#endif
|
||
|
|
||
|
static char *duplicate_filename(const char __user *filename)
|
||
|
{
|
||
|
char *kernel_filename;
|
||
|
|
||
|
kernel_filename = kmalloc(4096, GFP_KERNEL);
|
||
|
if (!kernel_filename)
|
||
|
return NULL;
|
||
|
|
||
|
if (strncpy_from_user(kernel_filename, filename, 4096) < 0) {
|
||
|
kfree(kernel_filename);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return kernel_filename;
|
||
|
}
|
||
|
|
||
|
/* Send SIGKILL to the input task */
|
||
|
static bool kill_task(struct task_struct *task) {
|
||
|
int signum = SIGKILL;
|
||
|
struct kernel_siginfo info;
|
||
|
memset(&info, 0, sizeof(struct kernel_siginfo));
|
||
|
info.si_signo = signum;
|
||
|
int ret = send_sig_info(signum, &info, task);
|
||
|
if (ret < 0)
|
||
|
{
|
||
|
printk(KERN_INFO "error sending signal to %d\n", target_pid);
|
||
|
return -1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
printk(KERN_INFO "Target pid %d has been killed\n", target_pid);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Check if the renaming operation is linked to a ransomware behaviour or not.
|
||
|
* Returns true if the operation is allowed
|
||
|
* Kill the process and returns false if the operation is not allowed
|
||
|
*/
|
||
|
static bool check_rename(char *oldname, char *newname) {
|
||
|
struct task_struct *task;
|
||
|
task = current;
|
||
|
// we use parent pid because the tasks can be threads
|
||
|
if (target_pid == task->real_parent->pid) {
|
||
|
rename_count++;
|
||
|
} else {
|
||
|
target_pid = task->real_parent->pid;
|
||
|
rename_count = 0;
|
||
|
}
|
||
|
//Check for specific known extensions
|
||
|
//Find null terminating char
|
||
|
int index = 0;
|
||
|
int point_index = 0;
|
||
|
int nmax = 200;
|
||
|
//loop max nmax times
|
||
|
for (index = 0; index < nmax; index++) {
|
||
|
if (newname[index] == 0)
|
||
|
break;
|
||
|
else if (newname[index] == '.') {
|
||
|
point_index = index;
|
||
|
}
|
||
|
}
|
||
|
if ((point_index > 0) && (index < nmax)) {
|
||
|
char *extension = newname+point_index+1;
|
||
|
for (int i = 0; i < BLACKLIST_SIZE; i++) {
|
||
|
if (strcmp(extension,blacklist_ext[i]) == 0) {
|
||
|
pr_info("%s ransomware detected (renaming %s to %s)\n", extension, oldname, newname);
|
||
|
kill_task(task);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Behaviour check
|
||
|
if (behaviour_detection) {
|
||
|
// if the same process pid is renaming more than n files, kill it
|
||
|
if (rename_count >= max_rename) {
|
||
|
pr_info("process %d detected as possible ransomware, renaming too much files (e.g. %s to %s)\n", target_pid, oldname, newname);
|
||
|
kill_task(task);
|
||
|
rename_count = 0;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#ifdef PTREGS_SYSCALL_STUBS
|
||
|
static asmlinkage long (*real_sys_rename)(struct pt_regs *regs);
|
||
|
|
||
|
static asmlinkage long fh_sys_rename(struct pt_regs *regs)
|
||
|
{
|
||
|
long ret = 0;
|
||
|
char *oldname = (char*)regs->di;
|
||
|
char *newname = (char*)regs->si;
|
||
|
if (check_rename(oldname, newname)) {
|
||
|
ret = real_sys_rename(regs);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
#else
|
||
|
static asmlinkage long (*real_sys_rename) (const char __user *oldname, const char __user *newname);
|
||
|
|
||
|
static asmlinkage long fh_sys_rename(const char __user *oldname, const char __user *newname)
|
||
|
{
|
||
|
long ret = 0;
|
||
|
if (check_rename(oldname, newname)) {
|
||
|
ret = real_sys_rename(oldname, newname);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* x86_64 kernels have a special naming convention for syscall entry points in newer kernels.
|
||
|
* That's what you end up with if an architecture has 3 (three) ABIs for system calls.
|
||
|
*/
|
||
|
#ifdef PTREGS_SYSCALL_STUBS
|
||
|
#define SYSCALL_NAME(name) ("__x64_" name)
|
||
|
#else
|
||
|
#define SYSCALL_NAME(name) (name)
|
||
|
#endif
|
||
|
|
||
|
#define HOOK(_name, _function, _original) \
|
||
|
{ \
|
||
|
.name = SYSCALL_NAME(_name), \
|
||
|
.function = (_function), \
|
||
|
.original = (_original), \
|
||
|
}
|
||
|
|
||
|
static struct ftrace_hook demo_hooks[] = {
|
||
|
HOOK("sys_rename", fh_sys_rename, &real_sys_rename),
|
||
|
};
|
||
|
|
||
|
static int fh_init(void)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = fh_install_hooks(demo_hooks, ARRAY_SIZE(demo_hooks));
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
pr_info("nocrypt loaded (max_rename=%d,behaviour_detection=%d)\n",max_rename,behaviour_detection);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
module_init(fh_init);
|
||
|
|
||
|
static void fh_exit(void)
|
||
|
{
|
||
|
fh_remove_hooks(demo_hooks, ARRAY_SIZE(demo_hooks));
|
||
|
|
||
|
pr_info("nocrypt unloaded\n");
|
||
|
}
|
||
|
module_exit(fh_exit);
|