在本次实验中,我将采用内核编译法来给Ubuntu添加一个系统调用。
如果大家照着我的步骤来设计,那么一定会成功。
本人Linux的版本是Ubuntu 22.04-desktop-LTS。
想要添加一个系统调用,我们就必须要找到一个合适的内核版本,然后修改其内核代码,最后编译这个内核代码,编译完成后将其设为虚拟机默认的内核版本。
这里提供一个网站,可以查看每个Linux内核版本及其源代码,当然我们也可以搜索一些特定的C函数。链接如下:
一.下载内核
我们需要先更新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并成功编译的。
二.添加系统调用
在本次实验中,需要实现两个要求:
- 实现系统调用int hide(pid_t pid, int on),在进程pid有效的前提下,如果on置1,进程被隐藏,用户无法通过ps或top观察到进程状态;如果on置0且此前为隐藏状态,则恢复正常状态(考虑权限问题,只有root用户才能隐藏进程)
- 设计一个新的系统调用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如何查找某一字符串所在位置,我就不在这里浪费时间了,请自行阅读下面给出的教程。
找到task_struct的定义域后,先到达该定义域的末尾(直接让光标落在{上,然后输入%进行括号匹配,直接跳到末尾),然后往上划,找到一串字符串,如下图所示:
这串注释(在一千五百多行)说,新的字段写在上面,所以我们将下列代码写到这段注释的上面:
int hide;
添加完后,保存并退出。
然后修改kernel/fork.c中的copy_process函数:
sudo vim kernel/fork.c
在copy_process函数的定义域中,找到下列代码:
if (!p)
goto fork_out;
然后在这段代码后面加上下列代码:
p->hide = 0;
如下图所示:
然后在fs/proc/base.c中修改proc_pid_readdir函数的定义:
sudo vim fs/proc/base.c
根据下图所示来修改一小段代码:
即将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 */ 这一行注释,然后再下面添加声明,如下所示:
然后进入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
如图所示:
图中的注释说添加新的系统调用在最后一个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)
如下图所示:
要根据图中指示进行修改。
然后就到了最后的添加系统调用实现代码的时候了,进入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_KEYS和CONFIG_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
课上的链接:需要下载某些东西