一、实验内容
实验3:Linux进程管理及其扩展
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之间用空格分开。
二、实验目的
通过实验,加深理解进程控制块、进程队列等概念,了解进程管理的具体实施方法。
三、设计思路和关键代码
特色功能:
1、进程按照pid、uid、进程名进行隐藏和恢复
2、实现了动态模块加载和内核静态编译两种方式生成proc文件
3、编写了测试程序
(1)修改进程控制块(相当于PCB)的数据结构,增加cloak参数,1表示该进程隐藏。在使用ps或者top指令的时候,事实上也就是系统调用,所以要在显示进程的系统调用的地方按照cloak参数进行过滤。
A、Include/linux/sched.h文件中存储进程控制块(struct task_struct)。
B、首先要在进程创建的时候对cloak进行初始化,这里默认初始化为0,表示不隐藏。
fork系统调用的实现代码在kernel/fork.c中,具体实现的主要函数为do_fork,do_fork中调用copy_process函数创建子进程,建议将初始化cloak的代码添加在copy_process函数中。
C、然后,创建hide系统调用,这里不再像实验2一样新建文件,而是直接在fs/proc/base.c中添加。通过pid获取进程task_struct的内核函数为find_task_by_pid。在隐藏后最好调用函数proc_flush_task来清空VFS层的缓冲,解除已有的dentry项。
修改proc_pid_readdir函数(在fs/proc/base.c文件中)。其中使用for循环遍历进程,在遍历过程中添加判断,过滤掉被隐藏的进程。
修改proc_pid_lookup函数,在进程查找完成前过滤掉被隐藏的进程。
(2)考虑权限时,需要修改cloak的hide系统调用中增加对权限的判断,使用全局变量current->uid获取当前用户权限,值为0代表root用户。
(3)int hide_user_processes(uid_t uid, char *binname),系统调用按照uid或者uid和进程名对进程进行隐藏的系统调用。在fs/proc/base.c添加系统调用。
(4)在/proc目录下创建一个文件/proc/hidden,该文件可读可写,对应一个全局变量hidden_flag,当hidden_flag为0时,所有进程都无法隐藏,即便此前进程被hide系统调用要求隐藏。只有当hidden_flag为1时,此前通过hide调用要求被屏蔽的进程才隐藏起来。
这个hidden_flag相当于更高级别的控制,当其为0时,仍然可以对cloak操作,但是隐藏的作用失效。
实现proc文件的创建和读写,可以直接写在内核中也可以通过动态加载模块的方式实现。这里两种都做介绍:
内核中创建proc文件方式:
A、 首先在fs/proc/proc_misc.c文件中声明全局变量,EXPORT_SYMBOL()函数可以使该变量在整个内核中可见。使用时只要extern int hidden_flag;即可访问同一变量。
B、 proc文件系统在初始化函数proc_root_init中会调用proc_misc_init函数,此函数用于创建/proc根目录下的文件,那么将创建hidden文件的代码插入到此函数中就可以在proc初始化时得到执行。(在fs/proc/proc_misc.c文件中)
其中proc_read_hidden和proc_write_hidden分别是对hidden文件读写时的回调函数。
即当用户读取hidden文件时,返回全局变量hidden_flag的值;
即当用户写入hidden文件时,修改全局变量hidden_flag的值;
这样就可以实现文件的值和全局变量实现同步。达到用户态对内核进行设置的目的。
C、 修改proc_pid_readdir函数和proc_pid_lookup函数(在fs/proc/base.c文件中),增加对hidden_flag的判断逻辑。
proc_pid_lookup:
proc_pid_readdir:
动态加载模块方式:
A、 只需要在内核fs/proc/proc_misc.c文件中,添加全局变量
B、 创建一个动态模块文件hide_module.c
在其初始化函数init_hide(void)中编写:
在其关闭函数exit_hide(void)中编写:
C、 将这两个函数绑定为初始化和退出函数
D、 读写文件的回调函数
(5)在/proc目录下创建一个文件/proc/hidden_process,该文件的内容包含所有被隐藏进程的pid,各pid之间用空格分开。那么也可以分为静态内核和动态模块两种实现方式。
内核中创建proc文件方式:
A、proc文件系统在初始化函数proc_root_init中会调用proc_misc_init函数,此函数用于创建/proc根目录下的文件,那么将创建hidden文件的代码插入到此函数中就可以在proc初始化时得到执行。(在fs/proc/proc_misc.c文件中)
B、由于这个文件只需要用户读取,所以只写读操作的回调函数即可:
这里需要注意的是,page即为返回给用户的字符串,这个回调函数的返回值,为这个字符串的长度。
动态加载模块方式:
A、 与hidden文件的动态加载模块方式相似
在其初始化函数init_hide(void)中编写:
在其关闭函数exit_hide(void)中编写:
B、 其读操作回调函数为:
四、主要数据结构及其说明
task_struct数据结构说明
源自:
http://www.baidu.com/link?url=SO-vtkUs6iRdOHI9se20ZDyI8zjNisNDVxeW7ccNtCfx3YZbVlQFptbKekoIcG9zyDe0PuRvvV7yNn-5zc46_q
proc文件操作说明
创建proc文件
struct proc_dir_entry * hidefile = create_proc_entry("hidden",0644,NULL);
1、 第一个参数是文件名
2、 第二个参数是文件的读写权限
3、 第三个参数是路径,因为在proc文件的根目录所以为NULL
然后绑定读写回调函数:
hidefile->read_proc = proc_read_hidden;// call back : read
hidefile->write_proc = proc_write_hidden;// call back : write
也可以采用以下方式创建并绑定回调函数:
struct proc_dir_entry * hideprocessfile = create_proc_read_entry("hidden_process",
0444,
NULL,
proc_read_hidden_process,
NULL);
读proc文件
proc文件不能像普通文件一样打开然后阅读,需使用如下指令
cat < hidden
使用如下指令写入文件
echo “1” > hidden
加载动态模块
在加载模块的目录下编写Makefile文件,然后编译
$ make
进入到root用户下,加载模块
# insmod hide_module.ko
卸载模块
# rmmod hide_module
五、源程序
动态加载模块hide_module.h
#include<linux/init.h> #include<linux/module.h> #include <linux/fs.h> #include <asm/unistd.h> #include <asm/uaccess.h> #include<linux/proc_fs.h> #include<linux/sched.h> #include<linux/proc_fs.h> #include<linux/slab.h> #include <asm/uaccess.h> MODULE_LICENSE("GPL"); extern int hidden_flag; static int proc_read_hidden(char* page,char**start,off_t off,int count,int *eof,void *data){ // hidden 's read callback function int length; length = sprintf(page,"%d",hidden_flag);//before user read this file, we write the 'hidden_flag' to the file return length; } static int proc_write_hidden(struct file * file,const char *buffer,unsigned long count,void *data){ // hidden 's write callback function hidden_flag = buffer[0]-'0';//before user write this file , we catch the user's input and modify the 'hidden_flag' return count; } static int proc_read_hidden_process(char* page,char**start,off_t off,int count,int *eof,void *data){ // hidden_process 's read callback function struct task_struct *task=NULL; int num = 0; for_each_process(task){ if(task->cloak==1){ sprintf(page+num,"%-4d ",task->pid); num+=5; } } return num-5; } static int init_hide(void){ // init module -> create proc file (hidden and hidden_process) struct proc_dir_entry * hidefile = create_proc_entry("hidden",0644,NULL);// create hidden file and its privilage is read&write hidefile->read_proc = proc_read_hidden;// call back : read hidefile->write_proc = proc_write_hidden;// call back : write struct proc_dir_entry * hideprocessfile = create_proc_read_entry("hidden_process", 0444, NULL, proc_read_hidden_process, NULL); printk("<0>""hidden file ok\n"); return 0; } static void exit_hide(void){ // delete proc file remove_proc_entry("hidden",NULL); remove_proc_entry("hidden_process",NULL); printk("<0>""hidden file close\n"); return; } module_init(init_hide); module_exit(exit_hide);
动态加载模块的Makefile
ifneq ($(KERNELRELEASE),) obj-m:=hide_module.o else KDIR:=/lib/modules/$(shell uname -r)/build PWD:=$(shell pwd) default: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean Endif
测试程序
#include<sys/syscall.h> #include<unistd.h> #include<stdio.h> #include<stdlib.h> int main(){ int func=0; int pid; int on; char name[200]; int uid; while(1){ printf("###please select test function:\n0:my system call\n1:hide\n2hide_process by uid\n3hide_process by name&uid\n4:exit\n"); scanf("%d",&func); switch(func){ case 0: syscall(320); break; case 1: printf("input: pid on\n"); scanf("%d %d",&pid,&on); syscall(321,pid,on); break; case 2: printf("input: uid on\n"); scanf("%d %d",&uid,&on); syscall(322,uid,NULL,on); break; case 3: printf("input: uid on name\n"); scanf("%d %d %s",&uid,&on,&name); syscall(322,uid,name,on); break; default: return 0; } } return 0; }