Linux添加系统调用(东南大学 OS Lab 1)

8668e703c09d44378725e556072e3855.png

在本次实验中,我将采用内核编译法来给Ubuntu添加一个系统调用。

如果大家照着我的步骤来设计,那么一定会成功。

本人Linux的版本是Ubuntu  22.04-desktop-LTS

想要添加一个系统调用,我们就必须要找到一个合适的内核版本,然后修改其内核代码,最后编译这个内核代码,编译完成后将其设为虚拟机默认的内核版本。

这里提供一个网站,可以查看每个Linux内核版本及其源代码,当然我们也可以搜索一些特定的C函数。链接如下:

Linux内核源代码阅读

一.下载内核

我们需要先更新apt和apt-get,如下:

sudo apt update
sudo apt-get update

然后下载wget工具:

sudo apt install wget

开始下载Linux内核源代码:

sudo wget https://git.kernel.org/torvalds/t/linux-6.0-rc7.tar.gz

大家可以将linux-6.0-rc7替换成自己想要下载的内核版本,其余不变。内核版本在上面的链接中可以查看。

下载完后将内核源代码解压到/usr/src中,这里面存储着自己系统已下载的内核版本源代码:

sudo tar -xvf linux-6.0-rc7.tar.gz -C /usr/src

我看了很多教程,都说不需要解压到/usr/src中,因为后面编译内核时会自动帮我们放入其中。反正我是解压到/usr/src并成功编译的。

二.添加系统调用

在本次实验中,需要实现两个要求:

  1. 实现系统调用int hide(pid_t pid, int on),在进程pid有效的前提下,如果on置1,进程被隐藏,用户无法通过ps或top观察到进程状态;如果on置0且此前为隐藏状态,则恢复正常状态(考虑权限问题,只有root用户才能隐藏进程)
  2. 设计一个新的系统调用int hide_user_processes(uid_t uid, char *binname),参数uid为用户ID号,当binname参数为NULL时,隐藏该用户的所有进程;否则,隐藏二进制映像名为name的用户进程

下面的操作均是在内核源码目录下进行操作的,所以先进入该目录下:

cd /usr/src/linux-6.0-rc7

1.添加前的准备

先下载vim编辑器:

sudo apt install vim

为了实现要求1,首先需要在task_struct结构体中添加一个int型的属性hide,如果hide为1,进程隐藏。task_struct结构体的定义在include/linux/sched.h中。输入下列命令:

sudo vim include/linux/sched.h

然后查找task_struct的定义,大家不知道怎么查找,可以先去了解一下vim如何查找某一字符串所在位置,我就不在这里浪费时间了,请自行阅读下面给出的教程。

Vim操作教程

找到task_struct的定义域后,先到达该定义域的末尾(直接让光标落在{上,然后输入%进行括号匹配,直接跳到末尾),然后往上划,找到一串字符串,如下图所示:

a877a0b9781a496aad284f50e201d027.png

这串注释(在一千五百多行)说,新的字段写在上面,所以我们将下列代码写到这段注释的上面:

int hide;

添加完后,保存并退出。

 然后修改kernel/fork.c中的copy_process函数:

sudo vim kernel/fork.c

在copy_process函数的定义域中,找到下列代码:

if (!p)
    goto fork_out;

然后在这段代码后面加上下列代码:

p->hide = 0;

 如下图所示:

abd19d8ca50e45e593d00d62b8b40058.png

然后在fs/proc/base.c中修改proc_pid_readdir函数的定义:

sudo vim fs/proc/base.c

根据下图所示来修改一小段代码:

740d704bcddb4a8e98dc6e09c2f4f25a.png

即将iter.task->hide == 0加入到if的条件中,并与原有条件使用&&连接。

2.开始添加

现在,开始将系统调用hide添加进内核中。

我们首先需要在include/linux/syscalls.h中添加函数声明:

sudo vim include/linux/syscalls.h

 添加的函数声明如下:

asmlinkage long sys_hide( pid_t pid, int on );          //my system call
asmlinkage long sys_hide_user_process( uid_t uid, char* binname ); 

最主要的是找到 /* kernel/fork.c */ 这一行注释,然后再下面添加声明,如下所示:

028aabbf6d644c52b96c29675a6161ff.png

然后进入arch/x86/entry/syscalls/syscall_64.tbl中:

sudo vim arch/x86/entry/syscalls/syscall_64.tbl

添加如下信息:

451     64     hide                  sys_hide
452     64     hide_user_process     sys_hide_user_process

如图所示:

da7e11f6f8f5407cb9ef1a3facaac63b.png

图中的注释说添加新的系统调用在最后一个common调用下,而我这边最后一个common调用是450号,所以将新的系统调用放到451和452中。(并不是说所有人的都是这样,大家需要随机应变)

然后在include/uapi/asm-generic/unistd.h中再次添加系统调用声明:

sudo vim include/uapi/asm-generic/unistd.h

 添加如下声明:

#define __NR_hide 451
__SYSCALL(__NR_hide, sys_hide)
#define __NR_hide_user_process 452
__SYSCALL(__NR_hide_user_process, sys_hide_user_process)

如下图所示:

8ceb50f9a44942c09a419cf2a3c0b1bd.png

要根据图中指示进行修改。

然后就到了最后的添加系统调用实现代码的时候了,进入kernel/sys.c中:

sudo vim kernel/sys.c

然后到末尾中添加如下的两个系统调用:

SYSCALL_DEFINE2(hide,pid_t,pid,int,on)  // 隐藏进程号对应的进程
{

	struct task_struct * me = NULL;  // 进程结构体对象
	
	me=pid_task(find_vpid(pid),PIDTYPE_PID);  // 获取对应进程的对象
	
	if(!uid_eq(current_euid(),GLOBAL_ROOT_UID))  // 判断是否是root用户
	{
	    printk("you aren't the root user! try to use sudo!\n");
		return -1;
	}

	if(me == NULL){  // 如果不存在pid对应的进程,返回
		return 0;
	}

	if( on == 1 )  // 如果on是1,则是要隐藏该进程,否则是要将已被隐藏的进程恢复
	{
		me->hide = 1;
	}
	else
	{
		if( me->hide == 1 )
		{
			me->hide = 0;
		}
	}
	return 0;
}

SYSCALL_DEFINE2(hide_user_process, uid_t, uid, char *, binname) { 
    if (!uid_eq(current_euid(), GLOBAL_ROOT_UID)) {  // 先判断是否是root用户
        printk("You aren't the root user! Try to use sudo!\n");
        return -1;
    }

    char user_binname[TASK_COMM_LEN];  // 将处于用户空间的binname转到内核空间上
    if (binname != NULL) {
        copy_from_user(user_binname, binname, TASK_COMM_LEN);
        printk("User binary Name: %s\n", user_binname);
    }
    
    struct task_struct * task = NULL;

    for_each_process(task) {  // 按顺序读取系统中所有进程
        if (task->real_cred->uid.val == uid) {  // 如果该进程属于uid对应的用户进程
            if (binname == NULL) {  // 如果binname为NULL,则隐藏该进程
                task->hide = 1;
            } else {  // 如果binname不为NULL,则隐藏与binname对应的用户进程
                if (strcmp(user_binname, task->comm) == 0) {  // 进行字符串比对
                    task->hide = 1;
                    printk("Task binary Name: %s\n", task->comm);
                }
            }
        }

        
    }

    return 0; // Successfully hid process(es)
}

三.配置内核

首先,需要下载所需要的工具:

sudo apt install build-essential dwarves python3 libncurses-dev flex bison libssl-dev bc libelf-dev zstd gnupg2 make gcc g++ libc6-dev bin86 qttools5-dev -y

然后进入解压后的内核目录:

cd /usr/src/linux-6.0-rc7

然后先清理掉之前编译过的结果:

sudo make mrproper
sudo make clean

将现在Ubuntu使用的内核版本的配置文件复制一份到将要编译内核中:

sudo cp -v /boot/config-$(uname -r) .config

然后进行内核配置:

sudo make menuconfig

一般来说保持默认即可,即直接点击Save,然后Ok,然后Exit,再Exit。

最重要的一步来了,先输入下列命令:

sudo gedit .config

使用gedit编辑器对.config进行图形化编辑,点击Ctrl + F,分别搜索CONFIG_SYSTEM_TRUSTED_KEYSCONFIG_SYSTEM_REVOCATION_KEYS这两个变量,将其赋值为为空字符串,即结果如下:

CONFIG_SYSTEM_TRUSTED_KEYS = ""
CONFIG_SYSTEM_REVOCATION_KEYS = ""

到了这一步,内核配置已经完成。

四.编译内核

bzImage 是 Linux 内核编译生成的压缩镜像文件,用于引导计算机系统并提供操作系统核心功能。我们先对其进行编译:

sudo make bzImage -j4

上面命令的作用是以4个线程来对bzImage进行编译。使用多少个线程,取决于你在创建Ubuntu虚拟机时分配了多少个处理器。在Linux中线程等价于进程,所以一个处理器在处理进程时也是在处理线程。

如果你不知道分配了多少个处理器, 你可以直接输入下面命令进行编译:

sudo make bzImage -j$(cat /proc/cpuinfo | grep "processor" | wc -l)

 直接动态获取/proc/cpuinfo中存储的处理器分配信息。

对bzImage编译完成后,紧接着开始内核模块的编译:

sudo make modules -j$(cat /proc/cpuinfo | grep "processor" | wc -l)

 编译完内核模块后,需要将已编译的 Linux 内核模块安装到指定的目录,通常用于将新编译的内核模块安装到系统中:

sudo make modules_install

这里就不用多线程了,因为只需要几分钟时间,很快就好。

我们还需要将编译好的内核映像文件(通常是 bzImage)复制到系统启动目录(通常是 /boot 目录)下,以及更新系统引导程序( GRUB )的配置文件,以便它能够识别和引导新安装的内核:

sudo make install

到了这里,编译内核部分已经结束了。我们可以前往/boot目录下查看是否有下面的文件:

initrd.img-6.0.0-rc7
vmlinuz-6.0.0-rc7

如果有的话,就是编译成功了。 

当然,如果大家不放心GRUB 是否会更新的话,可以按照正常做法,继续输入下列命令:

sudo update-grub

五.设置默认内核版本

编译完内核后,Ubuntu22.04并不会设置其为默认内核版本,我们需要手动去设置。

在Ubuntu中,我们需要修改/etc/default/grub中的GRUB_DEFAULT变量的值,如下:

sudo vim /etc/default/grub  #使用vim进行修改
/GRUB_DEFAULT #从上到下搜索变量GRUB_DEFAULT
#原本是GRUB_DEFAULT = 0, 修改后如下
GRUB_DEFAULT = "Advanced options for Ubuntu>Ubuntu, with Linux 6.0.0-rc7"
#退出并保存

将6.0.0-rc7换成自己的内核版本号即可。

然后再次更新GRUB:

sudo update-grub

紧接着重启虚拟机:

reboot

重启后查看当前内核版本:

uname -sr

然后发现已经变成Linux 6.0.0-rc7了。

六.测试系统调用

先写一个C程序,来对hide系统调用进行测试:

进入桌面上,创建C程序:

cd
mkdir Lab1
sudo vim test_hide.c

程序代码如下: 

#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc,char **argv)
{
        int pid;
        int hide;
        scanf("%d %d",&pid,&hide);

        printf("System call return %ld\n",syscall(451,pid,hide));
        return 0;
}

对其进行编译:

sudo gcc test_hide.c -o test_hide

然后再写一个C程序来测试系统调用hide_user_process:

sudo vim test_hide_user_process.c

代码如下:

#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

int main(int argc,char **argv)
{
        int uid;
        char binname[20];
        scanf("%d %s",&uid,binname);

        printf("%s\n",binname);

        bool noBinname=false;
        if(strcmp(binname,"no") == 0){
                printf("Bin name set null\n");
                noBinname=true;
        }


        printf("System call return %ld\n",syscall(452,uid,noBinname?NULL:binname));
        return 0;
}

对其进行编译:

sudo gcc test_hide_user_process.c -o test_hide_user_process

课上的链接:需要下载某些东西

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Huonzy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值