一、实验内容

  • 阅读分析Linux内核源代码, 了解进程控制块, 进程队列等数据结构

  • 实现一个系统调用hide,使得可以根据指定的参数隐藏进程,使用户无法使用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系统调用共存。

二、实验目的

通过实验,加深理解进程控制块、进程队列等概念,了解进程管理的具体实施方法。

三、设计思路

ps

ps是用于显示进程瞬时信息的,可以用strace ps查看ps的系统调用,部分输出如下

可以看出主要调用了”/proc/pid”、”/proc/pid/status” 这两个文件,我们查看/proc 目录:

可以发现,/proc 下有当前所有进程的文件,文件名即为进程的pid。因此若需要实现隐藏进程, 可以在ps读取指定进程文件时修改判断条件, 使其忽略该进程信息。

top

top 命令可以动态地持续监听进程地运行状态,我们一般使用top -u seu来实现对某个用户(seu)的监听。

用户权限

我们通过cat /etc/group命令查看group文件获得所有用户的uid:

可以看到,root用户的uid为0,seu用户的uid为500。在hide()hide_user_processes()函数开始前先对用户uid进行判断, 若非root用户则直接
返回.,否则执行相应的系统调用。

四、实验环境

  • VMware 16

  • Fedora 7

五、主要数据结构

task_struct 说明:
定义在 linux/sched.h 中. 这个结构体相当于PCB, 其中存储了进程的相关信息, 包括pid, uid 等. 因此可以在其中添加变量指示该进程是否被隐藏。
在每次创建task_struct 时, 需要初始化为NULL 或者init_task. 在遍历整个进程树时, 可以使用for 循环或者for_each_process()

六、编译内核

  1. 解压源代码

    1
    2
    3
    cd Desktop
    tar zxvf linux-2.6.21.tar.gz
    cd linux-2.6.21
  2. 生成内核配置文件

    将当前正在运行的内核对应的配置文件作为模板来生成.config文件,即将/boot目录下的已有的config文件复制到linux-2.6.21目录下

    1
    2
    3
    4
    su root
    make mrproper //清理之前编译残留的文件
    sudo cp /boot/config-2.6.21-1.3194.fc7 ./config
    make oldconfig //更新config文件
  3. 编译安装内核

    1
    2
    3
    make all
    make modules
    make install
  4. 重启系统

    1
    reboot

七、系统调用hide()实现

文件修改

  1. linux-2.6.21/include/linux/sched.h

    在task_struct结构体中添加int hide ,指示当前进程是否被隐藏。

  1. linux-2.6.21/kernel/fork.c

    copy_process()中初始化hide0。当hide=1时进程需要被隐藏,当hide=0时进程不需要被隐藏。

  2. linux-2.6.21/fs/proc/base.c

    • 为筛选出需要被隐藏的进程,阻止系统返回信息,在proc_pid_readdir()添加判断条件

    ​ 即只有那些不需要隐藏的进程才能执行下面的代码, 被系统放入缓存。

    • 为阻止系统对隐藏进程的路径名的解析,在proc_pid_lookup()函数中添加如下代码:

  3. linux-2.6.21/kernel/hide.c

    接下来是定义hide系统调用, 通过用户的传参对给定的pid的hide变量进行修改, 指定其隐藏与否。 在kernel中新建文件 hide.c 添加函数asmlinkage long sys_hide(pid_t pid, int on),定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include<linux/linkage.h>
    #include<linux/types.h>
    #include<linux/sched.h>
    #include<linux/pid.h>
    #include<linux/proc_fs.h>

    asmlinkage long sys_hide(pid_t pid, int on)
    {
    struct task_struct *p=NULL;
    if(pid>0 && (current->uid)==0)//only the root user can hide the process
    {
    p=find_task_by_pid(pid);
    p->hide=on;//set the state of the process
    if(on==1)
    {
    printk("Process %d is hidden by root.\n",pid);
    }
    if(on==0)
    {
    printk("Process %d is displayed by root.\n",pid);
    }
    proc_flush_task(p);
    }
    else
    printk("Sorry, you are not root user.Permission denied.\n");

    return 0;
    }
    • 用户权限设置

      上述代码中对(current->uid)==0的判断可以保证只有root用户可以执行该代码块,若不是root用户则直接退出。在下面介绍的sys_hide_user_processes()函数中同样设置了用户权限。

系统调用修改

  1. arch/i386/kernel/syscall_table.S

    文件末尾添加

    1
    .long sys_hide
  2. include/asm-i386/unistd.h

    添加

    1
    #define __NR_hide   320

    修改#define NR_syscalls 320

    1
    #define NR_syscalls 321
  3. include/linux/syscalls.h

    文件末尾添加声明

    1
    asmlinkage long sys_hide(pid_t pid, int on);
  4. kernel/Makefile

    obj-y =后添加

    1
    hide.o

编译内核

打开终端

1
2
3
4
5
6
7
8
su root
cd linux-2.6.21
make mrproper //删除以前垃圾文件
make menuconfig //启动配置图形界面
make all //编译所有文件
make modules //编译模块
make install //安装内核
reboot //重启

测试

  1. 测试文件hide_test.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include<stdio.h>
    #include<sys/syscall.h>
    #include<unistd.h>

    #define __NR_hide 320 //对应于asm-i386/unistd.h中

    int main()
    {
    printf("start\n");
    pid_t pid=3918;
    int on=0;//测试中修改
    syscall(__NR_hide,pid,on);
    return 0;
    }
  1. 文件编译及运行

    1
    2
    gcc hide_test.c -o hide_test
    ./hide_test
  1. 测试结果
  • ​ 在终端查看目前用户为seu的进程

  • 选择进程3912,在测试文件中修改pid = 3912,on = 1,编译后在普通用户下运行,可以看见,在top后该进程依然被显示出来。

  • 进入root用户,重新执行测试文件,可以看见,进程3912被隐藏

  • 修改on = 0,编译后在root下运行,可以看见进程3912又被显示出来

hide()系统调用测试通过。

八、系统调用hide_user_processes()实现

文件修改

linux-2.6.21/kernel/hide_uesr_processes.c

相比于sys_hide系统调用, 该系统调用只是改变了传参. 实现的思路类似。在kernel中新建文件 hide_uesr_processes.c ,添加函数asmlinkage long sys_hide_user_processes(uid_t uid,char *binname,int recover),uid为该进程的用户id,binname为进程名COMMAND,根据要求隐藏对应进程。第三个参数recover用于恢复被隐藏的进程。如果recover=0那么隐藏相应的进程,如果不为0,则恢复所有进程为显示状态。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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 long 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->hide=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->hide=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->hide=0;
}
}

return 0;
}

*系统调用修改与内核编译与hide()相同,不再作过多赘述。

测试

  1. 测试文件hide_user_test.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<stdio.h>
    #include<sys/syscall.h>
    #include<unistd.h>

    #define __NR_hide_user_process 321

    int main()
    {
    printf("start \n");
    uid_t uid=500; //user
    char *binname=NULL;
    int recover=0;
    syscall(__NR_hide_user_process,uid,binname,recover);
    return 0;
    }
  2. 测试结果

  • 在终端查看目前用户为seu的进程

  • 修改测试文件参数uid = 500(seu用户),binname = NULL , recover = 0

在普通用户下编译并执行程序,seu用户的进程未被隐藏,此处不再过多展示

  • 进入root用户执行程序,seu用户的进程被隐藏

  • 修改recover=1之后,seu用户进程重新被显示出来

  • 我们选择PID=4006的进程,修改测试文件参数binname = "dbus-launch" , recover = 0,重新编译运行代码,可以看见,进程4006被隐藏

hide_user_processes()系统调用测试通过。

九、实验体会

本实验主要对Linux中的进程管理进行了探究, 对上学期的操作系统课程的理论知识进行了实践。

在本次实验中我学到了很多知识。首先我了解到PCB在Linux内核中体现为task_struct结构体;其次我对于如何添加系统调用更加清楚,不仅要在syscall.h中声明, 而且需要在syscall_table.S和unistd.h中添加编译语句和宏定义,还需要在Makefile中加入中间目标文件(*.o);最后我还学会了如何编译内核。

本次实验参考了许多网上的代码,许多文章侧重点不同,在同学的帮助和对文章的整合理解后完成此篇实验报告。

参考文章:

OSEXP/OSEXP-1.md at master · ch1y0q/OSEXP (github.com)

(22条消息) 操作系统实验二 内核编译winnower-sliff的博客-CSDN博客操作系统实验内核编译

操作系统实验三:Linux进程管理及其扩展 - 去冰三分糖 - 博客园 (cnblogs.com)