操作系统leb7实验报告

实验名称:实验7:隐藏进程

实验目的

  • 1、综合之前所有的知识
  • 2、熟悉系统编程,完成相应目的

实验内容

实现一个系统调用hide,来隐藏进程,使用户无法使用ps或者top命令观察到进程状态,要求实现以下基本功能:
1、实现一个系统调用 int hide(pid_t pid,int on),在进程pid有效的情况下,如果on置位1,进程被隐藏,用户无法通过proc文件系统观察到进程状态,如果on置位0,且此前为隐藏状态,那么则恢复为正常状态,调用的返回值自行设计。
2、考虑权限问题,只有根用户才能隐藏进程。
在上述功能完成的情况下,进行扩展,(4)和(5)中选择一个,(6)和(7)中选择一个。
3、设计一个系统调用 int hide_userprocess(uid_t uid,char* binname),参数uid为当前用户ID号,当binname为NULL时,隐藏该用户的所有进程,否则,隐藏二进制映像为biname的用户进程,该系统调用可以和hide系统调用共存。
4、在/proc的目录下创建一个文件/proc/hidden,该文件可读可写,对应一个全局变量hidden_flag,当hidden_flag为0时,所有进程都无法隐藏,即便此前进程被hide系统调用要求隐藏只有当hidden_flag为1时,此前通过hide调用要求被屏蔽的进程才隐藏起来。
5、将进程隐藏功能变成一个内核可编译选项。
6、设计一个内核模块,输出所有被隐藏的进程
7、在/proc目录下创建一个文件/proc/hidden_process,该文件的内容包含所有被隐藏进程的pid,各个pid之间用空格分开。

实验环境

  1. VMware
  2. Fedora7

实验作业

背景知识

命令ps和 top都可以显示进程信息,在此简单介绍ps,对 top命令感兴趣的读者可自行查看 man手册。ps 命令有很多选项,其中 ps aux显示系统所有用户的所有进程的信息。下面是ps aux 命令在笔者机器上产生的部分输出,其中USER是进程的拥有者,PID是进程标识号,%CPU是进程占用CPU的比例,%MEM是进程占用系统存储器的比例,VSZ是进程占用虚拟内存的大小,RSS是进程驻留集大小,TTY是进程控制终端,STAT是进程状态,START是进程启动时间,TIME是进程使用CPU的时间,COMMAND是进程对应的程序名。
![](https://img-blog.csdnimg.cn/img_convert/979bed9d1f3ebedbae527e495e62eb2e.png#from=url&id=ukaHs&margin=[object Object]&originHeight=675&originWidth=713&originalType=binary&ratio=1&status=done&style=none) Linux操作系统下ps 和 top 的实现都是利用proc文件系统提供的信息。
前面已经提到,proc不是一个磁盘文件系统,它的内容是动态生成的,在此我们介绍它的实现内幕。
如果内核编译包括了proc文件系统,则在内核启动时proc_root_init 函数被调用,下面给出了该函数裁减后的代码:

void _init proc_root_init( void)
{
    int err = proc_init_inodecache();
	err = register_filesystem(&proc_fs_type);
	proc_mnt = kern_mount(&proc_fs_type);
    proc_misc_init();
	proc_net = proc_mkdir("net", NULL);
	proc_net_stat = proc_mkdir("net/stat", NULL);
    proc_root_fs = proc_mkdir( "fs", NULL);
	proc_root_driver = proc_mkdir(" driver" , NULL);
    proc_mkdir("fs/nfsd", NULL);
	proc_bus = proc_mkdir(" bus", NULL);
    proc_sys_init();
}
	static const struct file__operations proc_root_operations = 
    {
		. read=generic_read_dir,
		. readdir=proc_root_readdir,
	};
static const struct inode_operations proc_root_inode_operations =
{
	. lookup=proc_root_lookup,
	. getattr=proc_root_getattr,
};


上面代码解释如下:
第4行:调用函数register_filesystem注册proc文件系统。
第5行: kern_mount 将创建文件系统的超级块,新建根结点的 proc_dir_entry结构(描述见实验5),为根结点建立 dentry和 inode结构。当根目录 inode结点被初始化时,成员i_op和i_fop分别被设置为proc_root_inode_operations 和 proc_root_operations。
第6行:创建根目录下的文件,如 loadavg , uptime等。
第7~13行:创建根目录下的子目录,如 net,fs 等。
对于6~13行代码,有两点需要特别说明。首先,proc文件系统是树形结构,从根目录、子目录到叶子结点,它们对应内存中的一个proc_dir_entry结构,并没有磁盘上的对应结构。根目录下的文件如 loadavg等也没有内容,只有当用户需要读取文件时才会动态生成。其次,此时proc文件系统还有大量的文件和目录没有生成,有些是在相关子系统的初始化时完成。但是,还有一些子树并不需要对应的proc_dir_entry 结构,它们的内容完全是根据需要动态生成的,典型的例子就是/proc目录下的进程信息。

实验结果

实现基础功能一、二

功能一:实现一个系统调用 int hide(pid_t pid,int on),在进程pid有效的情况下,如果on置位1,进程被隐藏,用户无法通过proc文件系统观察到进程状态,如果on置位0,且此前为隐藏状态,那么则恢复为正常状态,调用的返回值自行设计。
功能二:考虑权限问题,只有根用户才能隐藏进程。
该功能可以按下面的步骤进行:

(1)修改进程描述符结构task_struct

该结构定义在 linux/sched.h文件中,为其添加一个成员cloak,用来记录进程隐藏与否。
修改前
![](https://img-blog.csdnimg.cn/img_convert/fe009ca81d0fc2204a59972f9205f63e.png#from=url&id=MMeK7&margin=[object Object]&originHeight=379&originWidth=587&originalType=binary&ratio=1&status=done&style=none)
修改后
![](https://img-blog.csdnimg.cn/img_convert/916ca4d0edab7e2e4186eecd0f05c056.png#from=url&id=fXv29&margin=[object Object]&originHeight=252&originWidth=376&originalType=binary&ratio=1&status=done&style=none)

(2)cloak 初始化为未隐藏

在进程创建时,把 task_struct结构的成员cloak 初始化为未隐藏。fork系统调用的实现代码在文件kernel/fork.c中,具体实现的主要函数为do_fork,请读者自己考虑合适的代码插入位置。
在进程创建时,将task_struct的成员cloak初始化为未隐藏。fork系统调用的实现代码在kernel/fork.c中,具体实现的主要函数为do_fork,do_fork中调用copy_process函数创建子进程,建议将初始化cloak的代码添加在copy_process函数中:
![](https://img-blog.csdnimg.cn/img_convert/477c1978411cde3a400dce0713b8a582.png#from=url&id=LEPM7&margin=[object Object]&originHeight=550&originWidth=559&originalType=binary&ratio=1&status=done&style=none)
![](https://img-blog.csdnimg.cn/img_convert/dddc2024184961ee70437822606c21b0.png#from=url&id=DEBLY&margin=[object Object]&originHeight=541&originWidth=590&originalType=binary&ratio=1&status=done&style=none)
由于隐藏进程的原理是在进程的结构体中建立一个新的标记,并在输出的时候判断这个标记,并根据这个结果进行输出,那么如果我们可以绕过这个判断,就可以读出所以的进程了,所以发现隐藏进程的方法有很多,调用内核模块,构建一个不判断cloak的输出程序即可。

(3)添加 hide系统调用。

具体做法参见实验6,这里有两处提醒读者,一是sys_hide的实现应该放在哪个文件中,也许文件 fs/proc/base.c是合适的选择;另一个是关于sys_hide 的实现,仅仅设置task_struct结构的cloak成员是不够的,参见第(6)步。
代码:
在kernel目录下新建文件hide.c

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/proc_fs.h>

asmlinkage int sys_hide(pid_t pid, int on)
{
	struct task_struct *p = NULL;
	if(pid>0 &&  (current->uid)==0)
	{
		p = find_task_by_pid(pid);
		p->cloak=on;
		if(1==on){
			printk("Process %d is hidden by root.\n", pid);
		}
		if(0==on){
			printk("Process %d is displayed by root.\n", pid);
		}
		proc_flush_task(p);
	}
	else
		printk("Permission denied.\n");
	return 0;
}

![](https://img-blog.csdnimg.cn/img_convert/522ba6f279f82354778a2a08953f51b5.png#from=url&id=qfuav&margin=[object Object]&originHeight=504&originWidth=704&originalType=binary&ratio=1&status=done&style=none)
在这里我们实现我们的功能二,对用户身份进行判断,如果不是root用户则无法进行操作。

调整arch/i386/kernel/syscall_table.S

在文件 arch/i386/kernel/syscall_table.S 的尾部加上要新增的系统调用函数名称,如阴影行所示,注释中320表示它的系统调用号。
进入目录:
![](https://img-blog.csdnimg.cn/img_convert/aa143c344828d4db7d1c96b086feaf73.png#from=url&id=nb4Qx&margin=[object Object]&originHeight=110&originWidth=424&originalType=binary&ratio=1&status=done&style=none)
![](https://img-blog.csdnimg.cn/img_convert/1a8bd3e66b4f6d7a66830053f223f77e.png#from=url&id=yPCtR&margin=[object Object]&originHeight=215&originWidth=247&originalType=binary&ratio=1&status=done&style=none)

修改文件 kernel/Makefile

![](https://img-blog.csdnimg.cn/img_convert/fc4d89f2cdb7e9d393fcf416e7868774.png#from=url&id=cHHKB&margin=[object Object]&originHeight=190&originWidth=382&originalType=binary&ratio=1&status=done&style=none)

在include/asm-i386/unistd.h里面加上系统调用号的宏定义

![](https://img-blog.csdnimg.cn/img_convert/4b4ea13c5333ba55fcc05710ccbffdbb.png#from=url&id=nUrDB&margin=[object Object]&originHeight=129&originWidth=313&originalType=binary&ratio=1&status=done&style=none)

修改include/linux/syscalls.h

![](https://img-blog.csdnimg.cn/img_convert/ea65aa56c6dce71884e40c7fc28d28f9.png#from=url&id=NB2yB&margin=[object Object]&originHeight=85&originWidth=308&originalType=binary&ratio=1&status=done&style=none)

(4)修改proc_pid_readdir 函数的代码

如果被读出的进程描述符的cloak成员值为1,则进程标识符不返还给用户缓冲区。
![](https://img-blog.csdnimg.cn/img_convert/0adc9f6feb4d9b75d3adfc05d5422e86.png#from=url&id=I8YJL&margin=[object Object]&originHeight=250&originWidth=466&originalType=binary&ratio=1&status=done&style=none)

其中使用for循环遍历进程,在遍历过程中添加判断,过滤掉被隐藏的进程:
![](https://img-blog.csdnimg.cn/img_convert/b253a511f4765f810d1b3b7edc780a82.png#from=url&id=o6yCb&margin=[object Object]&originHeight=461&originWidth=626&originalType=binary&ratio=1&status=done&style=none)

(5)修改 proc_pid_lookup函数的代码

如果被找到的进程描述符的cloak成员值为1,则不返回相关信息。
![](https://img-blog.csdnimg.cn/img_convert/a06db39e69b22bf55a9bc36645eabf39.png#from=url&id=Ipo6g&margin=[object Object]&originHeight=429&originWidth=687&originalType=binary&ratio=1&status=done&style=none)

(6)解除已有的dentry项。

前面已经提到,第(4)步是为了确保readdir 系统调用读不到/proc目录下被隐藏的进程号,而第(5)步为了确保 open不能解析类似/proc/pid/xxx之类的路径名,但是只有这两步是不够的,还必须考虑VFS层的缓冲问题,如果缓冲区里已经有相关数据,则操作会立即返回而不经过底层文件系统。许多文件系统的readdir实现都采用了页面缓冲,庆幸的是,proc_pid_readdir没有这个问题,每次系统调用都会经过我们前面已经提到的执行路径。但是在4.3节中提到过,路径解析先访问dcache,在 dache 中找不到相应项时才调用索引结点的lookup方法。因此 hide(pid,1)的实现不仅要设置cloak,而且要判断pid是否在dcache中存在,如果存在则要清除,否则下一次解析依然可能看见/proc/pid/ 。
清除dcache可采用d_drop函数,下面是来自 proc_flush_task函数的示例代码,该函数在fs/proc/base.c中。

void proc_flush_task(struct task_structtasK)
{
	struct dentry *dentry, *leader,*dir;
	char buf[PROC_NUMBUF];
	struct qstr name;
	name.name = buf;
	name.len = snprintf(buf,sizeof(buf), " %d" , task->pid);
	dentry = d_hash_and_lookup(proc_mnt->mnt_root,&name);
	if (dentry)
	{
		shrink_dcache_parent(dentry);d_drop(dentry );
		dput(dentry);
	}
    ...
};

第5行: qstr是用做路径解析的结构,解析之前先要填好路位名和长度。第6~7:将待查pid和长度分别填入name.name和 name.len。
第8行:根据name求得 hash值,然后在dcache中查询是否有名字为name.name的目录项。
第10行:将查到的目录项从其父目录项的子目录项链表中删除。
第11行:将该目录项从dcache 中移出。注意,此时如果还有另外的执行路径用到该目录项,该目录项依然存在,只是路径解析再也访问不到了。
然后重新编译安装。
效果展示:
![](https://img-blog.csdnimg.cn/img_convert/d21c785f4b9a9b998dca8175a54b71c4.png#from=url&id=JSOzb&margin=[object Object]&originHeight=257&originWidth=528&originalType=binary&ratio=1&status=done&style=none)
隐藏进程号为4317的进程:
![](https://img-blog.csdnimg.cn/img_convert/1351f6793bc24a8d1bdff344046715c7.png#from=url&id=mJcR4&margin=[object Object]&originHeight=125&originWidth=354&originalType=binary&ratio=1&status=done&style=none)

功能三

设计一个系统调用 int hide_userprocess(uid_t uid,char* binname),参数uid为当前用户ID号,当binname为NULL时,隐藏该用户的所有进程,否则,隐藏二进制映像为biname的用户进程,该系统调用可以和hide系统调用共存。
程序代码:

#include<linux/linkage.h>
#include<linux/types.h>
#include<linux/sched.h>
#include<linux/pid.h>
#include<linux/proc_fs.h>
#include<linux/string.h>
 
asmlinkage int sys_hide_user_processes(uid_t uid,char *binname,int recover){
    struct task_struct *p=NULL;
    if(recover==0)
    {
        if(current->uid==0)//1.Paragram recover=0;2.root => you can hide the process
        {
            if(binname==NULL)//when null, hide all the processes of the corresponding user
            {
                for_each_process(p)
                {
                    if((p->uid)==uid)
                    {
                        p->cloak=1;
                        proc_flush_task(p);
                    }
                }
                printk("All of the processes of uid %d are hidden.\n",uid);
            }
            else//otherwise, hide the process of the corresponding name
            {
                for_each_process(p)
                {
                    char *s=p->comm;
                    if(strcmp(s,binname)==0 && p->uid==uid)
                    {
                        p->cloak=1;
                        printk("Process %s of uid %d is hidden.\n",binname,uid);
                        proc_flush_task(p);
                    }
                }
            }
        }
        else
            printk("Sorry, you are not root user. Permission denied.\n");
    }
    else if(recover != 0 && (current->uid)==0)//display all of the processes, including which are hidden
    {
        for_each_process(p)
        {
            p->cloak=0;
        }
    }
     
    return 0;
}

程序通过遍历程序,判断其uid号是否为选定uid,如果是则将其cloak属性置为1,将其隐藏。

调整arch/i386/kernel/syscall_table.S

在文件 arch/i386/kernel/syscall_table.S 的尾部加上要新增的系统调用函数名称,如阴影行所示,注释中322表示它的系统调用号。
进入目录:
![](https://img-blog.csdnimg.cn/img_convert/bddead215cc9a978d11d5c2200967dbb.png#from=url&id=BJ7UH&margin=[object Object]&originHeight=300&originWidth=441&originalType=binary&ratio=1&status=done&style=none)
![](https://img-blog.csdnimg.cn/img_convert/492115d3ba29969b1b051ddd9949bcbb.png#from=url&id=LonUq&margin=[object Object]&originHeight=210&originWidth=321&originalType=binary&ratio=1&status=done&style=none)

修改文件 kernel/Makefile

![](https://img-blog.csdnimg.cn/img_convert/a6dae0cf33764410025b033633986720.png#from=url&id=nQqZ7&margin=[object Object]&originHeight=232&originWidth=430&originalType=binary&ratio=1&status=done&style=none)

修改include/asm-i386/unistd.h

![](https://img-blog.csdnimg.cn/img_convert/f02e1945b82a3f78b3a4cd04128f6feb.png#from=url&id=Pdl9Q&margin=[object Object]&originHeight=323&originWidth=315&originalType=binary&ratio=1&status=done&style=none)

修改include/linux/syscalls.h

![](https://img-blog.csdnimg.cn/img_convert/6ea73041d0546df054bd7b5f4004c64a.png#from=url&id=M7Hzg&margin=[object Object]&originHeight=108&originWidth=607&originalType=binary&ratio=1&status=done&style=none)
重新编译启动:
运行图:
![](https://img-blog.csdnimg.cn/img_convert/ba561fc293b72ec31bd6660a17cad6c9.png#from=url&id=GkvrH&margin=[object Object]&originHeight=314&originWidth=495&originalType=binary&ratio=1&status=done&style=none)
![](https://img-blog.csdnimg.cn/img_convert/87257796cf795b4bb3a413e2761e0dac.png#from=url&id=IX3H3&margin=[object Object]&originHeight=212&originWidth=534&originalType=binary&ratio=1&status=done&style=none)

功能四

在/proc的目录下创建一个文件/proc/hidden,该文件可读可写,对应一个全局变量hidden_flag,当hidden_flag为0时,所有进程都无法隐藏,即便此前进程被hide系统调用要求隐藏只有当hidden_flag为1时,此前通过hide调用要求被屏蔽的进程才隐藏起来。
在/fs/proc/proc_misc.c中添加回调函数
![](https://img-blog.csdnimg.cn/img_convert/15e8293e1fb18c0d261905499bfd5703.png#from=url&id=rtMQh&margin=[object Object]&originHeight=363&originWidth=655&originalType=binary&ratio=1&status=done&style=none)
/proc_misc.c中proc_misc_init函数的最后添加创建hidden文件的代码,并指定其回调函数。
![](https://img-blog.csdnimg.cn/img_convert/2be44c80924209dc6426120f4bf3720e.png#from=url&id=EA07w&margin=[object Object]&originHeight=67&originWidth=513&originalType=binary&ratio=1&status=done&style=none)

修改判断条件。

![](https://img-blog.csdnimg.cn/img_convert/77e54ebe50db3754df1a916f1f786d21.png#from=url&id=wn2ad&margin=[object Object]&originHeight=174&originWidth=468&originalType=binary&ratio=1&status=done&style=none)

![](https://img-blog.csdnimg.cn/img_convert/47cf04d18eed8e91513683d756b5da19.png#from=url&id=qjINf&margin=[object Object]&originHeight=274&originWidth=720&originalType=binary&ratio=1&status=done&style=none)

运行图:
重新编译后hidden默认为1(开启隐藏功能)
![](https://img-blog.csdnimg.cn/img_convert/f5f8693ab61adc5b798ea25263ab6f99.png#from=url&id=pr2SA&margin=[object Object]&originHeight=44&originWidth=253&originalType=binary&ratio=1&status=done&style=none)
将hidden改为0,此时之前被隐藏的进程都可以重新看到了
![](https://img-blog.csdnimg.cn/img_convert/11aafb749a001dba518ce42dc48bb69e.png#from=url&id=amMR6&margin=[object Object]&originHeight=53&originWidth=317&originalType=binary&ratio=1&status=done&style=none)
![](https://img-blog.csdnimg.cn/img_convert/425cf0746aa2c55c44bebff5ac3ee662.png#from=url&id=eOX9j&margin=[object Object]&originHeight=296&originWidth=466&originalType=binary&ratio=1&status=done&style=none)

功能七

在/proc目录下创建一个文件/proc/hidden_process,该文件的内容包含所有被隐藏进程的pid,各个pid之间用空格分开。
方法和创建hidden文件一样,hidden_process文件只需要设置读的回调函数即可。
在/fs/proc/proc_misc.c中添加回调函数
![](https://img-blog.csdnimg.cn/img_convert/47cf04d18eed8e91513683d756b5da19.png#from=url&id=SkkcD&margin=[object Object]&originHeight=274&originWidth=720&originalType=binary&ratio=1&status=done&style=none)
/proc_misc.c中proc_misc_init函数的最后添加创建hidden文件的代码,并指定其回调函数。
![](https://img-blog.csdnimg.cn/img_convert/c69e2c0022245c2391cb219c53f81df2.png#from=url&id=e7l1a&margin=[object Object]&originHeight=95&originWidth=573&originalType=binary&ratio=1&status=done&style=none)

![](https://img-blog.csdnimg.cn/img_convert/4bb81eebcd1abc3cc91dc68e76d9280b.png#from=url&id=tDqE3&margin=[object Object]&originHeight=208&originWidth=479&originalType=binary&ratio=1&status=done&style=none)
修改判断条件。

![](https://img-blog.csdnimg.cn/img_convert/b932cfcf808f0f4291163e30183dc393.png#from=url&id=niRWJ&margin=[object Object]&originHeight=459&originWidth=768&originalType=binary&ratio=1&status=done&style=none)

![](https://img-blog.csdnimg.cn/img_convert/03f843073b814be8cdb687bb3e3aa680.png#from=url&id=Ptoq4&margin=[object Object]&originHeight=421&originWidth=798&originalType=binary&ratio=1&status=done&style=none)

运行图:
![](https://img-blog.csdnimg.cn/img_convert/81dfe82551ffd6f512bc16d32233fe7c.png#from=url&id=IRbBc&margin=[object Object]&originHeight=49&originWidth=523&originalType=binary&ratio=1&status=done&style=none)
至此实验完成。

实验总结

通过这次实验,更加理解了系统调用的方式,熟悉了系统内核编程,我认为系统编程是要结合现有的系统内容,对自身的需求进行解决,任务 的完成很大一部分取决于自己对系统内容是否熟悉,对经常使用的概念与函数是否有影响,理解是否深刻。这次实验锻炼了我的编程能力与解决问题的能力,在经历无数次错误之后完成最终的结果是令人兴奋的。操作系统实验的经历提高了我很多。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值