Ubuntu20.04+Linux5.8.8 添加系统调用实现进程隐藏
虚拟机版本 | 主机信息 |
---|---|
VMware 15 | Thinkpad carbon X1 2020 + win10 |
目录
1 目标
- 实现系统调用int hide(pid_t pid, int on),在进程pid有效的前提下,如果on置1,进程被隐藏,用户无法通过ps或top观察到进程状态;如果on置0且此前为隐藏状态,则恢复正常状态。
- 考虑权限问题,根用户才能隐藏进程。
- 设计一个新的系统调用int hide_user_processes(uid_t uid, char *binname),参数uid为用户ID号,当binname参数为NULL时,隐藏该用户的所有进程;否则,隐藏二进制映像名为binname的用户进程。该系统调用应与hide系统调用共存。
2. 方法与原理
2.1 修改proc_pid_readdir
- 安全的方法
- 原理:
- proc目录中保存着正在运行的全部进程的相关信息文件,可以调用proc_pid_readdir逐一读取。
- 在进程的task_struct结构体中自定义一个hide变量,proc_pid_readdir()函数中加入判断条件,如果hide=1就跳过对该进程信息的读取。
- 自添加的系统调用通过修改该变量,实现进程的查看隐藏与恢复显示
2.2 pid赋值为0
- 投机取巧的危险的方法。
- 只对Linux早期版本(如2.x)有效。但是之后的版本无效,非常不建议使用,有太多bug。
- 原理:0号init进程不会被ps和top指令显示。
- Bug:隐藏的进程如果不恢复,重启后无法进入正确的0号init进程,导致死机。
2.3 原理
关键是理解和linux进程相关的结构体task_struct、结构体pid和结构体cred等,用于创建进程的fork函数的源码的基本实现。
详见我精选的参考博文:
3 遇到的困难
3.1 虚拟机开机黑屏
- 最有效的方法:使用快照备份关键节点
- 相关博文推荐:link,基本可以解决90%的Ubuntu系统崩溃问题。
4 流程
- 按照官网的步骤,完成对kernel代码修改Adding a New System Call
- 在kernel/sys.c文件里面末尾添加两个系统调用,使用SYSCALL_DEFINE2
SYSCALL_DEFINE2(hide, pid_t, pid,int,on)
{
printk("hide process invoked with params: pid=%d on=%d\n",pid,on);
printk("current uid = %d\n", get_current_cred()->uid.val);
//if( get_current_cred()->uid.val != 0) //判断是否为root用户
if(!uid_eq(current_euid(),GLOBAL_ROOT_UID))
{
printk("you aren't the root user! try to use sudo!\n");
return -1;
}
struct task_struct *p;
struct task_struct * me = NULL;
p = &init_task;
do
{
printk("check proc with pid = %d and old_pid = %d\n", p->pid,p->old_pid);
if( pid == p->old_pid ) //pid_t == int old_pid is global pid
{
me = p;
break;
}
}while( ( p = next_task(p) ) && ( p != &init_task ) );
//next_task get the next PCB pointer,
//it's a loop list, so the stop signal is p != &init_task!
if(me == NULL ) //判断pid是否有效
{
printk("the target pid doesn't exist!\n");
return -2;
}
if( on == 1 )
{
printk("hide proccess with pid=%d\n",me->pid);
me->hide = 1; //置隐藏标志为1
}
else
{
if( me->hide == 1 )
{
printk("reveal proccess with pid=%d\n",me->pid);
me->hide = 0;
}
}
printk("nice system call! goodbye!\n");
return 0;
}
SYSCALL_DEFINE2(hide_user_process,uid_t,uid,char*,binname){
struct task_struct *p;
struct user_namespace *ns = current_user_ns();
struct pid *thread_pid;
char buf[TASK_COMM_LEN];
char buf2[TASK_COMM_LEN];
int hide,i;
kuid_t kuid;
kuid = make_kuid(ns, uid);
printk("hide_user_proc invoked");
printk(" uid=%d ",uid);
if(binname!=NULL){
copy_from_user(buf2,binname,TASK_COMM_LEN);
printk(" binname=%s",buf2);
}else printk(" binname=NULL");
for_each_process(p){
if(uid_eq(task_uid(p),kuid)){
hide=1;
get_task_comm(buf, p);
if(binname!=NULL){
for(i=0;buf2[i];i++){
if(!buf[i]||buf[i]!=buf2[i]){
hide=0;
break;
}
}
if(buf[i])hide=0;
}
printk("scan on '%s' hide it? =%d",buf,hide);
if(hide){
p -> hide = 1;
thread_pid = get_pid(p->thread_pid);
proc_flush_pid(thread_pid);
}
}
}
return 0;
}
- 在include/linux/syscalls.h中的对应位置添加函数声明,注意返回值都是long,因为是64位系统
asmlinkage long sys_hide( pid_t pid, int on ); //my system call
asmlinkage long sys_hide_user_process( uid_t uid, char* binname ); //my system call
- 在include/uapi/asm-generic/unistd.h中的对应位置添加函数声明,同时注意修改 __NR_syscalls 440为__NR_syscalls 442
#define __NR_xyzzy 440
__SYSCALL(__NR_hide, sys_hide)
#define __NR_xyzzy 441
__SYSCALL(__NR_hide_user_process, sys_hide_user_process)
- 修改arch/x86/entry/syscalls/syscall_64.tbl,该表是x86_64的系统调用表,对应位置添加如下信息
440 64 hide sys_hide
441 64 hide_user_process sys_hide_user_process
- 在fs/proc/base.c中修改函数 proc_pid_readdir,在3343行之后添加如下一行
if (iter.task->hide==1) continue;
- 修改结构体task_struct,在include/linux/sched.h中636行之后添加如下
int hide;
- 修改进程派生的fork函数,在kernel/fork.c的2443行之后添加
p->hide = 0;
- 编译修改后的内核
# 从官网下载5.8.8的linux内核的压缩包,解压并在终端打开
# 依次修改对应位置的源码之后,执行如下指令
sudo make mrproper
sudo make clean
#进入界面后,直接exit,再yes,一般这个步骤的调整会影响微内核的大小,进而影响开机时间
sudo make menuconfig
# 我给ubuntu虚拟机的配置是,1个处理器,6个内核
#本机的CPU为一个CORE i7-10710U CPU, 6核12线程
sudo make -j12 # 同时允许最多12线程并行,约1h20min
sudo make modules_install //安装内核模块
sudo make install //安装内核
reboot
- 重启后,使用测试程序测试系统调用的准确性
待续