建筑资建设库网站缺陷,电子商务网站建设实训 报告,百度指数分析报告案例,深圳小程序制作前言
最近完成了一个需要修改和编译linux内核源码的操作系统实验#xff0c;个人感觉这个实验还是比较有意思的。这次实验总共耗时4天#xff0c;从对linux实现零基础#xff0c;通过查阅资料和不断尝试#xff0c;直到完成实验目标#xff0c;在这过程中确实也收获颇丰个人感觉这个实验还是比较有意思的。这次实验总共耗时4天从对linux实现零基础通过查阅资料和不断尝试直到完成实验目标在这过程中确实也收获颇丰特此记录
实验内容
实现系统调用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时隐藏该用户的所有进程否则隐藏二进制映像名为binname的用户进程在/proc目录下创建一个文件/proc/hidden该文件可读可写对应一个全局变量hidden_flag当hidden_flag为0时所有进程都无法隐藏即便此前进程被hide系统调用要求隐藏。只有当hidden_flag为1时此前通过hide调用要求被屏蔽的进程才隐藏起来在/proc目录下创建一个文件/proc/hidden_process该文件的内容包含所有被隐藏进程的pid各pid之间用空格分开
实现思路
对于要求1首先要修改PCB对应到源码里面就是task_struct在其中添加一个属性hide用来表示该进程是否需要隐藏然后修改复制进程的系统调用用于给hide属性设置默认值0最后修改列举所有进程的系统调用在其中加入一个判断如果进程的hide是1则不展示这个进程 注也有方法说是可以通过把pid设置为0来达到隐藏的效果但是实测下来在5.15.60的kernel里面这样做不能隐藏所以只能通过劫持系统调用来实现
对于要求2则可以遍历所有进程把符合条件的进程的hide设置为1即可
对于要求3最开始以为可以通过用户态的文件操作来实现结果后来发现/proc是个虚拟文件系统所以需要在初始化proc文件系统时添加一个hide条目然后设置这个条目的write函数来达到创建该文件的目的
对于要求4也是用和要求3一样的思路只是这里需要设置read函数然后遍历所有进程把hide为1的pid全部返回
实验环境
操作系统使用的ubuntu 22.04 linux kernel代码版本是5.15.60 虚拟机使用的是VM Ware Workstation Pro 16 注意虚拟机硬盘大小建议为60GB编译内核代码非常吃硬盘本人在实验中前前后后扩容了几次硬盘最终发现60GB是个比较合适的大小内核源码编译安装之后还能剩15GB左右下一次编译安装还需要一些硬盘空间做缓存所以剩15GB是比较合适的
实验流程
编译与安装内核
参考https://www.cnblogs.com/robotech/p/16152269.html 即可如果这部分出错了网上可以找到的资料很多这里不再赘述了 不过这一步一定要有耐心源码编译很慢第一次全量编译估计会耗时一个多小时可以用这个闲暇时间玩玩原神
完成要求一
把编译和安装的流程跑通以后就开始进行源代码的修改了
修改PCB
linux的PCB结构体是task_struct这个定义位于include/linux/sched.h Tips如果想在linux源码里找东西可以用https://elixir.bootlin.com/ 这个网站左边选择版本右边输入关键字即可查询到 在linux使用vim打开这个文件往下翻看到这段注释 按照提示添加属性即可
修改fork
这部分的源码是在kernel/fork.c中的copy_process函数里面 阅读源码在合适的地方插入初始化hide属性的代码即可这里我选择的位置是复制完进程信息之后即下图所示的位置
添加系统调用
这部分我是看网上的各种文章东拼西凑进行多次实验之后才跑通的事后想想我应该最先去看linux kernel的官方手册 https://docs.kernel.org/process/adding-syscalls.html 附上官方手册 以下是我自己添加系统调用的过程这里用添加一个输出Hello World的简单系统调用来举例子 首先找到kernel/sys.c在文件末尾使用SYSCALL_DEFINE宏来定义系统调用的函数体
SYSCALL_DEFINE0(hello)
{printk(hello world.114514\n);return 0;
}解释一下SYSCALL_DEFINE0(hello)表示定义一个含有0个参数的系统调用名字是hello通过查看sys.c里面其它函数的定义代码可以得知如果想要添加一个只有一个参数的系统调用那么应该使用SYSCALL_DEFINE1(hide,pid_t,pid)其中hide是系统调用的名字pid_t是第一个参数类型pid是第一个参数的名字2个参数的同理 printk是输出日志这个日志可以在sudo dmesg里面看到printk支持使用%d%s等对输出进行格式化用法类似于printf
接下来修改系统调用表在arch/x86/entry/syscalls/syscall_64.tbl中合适的地方添加刚才写的系统调用这里我是添加在了334号系统调用之后的 仿照上面334写即可其中hello是自己随便起的名字而sys_hello是系统调用的函数名这个函数名是上面SYSCALL_DEFINE0里写的函数名前面加上前缀sys_得到的
添加完成后可以尝试编译运行一下新的内核 可以使用uname -a查看当前内核是不是最新编译的看时间即可 下面将使用一段代码来测试一下新添加的335号系统调用
#include linux/kernel.h
#include sys/syscall.h
#include unistd.h
#include stdio.hint main(int argc,char **argv)
{printf(System call return %ld\n,syscall(335));return 0;
}运行程序 然后使用sudo dmesg查看
编写hide系统调用
hide系统调用的实现有2个思路一个是遍历所有进程找到pid相符的进程然后设置hide另一个思路是通过pid找到进程然后直接设置这里采取后者 通过查找资料可以得知根据pid查找进程是用这段代码 pid_task(find_vpid(pid),PIDTYPE_PID); 最终完整的系统调用代码如下
SYSCALL_DEFINE2(hide,pid_t,pid,int,on)
{struct task_struct * me NULL;mepid_task(find_vpid(pid),PIDTYPE_PID);if(current-uid ! 0){//User is not rootreturn 0;}if(me NULL){return 0;}if( on 1 ){me-hide 1;}else{if( me-hide 1 ){me-hide 0;}}return 0;
}接下来再修改系统调用表即可完成系统调用的添加
劫持获取所有进程的函数
现在已经可以通过系统调用来设置PCB里面的hide下一步就是修改列举所有进程的函数让它在列举时判断一下如果hide1就不列举
proc文件系统
在劫持之前需要简单介绍一下proc文件系统。在linux根目录下有一个/proc文件夹这其实并不是在磁盘上真实存在的文件而是一个虚拟文件系统。 proc文件夹里面有很多个以pid为名字的文件夹这些文件夹里面又有若干个文件读取这些文件就可以获取这个进程的相关信息例如想查看pid为1的程序的名字可以使用sudo cat /proc/1/comm 这一系列操作在系统底层的实现是系统在启动的时候就挂载了一个proc虚拟文件系统当用户访问proc文件夹下的文件时系统会调用proc文件系统里面相关的函数而不是常规文件系统的函数例如在执行ls /proc时实际上系统会调用位于s/proc/base.c里面的proc_pid_readdir函数这个函数会获取当前系统中所有的进程随后会有函数把这个函数的返回值写入到读取文件操作的缓冲区中
修改代码
所以我们的突破口就是proc_pid_readdir函数在阅读这个函数的代码之后可以找到突破口是一个put_task_struct函数的调用如下图 那么只需要在这个if里面加上一个条件即必须这个进程不被隐藏才能put即可完成劫持
结果验证
在修改完源代码之后重新编译和安装内核启动新的内核 使用下面这段代码来测试
#include linux/kernel.h
#include sys/syscall.h
#include unistd.h
#include stdio.h
#include string.hint main(int argc,char **argv)
{int pid;int hide;scanf(%d %d,pid,hide);printf(System call return %ld\n,syscall(336,pid,hide));return 0;
}编译运行程序 从图中可以看出进程顺利隐藏并且能够重新展示要求一顺利实现
完成要求二
要求二是在要求一的基础上进行一些简单的扩展这里可以使用一个比较暴力的思路就是遍历所有进程然后挨个判断uid和进程名称把符合要求的进程的hide设置为1即可 这里只有三点需要注意一下 1.遍历所有进程可以使用for_each_process这个宏来完成这个宏有类似于for循环的作用用法如下
struct task_struct* p;
for_each_process(p){//Do something.....
}这个宏的定义在include/linux/sched/signal.h里面定义如下
#define for_each_process(p) \for (p init_task ; (p next_task(p)) ! init_task ; )2.用户态的字符串不能在内核态直接使用需要调用strncpy_from_user把用户态的字符串复制到内核态的缓冲区才能使用方法如下
char tmp_buf[256];
if(binname ! NULL)strncpy_from_user(tmp_buf,binname,256);最终的系统调用代码如下
SYSCALL_DEFINE2(hide_user_processes,uid_t,uid,char*,binname)
{uid_t curr_uidcurrent-uid;if(curr_uid ! 0){//User is not rootreturn 0;}char tmp_buf[256];if(binname ! NULL)strncpy_from_user(tmp_buf,binname,256);struct task_struct* pNULL;for_each_process(p){if(p-real_cred-uid.val uid){if(binname NULL){p-hide1;}else{char* sp-comm;int identical1;int i0;for(i0;tmp_buf[i]!\0 s[i] ! \0;i){if(tmp_buf[i] ! s[i]){identical0;break;}}if(tmp_buf[i] ! s[i])identical0;if(identical 1){p-hide1;}}}}return 0;
}在编译和安装完成之后可以写一段测试代码来验证一下代码的正确性
#include linux/kernel.h
#include sys/syscall.h
#include unistd.h
#include stdio.h
#include string.h
#include stdbool.hint main(int argc,char **argv)
{int uid;char binname[20];scanf(%d %s,uid,binname);printf(%s\n,binname);bool noBinnamefalse;if(strcmp(binname,no) 0){printf(Bin name set null\n);noBinnametrue;}printf(System call return %ld\n,syscall(337,uid,noBinname?NULL:binname));return 0;
}编译运行该程序 要求二完成
完成要求三
思路分析
之前提到过的proc文件系统是一个虚拟文件系统读取和写入proc文件夹下的文件的操作会交给一些特定的内核函数来执行那么我们只需要添加一个/proc/hide条目并配置这个条目的write函数当write被调用的时候就根据写入的值设置一个全局变量然后再修改proc_pid_readdir函数添加一个判断如果这个全局变量为0就不隐藏任何进程这样就可以达到设置全局开关的目的
全局变量的定义和使用
全局变量可以跨文件被使用在需要使用全局变量的地方使用extern关键字声明全局变量即可 需要注意的是全局变量需要进行一次初始化并且仅可以进行一次初始化 具体而言可以这样操作在需要使用全局变量hidden_flag的c文件里面使用下面这条语句进行声明
extern int hidden_flag;然后在某个c文件中对hidden_flag变量进行定义
extern int hidden_flag;
int hidden_flag1;注意声明是告诉编译器我这里有一个名叫hidden_flag的变量我接下来会用这个变量这个变量具体在哪需要编译器自己去找而定义则是告诉编译器我新建了一个名为hidden_flag的变量相当于真正为这个变量分配了内存空间
添加proc条目
大体流程
通过查阅资料和反复实验我找到了在5.15.60版本添加proc条目的方法 proc文件系统的初始化函数在fs/proc/root.c里面名叫proc_root_init 网上很多教材是要修改一个名叫proc_misc_init函数但是在这个版本的内核源码里面找不到这个函数所以索性就在proc_root_init函数里面添加条目了因为看网上的代码root_init是会调用misc_init的所以猜测直接在root_init里面添加应该也是可以的最后实践证明确实可行
添加proc条目需要调用proc_create函数该函数的定义如下
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct proc_ops *proc_ops);可以看到这个函数需要4个参数第一个是文件名这里要创建一个/proc/hidden所以这个参数传hidden第二个参数是权限为了防止后续因为权限问题导致实验翻车这里就给666了第三个是parent传NULL即可第四个是这个条目操作的配置项的指针可以在这里配置该条目的read和write函数
下面开始添加proc条目 首先要实现该条目的read和write函数当用户态程序读取和写入/proc/hidden时这两个函数就会被调用
read函数
下面是read函数的定义
ssize_t hidden_read_proc(struct file *filp,char *buf,size_t count,loff_t *offp);第一个参数是文件第二个参数是用户态的读取缓冲区我们需要往这个缓冲区里面写数据来完成读取操作第三个参数是这个用户态缓冲区的大小第四个参数是上一次读取的位置因为可能出现缓冲区不够等情况用户态程序在读文件时通常是用下面的方式进行多次读取的
char buf[256];
int len;
while((lenread(buf))!0){//此时buf中读取了len字节的数据进行相应处理
}所以read函数要做的事情就是往缓冲区中写入数据修改offp然后返回已经读入的字节数下面是/proc/hidden条目的read函数的实现
ssize_t hidden_read_proc(struct file *filp,char *buf,size_t count,loff_t *offp )
{if(*offp 0)return 0;char msg[256];int lensprintf(msg,Current flag is %d\n,hidden_flag);copy_to_user(buf,msg,len);*offplen;return len;
}需要注意的是如果没有最开始判断offp这一行那么会出现读取/proc/hidden文件读不完的情况具体而言如果使用指令cat /proc/hidden那么它会一直源源不断地蹦出字符不会停这是因为read函数始终不会返回0导致那个while循环不会停 此外同样的内核态的内存和用户态的内存是不互通的需要使用copy_to_user函数来完成内存的拷贝
write函数
write函数的定义如下
ssize_t hidden_write_proc(struct file *filp,const char *buf,size_t count,loff_t *offp);参数的意义和read是类似的第二个参数是用户即将写入的数据缓冲区地址第三个则是数据量以下是write函数的具体实现
ssize_t hidden_write_proc(struct file *filp,const char *buf,size_t count,loff_t *offp)
{char msg[2056];copy_from_user(msg,buf,count);hidden_flagmsg[0]-0;return count;
}需要注意的是同样的需要进行从用户态到内核态的内存拷贝
proc_ops结构体
接下来新建一个proc_ops结构体的对象传入我们写的read和write函数
struct proc_ops hidden_proc_fops {proc_read: hidden_read_proc,proc_write: hidden_write_proc
};当然这个结构体还支持我们配置更多的内容具体可以看这个结构体的定义这里不再赘述了
调用proc_create
最后调用proc_create传入参数即可完成条目的创建
proc_create(hidden,666,NULL,hidden_proc_fops);结果验证
我们重新编译安装内核然后隐藏一个进程然后再向/proc/hidden里面写入0 可见hidden_flag起效果了要求三完成
完成要求四
有了要求三的铺垫要求四就显得比较简单了只需要实现一个read函数在其中遍历所有进程把hide为1的进程pid返回即可 read函数的实现如下
ssize_t pid_read_proc(struct file *filp,char *buf,size_t count,loff_t *offp )
{if(*offp 0)return 0;char msg[1024];int len0;struct task_struct* p;for_each_process(p){if(p-hide 1){len sprintf(msglen,%d ,p-pid);}}copy_to_user(buf,msg,len);*offplen;return len;
}可以把1000用户所有进程隐藏了然后查看/proc/hidden_process文件来检查效果 可以在里面看到所有被隐藏的进程的pid要求四完成
总结与心得
这次实验的代码量并不多操作步骤也不复杂主要的时间都花在了学习linux内核编程上面了。从零开始学习proc文件系统linux源码并建立临时知识体系然后根据学到的东西进行开发实践这是一个充满挑战性但也非常有意思的过程。在这过程中我学到了linux内核编程的技术跑通了从内核源码修改到最终运行的全流程并对proc虚拟文件系统进行了更深入的自学完成了四个实验要求。 个人感觉这过程中查资料自学的效率有点低下次遇到此类问题应该首先查找官方的手册和教程而不是在网上胡乱找相关的文章。 总的而言收获很多这是一次非常有意思的经历。 Anyway写这篇博客也是记录一下这次实验的经历感悟和收获同时也为其他做这个实验的同学提供一点过来人的经验希望能起到避坑的效果。