一、实验内容
1. 阅读并分析Linux内核源代码,了解进程控制块、进程队列等数据结构;
2. 实现一个系统调用,使得可以根据指定的参数隐藏进程,使用户无法使用ps或top观察到进程状态。具体要求如下:
(1)实现系统调用int hide(pid_t pid, int on),在进程pid有效的前提下,如果on置1,进程被隐藏,用户无法通过ps或top观察到进程状态;如果on置0且此前为隐藏状态,则恢复正常状态。
(2)考虑权限问题,只有根用户才能隐藏进程。
3.设计一个新的系统调用int hide_user_processes(uid_t uid, char *binname),参数uid为用户ID号,当binname参数为NULL时,隐藏该用户的所有进程;否则,隐藏二进制映像名为binname的用户进程。该系统调用应与hide系统调用共存。
4.在/proc目录下创建一个文件/proc/hidden,该文件可读可写,对应一个全局变量hidden_flag,当hidden_flag为0时,所有进程都无法隐藏,即便此前进程被hide系统调用要求隐藏。只有当hidden_flag为1时,此前通过hide调用要求被屏蔽的进程才隐藏起来。
5.在/proc目录下创建一个文件/proc/hidden_process,该文件的内容包含所有被隐藏进程的pid,各pid之间用空格分开。
二、实现
-
准备工作与内核编译
使用VMware,下载并安装Fedora7.
su root 切换到根账号
切换下载源
yum install yum-fastestmirror -y
安装依赖软件包
yum install gcc
yum install gcc-c++
yum -y install ncurces
yum install ncurces-devel
cp将源码移动到/usr/src
[root@localhost /]# cd /home/seu/Desktop
[root@localhost Desktop]# sudo cp linux-2.6.21.tar.gz /usr/src/linux-2.6.21.tar.gz
cd /usr/src 切换到src
tar -xvf linux-2.6.21.tar.gz
cd linux-2.6.21
利用make命令开始编译内核。
make mrproper //删除以前垃圾文件
make menuconfig //直接exit即可
make dep //建立相依的属性关系
make clean //去除旧资料
make [-j4] bzImage //编译内核,多线程加速
make [-j4] modules //编译模块
make modules_install //安装模块
make install //安装内核
reboot //重启
tip:fedora 重启阶段蓝屏时按空格可以切换内核
2.实现隐藏进程的系统调用,并限制用户权限
2.1修改/usr/src/linux-2.6.21/include/linux/sched.h:
[root@localhost include]# cd /usr/src/linux-2.6.21/include/linux
[root@localhost linux]# gedit sched.h
在task_structd的末尾添加变量hide和old_pid。 hide用于表示是否隐藏,old_pid用于保存进程原本的pid,用于进程的恢复。在进程创建的时候对hide进行初始化,这里默认初始化为0,表示不隐藏。
2.2修改/usr/src/linux-2.6.21/kernel/fork.c:
[root@localhost linux]# gedit /usr/src/linux-2.6.21/kernel/fork.c
fork系统调用具体实现的主要函数为do_fork,do_fork中调用copy_process函数创建子进程,将初始化hide的代码添加在copy_process函数中。
2.3修改系统调用sys.c,在文件结尾添加新的系统调用hide和hide_user_process
[root@localhost linux]# gedit /usr/src/linux-2.6.21/kernel/sys.c
考虑用户权限限制,使用全局变量current->uid获取当前用户权限,值为0代表root用户。
2.4修改内核unistd.h,统一系统调用个数。
对于类 Unix 系统,unistd.h 中所定义的接口通常都是大量针对系统调用的封装。
/usr/src/linux-2.6.21/include/asm/unistd.h是内核代码头文件
/usr/include/asm/unistd.h 是标准C库的头文件
检查两个头文件定义的系统调用是否一致,并在/usr/src/linux-2.6.21/include/asm/unistd.h中加上自定义的系统调用hide及hide_user_process。
2.5修改syscall_table,末尾添加新的系统调用
gedit /usr/src/linux-2.6.21/arch/i386/kernel/syscall_table.S
2.6重新编译内核,并测试这两个新的系统调用
3.在/proc目录下创建一个文件/proc/hidden。
该文件可读可写,对应一个全局变量hidden_flag,当hidden_flag为0时,所有进程都无法隐藏,即便此前进程被hide系统调用要求隐藏。只有当hidden_flag为1时,此前通过hide调用要求被屏蔽的进程才隐藏起来。
3.0将系统调用移到/proc/base.c下,添加头文件
3.1在fs/proc/proc_misc.c文件中声明全局变量int hidden_flag,EXPORT_SYMBOL()函数可以使该变量在整个内核中可见。使用时只要extern int hidden_flag;即可访问同一变量。
3.2 同文件下,将创建hidden文件的代码插入proc_misc_init()
proc文件系统在初始化函数proc_root_init中会调用proc_misc_init函数,此函数用于创建/proc根目录下的文件,那么将创建hidden文件的代码插入到此函数中就可以在proc初始化时得到执行。
3.3读写hidden文件的回调函数
原型:
typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data);
typedef int (write_proc_t)(struct file *file, const char __user *buffer, unsigned long count, void *data);
实现:
static int proc_write_hidden(struct file* file, const char *buffer, unsigned long count, void *data)
static int proc_read_hidden(char* page, char** start, off_t off, int count, int *eof, void *data)
当某个进程读取proc文件时,内核会分配一个内存页,内核模块将数据写入到这张页来返回数据到用户空间。
Read:
//page参数是为进程分配的内存页
//count定义了可以写入的最大字符数。
//start+off:在返回多页数据(通常一页是4K),我们需要使用start和off参数。在read_proc_t方法被调用时,*start的初始值为N。
//eof:文件结束参数,当所有数据全部写入后,需要设置eof。
//return:返回写入的字节数。
Write:
//buffer为用户空间需要写入的数据的头指针
//count 为用户空间需要写入内核的数据长度
//data为proc结构体得私有数据,对应于 struct proc_dir_entry结构体中的data字段。
4.在/proc目录下创建一个文件/proc/hidden_process
该文件的内容包含所有被隐藏进程的pid,各pid之间用空格分开。类比3的操作,但hidden_process的权限为只读
三、数据结构
(一)进程控制块PCB(task_struct)
Linux中保存进程信息的数据结构叫做 task_struct
/include/sched.h:
struct task_struct
{
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped *///进程状态
struct thread_info *thread_info;
atomic_t usage;
unsigned long flags; /* per process flags, defined below *///进程标记
unsigned long ptrace;// ptrace被设置为0时表示不需要被跟踪
int lock_depth; /* BKL lock depth */
……
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif
int hide;//是否隐藏进程
int oid_pid;//保存原pid用于恢复
};
(二)proc文件结构
struct proc_dir_entry {
unsigned int low_ino;
unsigned short namelen;
const char *name;
mode_t mode;
nlink_t nlink;
uid_t uid;
gid_t gid;
loff_t size;
struct inode_operations * proc_iops;
const struct file_operations * proc_fops;
get_info_t *get_info;
struct module *owner;
struct proc_dir_entry *next, *parent, *subdir;
void *data;
read_proc_t *read_proc;//读proc文件函数,实现内核到用户
write_proc_t *write_proc;//写proc文件函数,实现用户到内核
atomic_t count; /* use count */
int deleted; /* delete flag */
void *set;
};
四、源码
(一)fork.c:
修改
添加:
(二)sys.c
文件尾添加两个新的系统调用。由于在ps,top命令中不显示0号进程的相关信息,因此隐藏进程的方法是置pid=0。
系统调用hide:隐藏指定pid的进程
系统调用hide_user_process:按照指定uid或uid和进程名隐藏
(三)修改unistd.h,加上新的系统调用
两个文件末尾添加新的系统调用320/321并修改系统调用总数为322
(四)修改syscall_table.S,添加新的系统调用
(五)内核创建proc 文件hidden和hidden_process
声明全局变量:
修改proc_misc_init()
Create_proc_entry()第一个参数是文件名,第二个参数是文件的读写权限,第三个参数是路径,因为在proc文件的根目录所以为NULL。
对hidden和hiddenprocess的读写函数
#define for_each_process(p) \
for (p = &init_task ; (p = next_task(p)) != &init_task ; )
(六)修改proc/base.c中的proc_pid_lookup函数及proc_pid_readdir函数,使hidden_flag优先级高于task->hide
(七)修改自定义系统调用逻辑,移动到proc/base.c中,增加调试语句。利用find_task_by_pid()定位进程,代码更简洁。调用函数proc_flush_task来清空VFS层的缓冲,解除已有的dentry项。
(八)测试320(hide)、321(hide_user_process)系统调用
查看本机seu uid=500
每次su进程的pid不一定相同,测试时需要先ps查看su的pid
五、测试
切换内核版本为linux-2.6.21,查看/proc文件夹下生成hidden、hidden_flag两个文件
默认设置hidden_flag=1,允许隐藏进程
- 测试hide_user_process
测试前top查看当前进程
运行测试文件,
在root用户下,成功隐藏了用户名为seu的进程。
- 测试hide su进程
ps查看su的pid为4054
运行系统调用,隐藏了pid=4054的进程
将系统调用第三个参数设置为0,su进程又显示出来。
cat查看hidden_process的内容
- 测试hidden_flag
echo
设置hidden_flag=0,禁止隐藏进程。
本次su进程pid为4520 可以看到su进程没没有被隐藏。
测试用户权限
Seu权限下,pid=4776的bash进程并没有被隐藏,只有root权限可以隐藏进程。